[toranj] add cli-based test-cases (#8599)

This commit updates cli-based `toranj` test framework and adds new
test-cases using this framework. The new test-cases re-implement all
the current ncp/wpantund based ones that cover OpenThread core
behavior (including the multi-radio and trel test-cases). Some of
the tests are enhanced (e.g. adding new checks) and some related
tests are combined into a single script.

This commit also updates the `toranj` CI GitHub workflow to use the
new cli-based model for multi-radio and trel (removing the ncp
based versions).
This commit is contained in:
Abtin Keshavarzian
2023-01-05 22:49:06 -08:00
committed by GitHub
parent ec494348f8
commit fd9f3237eb
32 changed files with 4606 additions and 37 deletions
+2 -2
View File
@@ -51,7 +51,7 @@ jobs:
strategy:
fail-fast: false
matrix:
TORANJ_RADIO: ['15.4', 'trel', 'multi']
TORANJ_RADIO: ['15.4']
env:
COVERAGE: 1
TORANJ_RADIO : ${{ matrix.TORANJ_RADIO }}
@@ -104,7 +104,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
TORANJ_RADIO: ['15.4']
TORANJ_RADIO: ['15.4', 'trel', 'multi']
env:
COVERAGE: 1
TORANJ_RADIO : ${{ matrix.TORANJ_RADIO }}
+14 -1
View File
@@ -145,6 +145,10 @@ Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, voi
#endif
#endif // OPENTHREAD_FTD || OPENTHREAD_MTD
{
#if (OPENTHREAD_FTD || OPENTHREAD_MTD) && OPENTHREAD_CONFIG_CLI_REGISTER_IP6_RECV_CALLBACK
otIp6SetReceiveCallback(GetInstancePtr(), &Interpreter::HandleIp6Receive, this);
#endif
OutputPrompt();
}
@@ -1957,7 +1961,7 @@ template <> otError Interpreter::Process<Cmd("channel")>(Arg aArgs[])
*/
else if (aArgs[1] == "delay")
{
error = ProcessSet(aArgs + 2, otChannelManagerSetDelay);
error = ProcessGetSet(aArgs + 2, otChannelManagerGetDelay, otChannelManagerSetDelay);
}
/**
* @cli channel manager interval
@@ -7195,6 +7199,15 @@ void Interpreter::HandleDiscoveryRequest(const otThreadDiscoveryRequestInfo &aIn
}
#endif
#if OPENTHREAD_CONFIG_CLI_REGISTER_IP6_RECV_CALLBACK
void Interpreter::HandleIp6Receive(otMessage *aMessage, void *aContext)
{
OT_UNUSED_VARIABLE(aContext);
otMessageFree(aMessage);
}
#endif
#endif // OPENTHREAD_FTD || OPENTHREAD_MTD
void Interpreter::Initialize(otInstance *aInstance, otCliOutputCallback aCallback, void *aContext)
+4
View File
@@ -508,6 +508,10 @@ private:
void HandleDiscoveryRequest(const otThreadDiscoveryRequestInfo &aInfo);
#endif
#if OPENTHREAD_CONFIG_CLI_REGISTER_IP6_RECV_CALLBACK
static void HandleIp6Receive(otMessage *aMessage, void *aContext);
#endif
#endif // OPENTHREAD_FTD || OPENTHREAD_MTD
void SetCommandTimeout(uint32_t aTimeoutMilli);
+13
View File
@@ -151,4 +151,17 @@
#define OPENTHREAD_CONFIG_CLI_TXT_RECORD_MAX_SIZE 512
#endif
/**
* @def OPENTHREAD_CONFIG_CLI_REGISTER_IP6_RECV_CALLBACK
*
* Define as 1 to have CLI register an IPv6 receive callback using `otIp6SetReceiveCallback()`.
*
* This is intended for testing only. Receive callback should be registered for the `otIp6GetBorderRoutingCounters()`
* to count the messages being passed to the callback.
*
*/
#ifndef OPENTHREAD_CONFIG_CLI_REGISTER_IP6_RECV_CALLBACK
#define OPENTHREAD_CONFIG_CLI_REGISTER_IP6_RECV_CALLBACK 0
#endif
#endif // CONFIG_CLI_H_
+199 -4
View File
@@ -46,6 +46,12 @@ import weakref
JOIN_TYPE_ROUTER = 'router'
JOIN_TYPE_END_DEVICE = 'ed'
JOIN_TYPE_SLEEPY_END_DEVICE = 'sed'
JOIN_TYPE_REED = 'reed'
# for use as `radios` parameter in `Node.__init__()`
RADIO_15_4 = "-15.4"
RADIO_TREL = "-trel"
RADIO_15_4_TREL = "-15.4-trel"
# ----------------------------------------------------------------------------------------------------------------------
@@ -106,16 +112,17 @@ class Node(object):
_all_nodes = weakref.WeakSet()
def __init__(self, verbose=_VERBOSE):
def __init__(self, radios='', index=None, verbose=_VERBOSE):
"""Creates a new `Node` instance"""
index = Node._cur_index
Node._cur_index += 1
if index is None:
index = Node._cur_index
Node._cur_index += 1
self._index = index
self._verbose = verbose
cmd = f'{self._OT_CLI_FTD} --time-speed={self._SPEED_UP_FACTOR} '
cmd = f'{self._OT_CLI_FTD}{radios} --time-speed={self._SPEED_UP_FACTOR} '
if Node._SAVE_LOGS:
log_file_name = self._LOG_FNAME + str(index) + '.log'
@@ -261,6 +268,12 @@ class Node(object):
def set_router_selection_jitter(self, jitter):
self._cli_no_output('routerselectionjitter', jitter)
def get_router_eligible(self):
return self._cli_single_output('routereligible')
def set_router_eligible(self, enable):
self._cli_no_output('routereligible', enable)
def interface_up(self):
self._cli_no_output('ifconfig up')
@@ -276,9 +289,18 @@ class Node(object):
def thread_stop(self):
self._cli_no_output('thread stop')
def get_rloc16(self):
return self._cli_single_output('rloc16')
def get_ip_addrs(self):
return self.cli('ipaddr')
def add_ip_addr(self, address):
self._cli_no_output('ipaddr add', address)
def remove_ip_addr(self, address):
self._cli_no_output('ipaddr del', address)
def get_mleid_ip_addr(self):
return self._cli_single_output('ipaddr mleid')
@@ -288,6 +310,132 @@ class Node(object):
def get_rloc_ip_addr(self):
return self._cli_single_output('ipaddr rloc')
def get_mesh_local_prefix(self):
return self._cli_single_output('prefix meshlocal')
def get_ip_maddrs(self):
return self.cli('ipmaddr')
def add_ip_maddr(self, maddr):
return self._cli_no_output('ipmaddr add', maddr)
def get_pollperiod(self):
return self._cli_single_output('pollperiod')
def set_pollperiod(self, period):
self._cli_no_output('pollperiod', period)
def get_partition_id(self):
return self._cli_single_output('partitionid')
def get_parent_info(self):
outputs = self.cli('parent')
result = {}
for line in outputs:
fields = line.split(':')
result[fields[0].strip()] = fields[1].strip()
return result
def get_child_table(self):
return Node.parse_table(self.cli('child table'))
def get_neighbor_table(self):
return Node.parse_table(self.cli('neighbor table'))
def get_router_table(self):
return Node.parse_table(self.cli('router table'))
def get_eidcache(self):
return self.cli('eidcache')
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# netdata
def get_netdata(self):
outputs = self.cli('netdata show')
outputs = [line.strip() for line in outputs]
routes_index = outputs.index('Routes:')
services_index = outputs.index('Services:')
result = {}
result['prefixes'] = outputs[1:routes_index]
result['routes'] = outputs[routes_index + 1:services_index]
result['services'] = outputs[services_index + 1:]
return result
def get_netdata_prefixes(self):
return self.get_netdata()['prefixes']
def get_netdata_routes(self):
return self.get_netdata()['routes']
def get_netdata_services(self):
return self.get_netdata()['services']
def get_netdata_versions(self):
leaderdata = Node.parse_list(self.cli('leaderdata'))
return (int(leaderdata['Data Version']), int(leaderdata['Stable Data Version']))
def add_prefix(self, prefix, flags=None, prf=None):
return self._cli_no_output('prefix add', prefix, flags, prf)
def add_route(self, prefix, flags=None, prf=None):
return self._cli_no_output('route add', prefix, flags, prf)
def remove_prefix(self, prefix):
return self._cli_no_output('prefix remove', prefix)
def register_netdata(self):
self._cli_no_output('netdata register')
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# ping and counters
def ping(self, address, size=0, count=1, verify_success=True):
outputs = self.cli('ping', address, size, count)
m = re.match(r'(\d+) packets transmitted, (\d+) packets received.', outputs[-1].strip())
verify(m is not None)
verify(int(m.group(1)) == count)
if verify_success:
verify(int(m.group(2)) == count)
def get_mle_counter(self):
return self.cli('counters mle')
def get_br_counter_unicast_outbound_packets(self):
outputs = self.cli('counters br')
for line in outputs:
m = re.match(r'Outbound Unicast: Packets (\d+) Bytes (\d+)', line.strip())
if m is not None:
counter = int(m.group(1))
break
else:
verify(False)
return counter
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# UDP
def udp_open(self):
self._cli_no_output('udp open')
def udp_close(self):
self._cli_no_output('udp close')
def udp_bind(self, address, port):
self._cli_no_output('udp bind', address, port)
def udp_send(self, address, port, text):
self._cli_no_output('udp send', address, port, '-t', text)
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# multiradio
def multiradio_get_radios(self):
return self._cli_single_output('multiradio')
def multiradio_get_neighbor_list(self):
return self.cli('multiradio neighbor list')
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# SRP client
@@ -532,6 +680,7 @@ class Node(object):
self.set_channel(channel)
if xpanid is not None:
self.set_ext_panid(xpanid)
self.set_mode('rdn')
self.set_panid(panid)
self.interface_up()
self.thread_start()
@@ -546,6 +695,9 @@ class Node(object):
self.set_mode('rn')
elif type == JOIN_TYPE_SLEEPY_END_DEVICE:
self.set_mode('-')
elif type == JOIN_TYPE_REED:
self.set_mode('rdn')
self.set_router_eligible('disable')
else:
self.set_mode('rdn')
self.set_router_selection_jitter(1)
@@ -565,6 +717,49 @@ class Node(object):
"""Removes a given node (of node `Node) from the allowlist"""
self._cli_no_output('macfilter addr remove', node.get_ext_addr())
# ------------------------------------------------------------------------------------------------------------------
# Parsing helpers
@classmethod
def parse_table(cls, table_lines):
verify(len(table_lines) >= 2)
headers = cls.split_table_row(table_lines[0])
info = []
for row in table_lines[2:]:
if row.strip() == '':
continue
fields = cls.split_table_row(row)
verify(len(fields) == len(headers))
info.append({headers[i]: fields[i] for i in range(len(fields))})
return info
@classmethod
def split_table_row(cls, row):
return [field.strip() for field in row.strip().split('|')[1:-1]]
@classmethod
def parse_list(cls, list_lines):
result = {}
for line in list_lines:
fields = line.split(':', 1)
result[fields[0].strip()] = fields[1].strip()
return result
@classmethod
def parse_multiradio_neighbor_entry(cls, line):
# Example: "ExtAddr:42aa94ad67229f14, RLOC16:0x9400, Radios:[15.4(245), TREL(255)]"
result = {}
for field in line.split(', ', 2):
key_value = field.split(':')
result[key_value[0]] = key_value[1]
radios = {}
for item in result['Radios'][1:-1].split(','):
name, prf = item.strip().split('(')
verify(prf.endswith(')'))
radios[name] = int(prf[:-1])
result['Radios'] = radios
return result
# ------------------------------------------------------------------------------------------------------------------
# class methods
+1 -3
View File
@@ -40,7 +40,7 @@ print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `Nodes` instances
speedup = 4
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
node = cli.Node()
@@ -48,8 +48,6 @@ node = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Test implementation
WAIT_TIME = 5
verify(node.get_state() == 'disabled')
node.form('test')
+15 -3
View File
@@ -40,14 +40,12 @@ print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Nodes` instances
speedup = 4
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
node1 = cli.Node()
node2 = cli.Node()
WAIT_TIME = 5
# -----------------------------------------------------------------------------------------------------------------------
# Test implementation
@@ -72,6 +70,20 @@ node2.join(node1, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
verify(node2.get_state() == 'child')
verify(node2.get_mode() == '-')
node2.interface_down()
# Create a poor link between child and parent using MAC fixed RSSI
# filter and make sure child can still attach.
node1.cli('macfilter rss add * -99')
node2.cli('macfilter rss add * -99')
node2.join(node1, cli.JOIN_TYPE_END_DEVICE)
verify(node2.get_state() == 'child')
verify(node2.get_mode() == 'rn')
verify(len(node1.get_child_table()) == 1)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
# -----------------------------------------------------------------------------------------------------------------------
# Test description: MAC scan operation
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
def verify_scan_result_conatins_nodes(scan_result, nodes):
table = cli.Node.parse_table(scan_result)
verify(len(table) >= len(nodes))
for node in nodes:
ext_addr = node.get_ext_addr()
for entry in table:
if entry['MAC Address'] == ext_addr:
verify(int(entry['PAN'], 16) == int(node.get_panid(), 16))
verify(int(entry['Ch']) == int(node.get_channel()))
break
else:
verify(False)
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Nodes` instances
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
node1 = cli.Node()
node2 = cli.Node()
node3 = cli.Node()
node4 = cli.Node()
node5 = cli.Node()
scanner = cli.Node()
nodes = [node1, node2, node3, node4, node5]
# -----------------------------------------------------------------------------------------------------------------------
# Test implementation
node1.form('net1', panid=0x1111, channel=12)
node2.form('net2', panid=0x2222, channel=13)
node3.form('net3', panid=0x3333, channel=14)
node4.form('net4', panid=0x4444, channel=15)
node5.form('net5', panid=0x5555, channel=16)
# MAC scan
# Scan on all channels, should see all nodes
verify_scan_result_conatins_nodes(scanner.cli('scan'), nodes)
# Scan on channel 12 only, should only see node1
verify_scan_result_conatins_nodes(scanner.cli('scan 12'), [node1])
# Scan on channel 20 only, should see no result
verify_scan_result_conatins_nodes(scanner.cli('scan 20'), [])
# MLE Discover scan
scanner.interface_up()
verify_scan_result_conatins_nodes(scanner.cli('discover'), nodes)
verify_scan_result_conatins_nodes(scanner.cli('scan 12'), [node1])
verify_scan_result_conatins_nodes(scanner.cli('scan 20'), [])
scanner.form('scanner')
verify_scan_result_conatins_nodes(scanner.cli('discover'), nodes)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
# -----------------------------------------------------------------------------------------------------------------------
# Test description:
#
# Traffic between router to end-device (link-local and mesh-local IPv6 addresses).
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Nodes` instances
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
node1 = cli.Node()
node2 = cli.Node()
node3 = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Test implementation
node1.form('net')
node2.join(node1, cli.JOIN_TYPE_END_DEVICE)
node3.join(node1, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
verify(node1.get_state() == 'leader')
verify(node2.get_state() == 'child')
verify(node3.get_state() == 'child')
ll1 = node1.get_linklocal_ip_addr()
ll2 = node2.get_linklocal_ip_addr()
ll3 = node2.get_linklocal_ip_addr()
ml1 = node1.get_mleid_ip_addr()
ml2 = node2.get_mleid_ip_addr()
ml3 = node2.get_mleid_ip_addr()
sizes = [0, 80, 500, 1000]
count = 2
for size in sizes:
node1.ping(ll2, size=size, count=count)
node2.ping(ll1, size=size, count=count)
node1.ping(ml2, size=size, count=count)
node2.ping(ml1, size=size, count=count)
poll_periods = [10, 100, 300]
for period in poll_periods:
node3.set_pollperiod(period)
verify(int(node3.get_pollperiod()) == period)
for size in sizes:
node1.ping(ll3, size=size, count=count)
node3.ping(ll1, size=size, count=count)
node1.ping(ml3, size=size, count=count)
node3.ping(ml1, size=size, count=count)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+168
View File
@@ -0,0 +1,168 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description:
#
# Traffic over multi-hop in a network with chain topology
#
# r1 ----- r2 ---- r3 ----- r4
# /\ /\
# / \ / \
# fed1 sed1 sed4 fed4
#
#
# Traffic flow:
# - From first router to last router
# - From SED child of last router to SED child of first router
# - From FED child of first router to FED child of last router
#
# The test verifies the following:
# - Verifies Address Query process to routers and FEDs.
# - Verifies Mesh Header frame forwarding over multiple routers.
# - Verifies forwarding of large IPv6 messages (1000 bytes) requiring lowpan fragmentation.
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Nodes` instances
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node()
r2 = cli.Node()
r3 = cli.Node()
r4 = cli.Node()
fed1 = cli.Node()
sed1 = cli.Node()
fed4 = cli.Node()
sed4 = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
r1.allowlist_node(r2)
r1.allowlist_node(fed1)
r1.allowlist_node(sed1)
r2.allowlist_node(r1)
r2.allowlist_node(r3)
r3.allowlist_node(r2)
r3.allowlist_node(r4)
r4.allowlist_node(r3)
r4.allowlist_node(sed4)
r4.allowlist_node(fed4)
fed1.allowlist_node(r1)
sed1.allowlist_node(r1)
fed4.allowlist_node(r4)
sed4.allowlist_node(r4)
r1.form("pan")
r2.join(r1)
r3.join(r2)
r4.join(r3)
fed1.join(r1, cli.JOIN_TYPE_REED)
sed1.join(r1, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
fed4.join(r4, cli.JOIN_TYPE_REED)
sed4.join(r4, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
verify(r1.get_state() == 'leader')
verify(r2.get_state() == 'router')
verify(r3.get_state() == 'router')
verify(r4.get_state() == 'router')
verify(fed1.get_state() == 'child')
verify(fed4.get_state() == 'child')
verify(sed1.get_state() == 'child')
verify(sed4.get_state() == 'child')
sed1.set_pollperiod(200)
sed4.set_pollperiod(200)
verify(int(sed1.get_pollperiod()) == 200)
verify(int(sed4.get_pollperiod()) == 200)
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
# Wait till first router has either established a link or
# has a valid "next hop" towards all other routers.
r1_rloc16 = int(r1.get_rloc16(), 16)
def check_r1_router_table():
table = r1.get_router_table()
verify(len(table) == 4)
for entry in table:
verify(int(entry['RLOC16'], 0) == r1_rloc16 or int(entry['Link']) == 1 or int(entry['Next Hop']) != 63)
verify_within(check_r1_router_table, 160)
sizes = [0, 80, 500, 1000]
# Traffic from the first router in the chain to the last one.
r4_mleid = r4.get_mleid_ip_addr()
for size in sizes:
r1.ping(r4_mleid, size=size)
# Send from the SED child of the last router to the SED child of the first
# router.
sed1_mleid = sed1.get_mleid_ip_addr()
for size in sizes:
sed4.ping(sed1_mleid, size=size)
# Send from the FED child of the first router to the FED child of the last
# router.
fed4_mleid = fed4.get_mleid_ip_addr()
for size in sizes:
fed1.ping(fed4_mleid, size=size)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+245
View File
@@ -0,0 +1,245 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description: Adding off-mesh routes (on routers and FEDs) and traffic flow to off-mesh addresses.
#
# Test topology:
#
# r1---- r2 ---- r3 ---- r4
# | |
# | |
# fed1 med3
#
# The off-mesh-routes are added as follows:
# - `r1` adds fd00:1:1::/48 high
# - `r2` adds fd00:1:1:2::64 med
# - `r3` adds fd00:3::/64 med & fed00:4::/32 med
# - 'r4' adds fd00:1:1::/48 low
# - `fed1` adds fd00:4::/32 med
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Nodes` instances
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node()
r2 = cli.Node()
r3 = cli.Node()
r4 = cli.Node()
fed1 = cli.Node()
med3 = cli.Node()
nodes = [r1, r2, r3, r4, fed1, med3]
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
r1.allowlist_node(r2)
r1.allowlist_node(fed1)
r2.allowlist_node(r1)
r2.allowlist_node(r3)
r3.allowlist_node(r2)
r3.allowlist_node(r4)
r3.allowlist_node(med3)
r4.allowlist_node(r3)
fed1.allowlist_node(r1)
med3.allowlist_node(r3)
r1.form("off-mesh")
r2.join(r1)
r3.join(r2)
r4.join(r3)
fed1.join(r1, cli.JOIN_TYPE_REED)
med3.join(r3, cli.JOIN_TYPE_END_DEVICE)
verify(r1.get_state() == 'leader')
verify(r2.get_state() == 'router')
verify(r3.get_state() == 'router')
verify(r4.get_state() == 'router')
verify(fed1.get_state() == 'child')
verify(med3.get_state() == 'child')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
# Wait till first router has either established a link or
# has a valid "next hop" towards all other routers.
r1_rloc16 = int(r1.get_rloc16(), 16)
def check_r1_router_table():
table = r1.get_router_table()
verify(len(table) == 4)
for entry in table:
verify(int(entry['RLOC16'], 0) == r1_rloc16 or int(entry['Link']) == 1 or int(entry['Next Hop']) != 63)
verify_within(check_r1_router_table, 120)
# Add an on-mesh prefix on r1 and different off-mesh routes
# on different nodes and make sure they are seen on all nodes
r1.add_prefix('fd00:abba::/64', 'paros', 'med')
r1.add_route('fd00:1:1::/48', 's', 'high')
r1.register_netdata()
r2.add_route('fd00:1:1:2::/64', 's', 'med')
r2.register_netdata()
r3.add_route('fd00:3::/64', 's', 'med')
r3.add_route('fd00:4::/32', 'med')
r3.register_netdata()
r4.add_route('fd00:1:1::/48', 's', 'med')
fed1.add_route('fd00:4::/32', 'med')
fed1.register_netdata()
def check_netdata_on_all_nodes():
for node in nodes:
netdata = node.get_netdata()
verify(len(netdata['prefixes']) == 1)
verify(len(netdata['routes']) == 6)
verify_within(check_netdata_on_all_nodes, 5)
# Send from `med3` to an address matching `fd00:1:1:2::/64` added
# by`r2` and verify that it is received on `r2` and not `r1`, since
# it is better prefix match with `fd00:1:1:2/64` on r2.
r1_counter = r1.get_br_counter_unicast_outbound_packets()
r2_counter = r2.get_br_counter_unicast_outbound_packets()
med3.ping('fd00:1:1:2::1', verify_success=False)
verify(r1_counter == r1.get_br_counter_unicast_outbound_packets())
verify(r2_counter + 1 == r2.get_br_counter_unicast_outbound_packets())
# Send from`r3` to an address matching `fd00:1:1::/48` which is
# added by both `r1` and `r4` and verify that it is received on
# `r1` since it adds it with higher preference.
r1_counter = r1.get_br_counter_unicast_outbound_packets()
r4_counter = r4.get_br_counter_unicast_outbound_packets()
r3.ping('fd00:1:1::2', count=3, verify_success=False)
verify(r1_counter + 3 == r1.get_br_counter_unicast_outbound_packets())
verify(r4_counter == r4.get_br_counter_unicast_outbound_packets())
# TRy the same address from `r4` itself, it should again end up
# going to `r1` due to it adding it at high preference.
r1_counter = r1.get_br_counter_unicast_outbound_packets()
r4_counter = r4.get_br_counter_unicast_outbound_packets()
r4.ping('fd00:1:1::2', count=2, verify_success=False)
verify(r1_counter + 2 == r1.get_br_counter_unicast_outbound_packets())
verify(r4_counter == r4.get_br_counter_unicast_outbound_packets())
# Send from `fed1` to an address matching `fd00::3::/64` (from `r3`)
# and verify that it is received on `r3`.
r3_counter = r3.get_br_counter_unicast_outbound_packets()
fed1.ping('fd00:3::3', count=2, verify_success=False)
verify(r3_counter + 2 == r3.get_br_counter_unicast_outbound_packets())
# Send from `r1` to an address matching `fd00::4::/32` which is added
# by both `fed1` and `r3` with same preference. Verify that it is
# received on `fed1` since it is closer to `r1` and we would have a
# smaller path cost from `r1` to `fed1`.
fed1_counter = fed1.get_br_counter_unicast_outbound_packets()
r3_counter = r3.get_br_counter_unicast_outbound_packets()
r1.ping('fd00:4::4', count=1, verify_success=False)
verify(fed1_counter + 1 == fed1.get_br_counter_unicast_outbound_packets())
verify(r3_counter == r3.get_br_counter_unicast_outbound_packets())
# Try the same, but now send from `fed1` and make sure it selects
# itself as destination.
fed1_counter = fed1.get_br_counter_unicast_outbound_packets()
r3_counter = r3.get_br_counter_unicast_outbound_packets()
fed1.ping('fd00:4::5', count=2, verify_success=False)
verify(fed1_counter + 2 == fed1.get_br_counter_unicast_outbound_packets())
verify(r3_counter == r3.get_br_counter_unicast_outbound_packets())
# Try the same but now send from `r2`. Now the `r3` would be closer
# and should be selected as destination.
fed1_counter = fed1.get_br_counter_unicast_outbound_packets()
r3_counter = r3.get_br_counter_unicast_outbound_packets()
r2.ping('fd00:4::6', count=3, verify_success=False)
verify(fed1_counter == fed1.get_br_counter_unicast_outbound_packets())
verify(r3_counter + 3 == r3.get_br_counter_unicast_outbound_packets())
# Again try same but send from `med1` and make sure its parent
# `r3` receives.
fed1_counter = fed1.get_br_counter_unicast_outbound_packets()
r3_counter = r3.get_br_counter_unicast_outbound_packets()
med3.ping('fd00:4::7', count=1, verify_success=False)
verify(fed1_counter == fed1.get_br_counter_unicast_outbound_packets())
verify(r3_counter + 1 == r3.get_br_counter_unicast_outbound_packets())
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+247
View File
@@ -0,0 +1,247 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description: Multicast traffic
#
# Network topology
#
# r1 ---- r2 ---- r3 ---- r4
# | |
# | |
# fed sed
#
# Test covers the following multicast traffic:
#
# - r2 =>> link-local all-nodes. Expected response from [r1, r3, fed].
# - r3 =>> mesh-local all-nodes. Expected response from [r1, r2, r4, fed].
# - r3 =>> link-local all-routers. Expected response from [r2, r4].
# - r3 =>> mesh-local all-routers. Expected response from all routers.
# - r1 =>> link-local all-thread. Expected response from [r1, r2].
# - fed =>> mesh-local all-thread. Expected response from all nodes.
# - r1 =>> mesh-local all-thread (one hop). Expected response from [r2].
# - r1 =>> mesh-local all-thread (two hops). Expected response from [r2, r3, fed].
# - r1 =>> mesh-local all-thread (three hops). Expected response from [r2, r3, r4, fed].
# - r1 =>> mesh-local all-thread (four hops). Expected response from [r2, r3, r4, fed, sed].
# - r1 =>> specific address (on r2 and sed). Expected to receive on [r2, sed].
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Nodes` instances
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node()
r2 = cli.Node()
r3 = cli.Node()
r4 = cli.Node()
fed = cli.Node()
sed = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
r1.allowlist_node(r2)
r2.allowlist_node(r1)
r2.allowlist_node(fed)
r2.allowlist_node(r3)
fed.allowlist_node(r2)
r3.allowlist_node(r2)
r3.allowlist_node(r4)
r4.allowlist_node(r3)
r4.allowlist_node(sed)
sed.allowlist_node(r4)
r1.form("multicast")
r2.join(r1)
r3.join(r2)
r4.join(r3)
fed.join(r1, cli.JOIN_TYPE_REED)
sed.join(r3, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
sed.set_pollperiod(600)
verify(r1.get_state() == 'leader')
verify(r2.get_state() == 'router')
verify(r3.get_state() == 'router')
verify(r4.get_state() == 'router')
verify(fed.get_state() == 'child')
verify(sed.get_state() == 'child')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
# Wait till first router has either established a link or
# has a valid "next hop" towards all other routers.
r1_rloc16 = int(r1.get_rloc16(), 16)
def check_r1_router_table():
table = r1.get_router_table()
verify(len(table) == 4)
for entry in table:
verify(int(entry['RLOC16'], 0) == r1_rloc16 or int(entry['Link']) == 1 or int(entry['Next Hop']) != 63)
verify_within(check_r1_router_table, 160)
# r2 =>> link-local all-nodes. Expected response from [r1, r3, fed].
outputs = r2.cli('ping ff02::1')
verify(len(outputs) == 4)
for node in [r1, r3, fed]:
ll_addr = node.get_linklocal_ip_addr()
verify(any(ll_addr in line for line in outputs))
# r3 =>> mesh-local all-nodes. Expected response from [r1, r2, r4, fed].
outputs = r3.cli('ping ff03::1')
verify(len(outputs) == 5)
for node in [r1, r2, r4, fed]:
ml_addr = node.get_mleid_ip_addr()
verify(any(ml_addr in line for line in outputs))
# r3 =>> link-local all-routers. Expected response from [r2, r4].
outputs = r3.cli('ping ff02::2')
verify(len(outputs) == 3)
for node in [r2, r4]:
ll_addr = node.get_linklocal_ip_addr()
verify(any(ll_addr in line for line in outputs))
# r3 =>> mesh-local all-routers. Expected response from all routers.
outputs = r3.cli('ping ff03::2')
verify(len(outputs) == 5)
for node in [r1, r2, r4, fed]:
ml_addr = node.get_mleid_ip_addr()
verify(any(ml_addr in line for line in outputs))
# r1 =>> link-local all-thread. Expected response from [r2].
ml_prefix = r1.get_mesh_local_prefix().strip().split('/')[0]
ll_all_thread_nodes_addr = 'ff32:40:' + ml_prefix + '1'
outputs = r1.cli('ping', ll_all_thread_nodes_addr)
verify(len(outputs) == 2)
for node in [r2]:
ll_addr = node.get_linklocal_ip_addr()
verify(any(ll_addr in line for line in outputs))
# fed =>> mesh-local all-thread. Expected response from all nodes.
ml_all_thread_nodes_addr = 'ff33:40:' + ml_prefix + '1'
outputs = fed.cli('ping', ml_all_thread_nodes_addr)
verify(len(outputs) == 6)
for node in [r1, r2, r3, r4, sed]:
ml_addr = node.get_mleid_ip_addr()
verify(any(ml_addr in line for line in outputs))
# Repeat the same with larger ping msg requiring fragmentation
outputs = fed.cli('ping', ml_all_thread_nodes_addr, 200)
verify(len(outputs) == 6)
for node in [r1, r2, r3, r4, sed]:
ml_addr = node.get_mleid_ip_addr()
verify(any(ml_addr in line for line in outputs))
# r1 =>> mesh-local all-thread (one hop). Expected response from [r2].
hop_limit = 1
outputs = r1.cli('ping', ml_all_thread_nodes_addr, 10, 1, 0, hop_limit)
verify(len(outputs) == 2)
for node in [r2]:
ml_addr = node.get_mleid_ip_addr()
verify(any(ml_addr in line for line in outputs))
# r1 =>> mesh-local all-thread (two hops). Expected response from [r2, r3, fed].
hop_limit = 2
outputs = r1.cli('ping', ml_all_thread_nodes_addr, 10, 1, 0, hop_limit)
verify(len(outputs) == 4)
for node in [r2, r3, fed]:
ml_addr = node.get_mleid_ip_addr()
verify(any(ml_addr in line for line in outputs))
# r1 =>> mesh-local all-thread (three hops). Expected response from [r2, r3, r4, fed].
hop_limit = 3
outputs = r1.cli('ping', ml_all_thread_nodes_addr, 10, 1, 0, hop_limit)
verify(len(outputs) == 5)
for node in [r2, r3, r4, fed]:
ml_addr = node.get_mleid_ip_addr()
verify(any(ml_addr in line for line in outputs))
# r1 =>> mesh-local all-thread (four hops). Expected response from [r2, r3, r4, fed, sed].
hop_limit = 4
outputs = r1.cli('ping', ml_all_thread_nodes_addr, 10, 1, 0, hop_limit)
verify(len(outputs) == 6)
for node in [r2, r3, r4, fed, sed]:
ml_addr = node.get_mleid_ip_addr()
verify(any(ml_addr in line for line in outputs))
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Subscribe to a specific multicast address on r2 and sed
mcast_addr = 'ff03:0:0:0:0:0:0:114'
r2.add_ip_maddr(mcast_addr)
sed.add_ip_maddr(mcast_addr)
time.sleep(1)
maddrs = r2.get_ip_maddrs()
verify(any(mcast_addr in maddr for maddr in maddrs))
maddrs = sed.get_ip_maddrs()
verify(any(mcast_addr in maddr for maddr in maddrs))
outputs = r1.cli('ping', mcast_addr)
verify(len(outputs) == 3)
for node in [r2, sed]:
ml_addr = node.get_mleid_ip_addr()
verify(any(ml_addr in line for line in outputs))
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+251
View File
@@ -0,0 +1,251 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description:
#
# Verify router table entries.
#
# r1 ------ r2 ---- r6
# \ /
# \ /
# \ /
# r3 ------ r4 ----- r5
#
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Nodes` instances
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node()
r2 = cli.Node()
r3 = cli.Node()
r4 = cli.Node()
r5 = cli.Node()
r6 = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
r1.allowlist_node(r2)
r1.allowlist_node(r3)
r2.allowlist_node(r1)
r2.allowlist_node(r3)
r2.allowlist_node(r6)
r3.allowlist_node(r1)
r3.allowlist_node(r2)
r3.allowlist_node(r4)
r4.allowlist_node(r3)
r4.allowlist_node(r5)
r5.allowlist_node(r4)
r6.allowlist_node(r2)
r1.form("topo")
for node in [r2, r3, r4, r5, r6]:
node.join(r1)
verify(r1.get_state() == 'leader')
for node in [r2, r3, r4, r5, r6]:
verify(node.get_state() == 'router')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
r1_rloc16 = int(r1.get_rloc16(), 16)
r2_rloc16 = int(r2.get_rloc16(), 16)
r3_rloc16 = int(r3.get_rloc16(), 16)
r4_rloc16 = int(r4.get_rloc16(), 16)
r5_rloc16 = int(r5.get_rloc16(), 16)
r6_rloc16 = int(r6.get_rloc16(), 16)
r1_rid = r1_rloc16 / 1024
r2_rid = r2_rloc16 / 1024
r3_rid = r3_rloc16 / 1024
r4_rid = r4_rloc16 / 1024
r5_rid = r5_rloc16 / 1024
r6_rid = r6_rloc16 / 1024
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def check_r1_router_table():
table = r1.get_router_table()
verify(len(table) == 6)
for entry in table:
rloc16 = int(entry['RLOC16'], 0)
link = int(entry['Link'])
nexthop = int(entry['Next Hop'])
cost = int(entry['Path Cost'])
if rloc16 == r1_rloc16:
verify(link == 0)
elif rloc16 == r2_rloc16:
verify(link == 1)
verify(nexthop == r3_rid)
elif rloc16 == r3_rloc16:
verify(link == 1)
verify(nexthop == r2_rid)
elif rloc16 == r4_rloc16:
verify(link == 0)
verify(nexthop == r3_rid)
verify(cost == 1)
elif rloc16 == r5_rloc16:
verify(link == 0)
verify(nexthop == r3_rid)
verify(cost == 2)
elif rloc16 == r6_rloc16:
verify(link == 0)
verify(nexthop == r2_rid)
verify(cost == 1)
verify_within(check_r1_router_table, 160)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def check_r2_router_table():
table = r2.get_router_table()
verify(len(table) == 6)
for entry in table:
rloc16 = int(entry['RLOC16'], 0)
link = int(entry['Link'])
nexthop = int(entry['Next Hop'])
cost = int(entry['Path Cost'])
if rloc16 == r1_rloc16:
verify(link == 1)
verify(nexthop == r3_rid)
elif rloc16 == r2_rloc16:
verify(link == 0)
elif rloc16 == r3_rloc16:
verify(link == 1)
verify(nexthop == r1_rid)
elif rloc16 == r4_rloc16:
verify(link == 0)
verify(nexthop == r3_rid)
verify(cost == 1)
elif rloc16 == r5_rloc16:
verify(link == 0)
verify(nexthop == r3_rid)
verify(cost == 2)
elif rloc16 == r6_rloc16:
verify(link == 1)
verify_within(check_r2_router_table, 160)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def check_r4_router_table():
table = r4.get_router_table()
verify(len(table) == 6)
for entry in table:
rloc16 = int(entry['RLOC16'], 0)
link = int(entry['Link'])
nexthop = int(entry['Next Hop'])
cost = int(entry['Path Cost'])
if rloc16 == r1_rloc16:
verify(link == 0)
verify(nexthop == r3_rid)
verify(cost == 1)
elif rloc16 == r2_rloc16:
verify(link == 0)
verify(nexthop == r3_rid)
verify(cost == 1)
elif rloc16 == r3_rloc16:
verify(link == 1)
elif rloc16 == r4_rloc16:
verify(link == 0)
elif rloc16 == r5_rloc16:
verify(link == 1)
elif rloc16 == r6_rloc16:
verify(link == 0)
verify(nexthop == r3_rid)
verify(cost == 2)
verify_within(check_r4_router_table, 160)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def check_r5_router_table():
table = r5.get_router_table()
verify(len(table) == 6)
for entry in table:
rloc16 = int(entry['RLOC16'], 0)
link = int(entry['Link'])
nexthop = int(entry['Next Hop'])
cost = int(entry['Path Cost'])
if rloc16 == r1_rloc16:
verify(link == 0)
verify(nexthop == r4_rid)
verify(cost == 2)
elif rloc16 == r2_rloc16:
verify(link == 0)
verify(nexthop == r4_rid)
verify(cost == 2)
elif rloc16 == r3_rloc16:
verify(link == 0)
verify(nexthop == r4_rid)
verify(cost == 1)
elif rloc16 == r4_rloc16:
verify(link == 1)
elif rloc16 == r5_rloc16:
verify(link == 0)
elif rloc16 == r6_rloc16:
verify(link == 0)
verify(nexthop == r4_rid)
verify(cost == 3)
verify_within(check_r5_router_table, 160)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+154
View File
@@ -0,0 +1,154 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description: Partition formation and merge
#
# Network Topology:
#
# r1 ---- / ---- r2
# | \ |
# | / |
# c1 \ c2
#
#
# Test covers the following situations:
#
# - r2 forming its own partition when it can no longer hear r1
# - Partitions merging into one once r1 and r2 can talk again
# - Adding on-mesh prefixes on each partition and ensuring after
# merge the info in combined.
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Nodes` instances
speedup = 25
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node()
r2 = cli.Node()
c1 = cli.Node()
c2 = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
r1.allowlist_node(r2)
r1.allowlist_node(c1)
r2.allowlist_node(r1)
r2.allowlist_node(c2)
r1.form("partmrge")
r2.join(r1)
c1.join(r1, cli.JOIN_TYPE_END_DEVICE)
c2.join(r2, cli.JOIN_TYPE_END_DEVICE)
verify(r1.get_state() == 'leader')
verify(r2.get_state() == 'router')
verify(c1.get_state() == 'child')
verify(c2.get_state() == 'child')
nodes = [r1, r2, c1, c2]
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
# Force the two routers to form their own partition
# by removing them from each other's allowlist table
r1.un_allowlist_node(r2)
r2.un_allowlist_node(r1)
# Add a prefix before r2 realizes it can no longer talk
# to leader (r1).
r2.add_prefix('fd00:abba::/64', 'paros', 'med')
r2.register_netdata()
# Check that r2 forms its own partition
def check_r2_become_leader():
verify(r2.get_state() == 'leader')
verify_within(check_r2_become_leader, 20)
# While we have two partition, add a prefix on r1
r1.add_prefix('fd00:1234::/64', 'paros', 'med')
r1.register_netdata()
# Update allowlist and wait for partitions to merge.
r1.allowlist_node(r2)
r2.allowlist_node(r1)
def check_partition_id_match():
verify(r1.get_partition_id() == r2.get_partition_id())
verify_within(check_partition_id_match, 20)
# Check that partitions merged successfully
def check_r1_r2_roles():
verify(r1.get_state() in ['leader', 'router'])
verify(r2.get_state() in ['leader', 'router'])
verify_within(check_r1_r2_roles, 10)
# Verify all nodes see both prefixes
def check_netdata_on_all_nodes():
for node in nodes:
netdata = node.get_netdata()
verify(len(netdata['prefixes']) == 2)
verify_within(check_netdata_on_all_nodes, 10)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+141
View File
@@ -0,0 +1,141 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description: Network Data (on-mesh prefix) timeout and entry removal
#
# Network topology
#
# r1 ----- r2
# |
# |
# c2 (sleepy)
#
#
# Test covers the following steps:
#
# - Every node adds a unique on-mesh prefix.
# - Every node also adds a common on-mesh prefix (with different flags).
# - Verify that all the unique and common prefixes are present on all nodes are associated with correct RLOC16.
# - Remove `r2` from network (which removes `c2` as well) from Thread partition created by `r1`.
# - Verify that all on-mesh prefixes added by `r2` or `c2` (unique and common) are removed on `r1`.
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Nodes` instances
speedup = 25
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node()
r2 = cli.Node()
c2 = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
r1.allowlist_node(r2)
r2.allowlist_node(r1)
r2.allowlist_node(c2)
c2.allowlist_node(r2)
r1.form("netdatatmout")
r2.join(r1)
c2.join(r1, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
c2.set_pollperiod(500)
verify(r1.get_state() == 'leader')
verify(r2.get_state() == 'router')
verify(c2.get_state() == 'child')
nodes = [r1, r2, c2]
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
common_prefix = 'fd00:cafe::'
prefix1 = 'fd00:1::'
prefix2 = 'fd00:2::'
prefix3 = 'fd00:3::'
# Each node adds its own prefix.
r1.add_prefix('fd00:1::/64', 'paros', 'med')
r2.add_prefix('fd00:2::/64', 'paros', 'med')
c2.add_prefix('fd00:3::/64', 'paros', 'med')
# a common prefix is added by all three nodes (with different preference)
r1.add_prefix('fd00:abba::/64', 'paros', 'high')
r2.add_prefix('fd00:abba::/64', 'paros', 'med')
c2.add_prefix('fd00:abba::/64', 'paros', 'low')
r1.register_netdata()
r2.register_netdata()
c2.register_netdata()
def check_netdata_on_all_nodes():
for node in nodes:
netdata = node.get_netdata()
prefixes = netdata['prefixes']
verify(len(prefixes) == 6)
verify_within(check_netdata_on_all_nodes, 10)
# Remove `r2`. This should trigger all the prefixes added by it or its
# child to timeout and be removed.
r2.thread_stop()
r2.interface_down()
def check_netdata_on_r1():
netdata = r1.get_netdata()
prefixes = netdata['prefixes']
verify(len(prefixes) == 2)
verify_within(check_netdata_on_r1, 120)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description:
# This test covers reset of parent, router, leader and restoring children after reset
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Nodes` instances
speedup = 25
cli.Node.set_time_speedup_factor(speedup)
leader = cli.Node()
router = cli.Node()
child1 = cli.Node()
child2 = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
leader.form('reset')
child1.join(leader, cli.JOIN_TYPE_END_DEVICE)
child2.join(leader, cli.JOIN_TYPE_END_DEVICE)
verify(leader.get_state() == 'leader')
verify(child1.get_state() == 'child')
verify(child2.get_state() == 'child')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Reset the parent and verify that both children are restored on
# parent through "Child Update" exchange process and none of them got
# detached and needed to attach back.
del leader
leader = cli.Node(index=1)
leader.interface_up()
leader.thread_start()
def check_leader_state():
verify(leader.get_state() == 'leader')
verify_within(check_leader_state, 10)
# Check that `child1` and `child2` did not detach
verify(child1.get_state() == 'child')
verify(child2.get_state() == 'child')
verify(int(cli.Node.parse_list(child1.get_mle_counter())['Role Detached']) == 1)
verify(int(cli.Node.parse_list(child2.get_mle_counter())['Role Detached']) == 1)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Reset `router` and make sure it recovers as router with same router ID.
router.join(leader)
verify(router.get_state() == 'router')
router_rloc16 = int(router.get_rloc16(), 16)
time.sleep(0.75)
del router
router = cli.Node(index=2)
router.interface_up()
router.thread_start()
def check_router_state():
verify(router.get_state() == 'router')
verify_within(check_router_state, 10)
verify(router_rloc16 == int(router.get_rloc16(), 16))
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Reset `leader` and make sure `router` is its neighbor again
del leader
leader = cli.Node(index=1)
leader.interface_up()
leader.thread_start()
def check_leader_state():
verify(leader.get_state() == 'leader')
verify_within(check_leader_state, 10)
def check_leader_neighbor_table():
verify(len(leader.get_neighbor_table()) == 3)
verify_within(check_leader_neighbor_table, 10)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+232
View File
@@ -0,0 +1,232 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description: Address Cache Table
#
# This test verifies the behavior of `AddressResolver` module and entries in
# address cache table. It also tests the behavior of nodes when there are
# topology changes in the network (e.g., a child switches parent). In
# particular, the test covers the address cache update through snooping, i.e.,
# the logic which inspects forwarded frames to update address cache table if
# source RLOC16 on a received frame differs from an existing entry in the
# address cache table.
#
# Network topology:
#
# r1 ---- r2 ---- r3
# | | |
# | | |
# c1 c2(s) c3
#
# c1 and c3 are FED children, c2 is an SED which is first attached to r2 and
# then forced to switch to r3.
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Nodes` instances
speedup = 25
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node()
r2 = cli.Node()
r3 = cli.Node()
c1 = cli.Node()
c2 = cli.Node()
c3 = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
r1.allowlist_node(r2)
r1.allowlist_node(c1)
r2.allowlist_node(r1)
r2.allowlist_node(r3)
r2.allowlist_node(c2)
r3.allowlist_node(r2)
r3.allowlist_node(c3)
c1.allowlist_node(r1)
c2.allowlist_node(r2)
c3.allowlist_node(r3)
r1.form('addrrslvr')
r2.join(r1)
r3.join(r1)
c1.join(r1, cli.JOIN_TYPE_REED)
c2.join(r1, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
c3.join(r1, cli.JOIN_TYPE_REED)
c2.set_pollperiod(400)
verify(r1.get_state() == 'leader')
verify(r2.get_state() == 'router')
verify(r3.get_state() == 'router')
verify(c1.get_state() == 'child')
verify(c2.get_state() == 'child')
verify(c3.get_state() == 'child')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
# Wait till first router has either established a link or
# has a valid "next hop" towards all other routers.
r1_rloc16 = int(r1.get_rloc16(), 16)
def check_r1_router_table():
table = r1.get_router_table()
verify(len(table) == 3)
for entry in table:
verify(int(entry['RLOC16'], 0) == r1_rloc16 or int(entry['Link']) == 1 or int(entry['Next Hop']) != 63)
verify_within(check_r1_router_table, 120)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
r1_address = r1.get_mleid_ip_addr()
c1_address = c1.get_mleid_ip_addr()
c2_address = c2.get_mleid_ip_addr()
c3_address = c3.get_mleid_ip_addr()
r1_rloc16 = int(r1.get_rloc16(), 16)
r2_rloc16 = int(r2.get_rloc16(), 16)
r3_rloc16 = int(r3.get_rloc16(), 16)
c1_rloc16 = int(c1.get_rloc16(), 16)
c3_rloc16 = int(c3.get_rloc16(), 16)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# From r1 ping c2 and c3
r1.ping(c2_address)
r1.ping(c3_address)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Verify that address cache table contains both c2 and c3 addresses c2
# address should match its parent r2 (since c2 is sleepy), and c1
# address should match to itself (since c3 is fed).
cache_table = r1.get_eidcache()
verify(len(cache_table) >= 2)
for entry in cache_table:
fields = entry.strip().split(' ')
verify(fields[2] == 'cache')
if fields[0] == c2_address:
verify(int(fields[1], 16) == r2_rloc16)
elif fields[0] == c3_address:
verify(int(fields[1], 16) == c3_rloc16)
else:
verify(False)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Force c2 to switch its parent from r2 to r3
#
# New network topology
#
# r1 ---- r2 ---- r3
# | /\
# | / \
# c1 c2(s) c3
r2.un_allowlist_node(c2)
r3.allowlist_node(c2)
c2.allowlist_node(r3)
c2.thread_stop()
c2.thread_start()
def check_c2_attaches_to_r3():
verify(c2.get_state() == 'child')
verify(int(c2.get_parent_info()['Rloc'], 16) == r3_rloc16)
verify_within(check_c2_attaches_to_r3, 10)
def check_r2_child_table_is_empty():
verify(len(r2.get_child_table()) == 0)
verify_within(check_r2_child_table_is_empty, 10)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Note that r1 still has r2 as the destination for c2's address in its
# address cache table. But since r2 is aware that c2 is no longer its
# child, when it receives the IPv6 message with c2's address, r2
# itself would do an address query for the address and forward the
# IPv6 message.
cache_table = r1.get_eidcache()
for entry in cache_table:
fields = entry.strip().split(' ')
if fields[0] == c2_address:
verify(int(fields[1], 16) == r2_rloc16)
break
else:
verify(False)
r1.ping(c2_address)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Ping c1 from c2. This will go through c1's parent r1. r1 upon
# receiving and forwarding the message should update its address
# cache table for c2 (address cache update through snooping).
c2.ping(c1_address)
cache_table = r1.get_eidcache()
for entry in cache_table:
fields = entry.strip().split(' ')
if fields[0] == c2_address:
verify(int(fields[1], 16) == r3_rloc16)
verify(fields[2] == 'snoop')
break
else:
verify(False)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+415
View File
@@ -0,0 +1,415 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description: Address Cache Table
#
# This test verifies the behavior of `AddressResolver` and how the cache
# table is managed. In particular it verifies behavior query timeout and
# query retry and snoop optimization.
#
# Build network topology
#
# r3 ---- r1 ---- r2
# | |
# | |
# c3 c2
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node()
r2 = cli.Node()
r3 = cli.Node()
c2 = cli.Node()
c3 = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
r1.allowlist_node(r2)
r1.allowlist_node(r3)
r2.allowlist_node(r1)
r2.allowlist_node(c2)
r3.allowlist_node(r1)
r3.allowlist_node(c3)
c2.allowlist_node(r2)
c3.allowlist_node(r3)
r1.form('addrrslvr')
prefix = 'fd00:abba::'
r1.add_prefix(prefix + '/64', 'pos', 'med')
r1.register_netdata()
r2.join(r1)
r3.join(r1)
c2.join(r1, cli.JOIN_TYPE_END_DEVICE)
c3.join(r1, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
c3.set_pollperiod(400)
verify(r1.get_state() == 'leader')
verify(r2.get_state() == 'router')
verify(r3.get_state() == 'router')
verify(c2.get_state() == 'child')
verify(c3.get_state() == 'child')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
# Wait till first router has either established a link or
# has a valid "next hop" towards all other routers.
r1_rloc16 = int(r1.get_rloc16(), 16)
def check_r1_router_table():
table = r1.get_router_table()
verify(len(table) == 3)
for entry in table:
verify(int(entry['RLOC16'], 0) == r1_rloc16 or int(entry['Link']) == 1 or int(entry['Next Hop']) != 63)
verify_within(check_r1_router_table, 120)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
r1_rloc = int(r1.get_rloc16(), 16)
r2_rloc = int(r2.get_rloc16(), 16)
r3_rloc = int(r3.get_rloc16(), 16)
c2_rloc = int(c2.get_rloc16(), 16)
c3_rloc = int(c3.get_rloc16(), 16)
# AddressResolver constants:
max_cache_entries = 16
max_snooped_non_evictable = 2
# Add IPv6 addresses matching the on-mesh prefix on all nodes
r1.add_ip_addr(prefix + '1')
num_addresses = 4 # Number of addresses to add on r2, r3, c2, and c3
for num in range(num_addresses):
r2.add_ip_addr(prefix + "2:" + str(num))
r3.add_ip_addr(prefix + "3:" + str(num))
c2.add_ip_addr(prefix + "c2:" + str(num))
c3.add_ip_addr(prefix + "c3:" + str(num))
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# From r1 send msg to a group of addresses that are not provided by
# any nodes in network.
num_queries = 5
stagger_interval = 1.2
port = 1234
initial_retry_delay = 8
r1.udp_open()
for num in range(num_queries):
r1.udp_send(prefix + '800:' + str(num), port, 'hi_nobody')
# Wait before next tx to stagger the address queries
# request ensuring different timeouts
time.sleep(stagger_interval / (num_queries * speedup))
# Verify that we do see entries in cache table for all the addresses
# and all are in "query" state
cache_table = r1.get_eidcache()
verify(len(cache_table) == num_queries)
for entry in cache_table:
fields = entry.strip().split(' ')
verify(fields[2] == 'query')
verify(fields[3] == 'canEvict=0')
verify(fields[4].startswith('timeout='))
verify(int(fields[4].split('=')[1]) > 0)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Check the retry-query behavior
#
# Wait till all the address queries time out and verify they
# enter "retry-query" state.
def check_cache_entry_switch_to_retry_state():
cache_table = r1.get_eidcache()
for entry in cache_table:
fields = entry.strip().split(' ')
verify(fields[2] == 'retry')
verify(fields[3] == 'canEvict=1')
verify(fields[4].startswith('timeout='))
verify(int(fields[4].split('=')[1]) >= 0)
verify(fields[5].startswith('retryDelay='))
verify(int(fields[5].split('=')[1]) == initial_retry_delay)
verify_within(check_cache_entry_switch_to_retry_state, 20)
# Try sending again to same addresses which are all in "retry" state.
for num in range(num_queries):
r1.udp_send(prefix + '800:' + str(num), port, 'hi_nobody')
# Make sure the entries stayed in retry-query state as before.
verify_within(check_cache_entry_switch_to_retry_state, 20)
# Now wait for all entries to reach zero timeout.
def check_cache_entry_in_retry_state_to_get_to_zero_timeout():
cache_table = r1.get_eidcache()
for entry in cache_table:
fields = entry.strip().split(' ')
verify(fields[2] == 'retry')
verify(fields[3] == 'canEvict=1')
verify(fields[4].startswith('timeout='))
verify(int(fields[4].split('=')[1]) == 0)
verify_within(check_cache_entry_in_retry_state_to_get_to_zero_timeout, 20)
# Now send again to the same addresses.
for num in range(num_queries):
r1.udp_send(prefix + '800:' + str(num), port, 'hi_nobody')
# We expect now after the delay to see retries for same addresses.
def check_cache_entry_switch_to_query_state():
cache_table = r1.get_eidcache()
for entry in cache_table:
fields = entry.strip().split(' ')
verify(fields[2] == 'query')
verify(fields[3] == 'canEvict=1')
verify_within(check_cache_entry_switch_to_query_state, 20)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Verify snoop optimization behavior.
# Send to r1 from all addresses on r2.
r2.udp_open()
for num in range(num_addresses):
r2.udp_bind(prefix + '2:' + str(num), port)
r2.udp_send(prefix + '1', port, 'hi_r1_from_r2_snoop_me')
# Verify that we see all addresses from r2 as snooped in cache table.
# At most two of them should be marked as non-evictable.
def check_cache_entry_contains_snooped_entries():
cache_table = r1.get_eidcache()
verify(len(cache_table) >= num_addresses)
snooped_count = 0
snooped_non_evictable = 0
for entry in cache_table:
fields = entry.strip().split(' ')
if fields[2] == 'snoop':
verify(fields[0].startswith('fd00:abba:0:0:0:0:2:'))
verify(int(fields[1], 16) == r2_rloc)
snooped_count = snooped_count + 1
if fields[3] == 'canEvict=0':
snooped_non_evictable = snooped_non_evictable + 1
verify(snooped_count == num_addresses)
verify(snooped_non_evictable == max_snooped_non_evictable)
verify_within(check_cache_entry_contains_snooped_entries, 20)
# Now we use the snooped entries by sending from r1 to r2 using
# all its addresses.
for num in range(num_addresses):
r1.udp_send(prefix + '2:' + str(num), port, 'hi_back_r2_from_r1')
time.sleep(0.1)
# We expect to see the entries to be in "cached" state now.
cache_table = r1.get_eidcache()
verify(len(cache_table) >= num_addresses)
match_count = 0
for entry in cache_table:
fields = entry.strip().split(' ')
if fields[0].startswith('fd00:abba:0:0:0:0:2:'):
verify(fields[2] == 'cache')
verify(fields[3] == 'canEvict=1')
match_count = match_count + 1
verify(match_count == num_addresses)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Check query requests and last transaction time
# Send from r1 to all addresses on r3. Check entries
# for r3 are at the top of cache table list.
for num in range(num_addresses):
r1.udp_send(prefix + '3:' + str(num), port, 'hi_r3_from_r1')
def check_cache_entry_contains_r3_entries():
cache_table = r1.get_eidcache()
for num in range(num_addresses):
entry = cache_table[num]
fields = entry.strip().split(' ')
verify(fields[0].startswith('fd00:abba:0:0:0:0:3:'))
verify(int(fields[1], 16) == r3_rloc)
verify(fields[2] == 'cache')
verify(fields[3] == 'canEvict=1')
verify(fields[4] == 'transTime=0')
verify_within(check_cache_entry_contains_r3_entries, 20)
# Send from r1 to all addresses of c3 (sleepy child of r3)
for num in range(num_addresses):
r1.udp_send(prefix + 'c3:' + str(num), port, 'hi_c3_from_r1')
def check_cache_entry_contains_c3_entries():
cache_table = r1.get_eidcache()
for num in range(num_addresses):
entry = cache_table[num]
fields = entry.strip().split(' ')
verify(fields[0].startswith('fd00:abba:0:0:0:0:c3:'))
verify(int(fields[1], 16) == r3_rloc)
verify(fields[2] == 'cache')
verify(fields[3] == 'canEvict=1')
verify(fields[4] == 'transTime=0')
verify_within(check_cache_entry_contains_c3_entries, 20)
# Send again to r2. This should cause the related cache entries to
# be moved to top of the list.
for num in range(num_addresses):
r1.udp_send(prefix + '2:' + str(num), port, 'hi_again_r2_from_r1')
def check_cache_entry_contains_r2_entries():
cache_table = r1.get_eidcache()
for num in range(num_addresses):
entry = cache_table[num]
fields = entry.strip().split(' ')
verify(fields[0].startswith('fd00:abba:0:0:0:0:2:'))
verify(int(fields[1], 16) == r2_rloc)
verify(fields[2] == 'cache')
verify(fields[3] == 'canEvict=1')
verify_within(check_cache_entry_contains_r2_entries, 20)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Check behavior when address cache table is full.
cache_table = r1.get_eidcache()
verify(len(cache_table) == max_cache_entries)
# From r1 send to non-existing addresses.
for num in range(num_queries):
r1.udp_send(prefix + '900:' + str(num), port, 'hi_nobody!')
cache_table = r1.get_eidcache()
verify(len(cache_table) == max_cache_entries)
# Send from c2 to r1 and verify that snoop optimization uses at most
# `max_snooped_non_evictable` entries
c2.udp_open()
for num in range(num_addresses):
c2.udp_bind(prefix + 'c2:' + str(num), port)
c2.udp_send(prefix + '1', port, 'hi_r1_from_c2_snoop_me')
def check_cache_entry_contains_max_allowed_snopped():
cache_table = r1.get_eidcache()
snooped_non_evictable = 0
for entry in cache_table:
fields = entry.strip().split(' ')
if fields[2] == 'snoop':
verify(fields[0].startswith('fd00:abba:0:0:0:0:c2:'))
verify(fields[3] == 'canEvict=0')
snooped_non_evictable = snooped_non_evictable + 1
verify(snooped_non_evictable == max_snooped_non_evictable)
verify_within(check_cache_entry_contains_max_allowed_snopped, 20)
# Now send from r1 to c2, the snooped entries would be used
# some other addresses will go through full address query.
for num in range(num_addresses):
r1.udp_send(prefix + 'c2:' + str(num), port, 'hi_c2_from_r1')
def check_cache_entry_contains_c2_entries():
cache_table = r1.get_eidcache()
for num in range(num_addresses):
entry = cache_table[num]
fields = entry.strip().split(' ')
verify(fields[0].startswith('fd00:abba:0:0:0:0:c2:'))
verify(int(fields[1], 16) == r2_rloc)
verify(fields[2] == 'cache')
verify(fields[3] == 'canEvict=1')
verify_within(check_cache_entry_contains_c2_entries, 20)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+232
View File
@@ -0,0 +1,232 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description: Address Cache Table
#
# This test verifies that address cache entries associated with SED child
# addresses are removed from new parent node ensuring we would not have a
# routing loop.
#
#
# r1 ---- r2 ---- r3
# |
# |
# c
#
# c is initially attached to r3 but it switches parent during test to r2 and then r1.
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
speedup = 40
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node()
r2 = cli.Node()
r3 = cli.Node()
c = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
r1.allowlist_node(r2)
r2.allowlist_node(r1)
r2.allowlist_node(r3)
r3.allowlist_node(r2)
r3.allowlist_node(c)
c.allowlist_node(r3)
r1.form('sed-cache')
prefix = 'fd00:abba::'
r1.add_prefix(prefix + '/64', 'paos', 'med')
r1.register_netdata()
r2.join(r1)
r3.join(r1)
c.join(r1, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
c.set_pollperiod(400)
verify(r1.get_state() == 'leader')
verify(r2.get_state() == 'router')
verify(r3.get_state() == 'router')
verify(c.get_state() == 'child')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
# Wait till first router has either established a link or
# has a valid "next hop" towards all other routers.
r1_rloc16 = int(r1.get_rloc16(), 16)
def check_r1_router_table():
table = r1.get_router_table()
verify(len(table) == 3)
for entry in table:
verify(int(entry['RLOC16'], 0) == r1_rloc16 or int(entry['Link']) == 1 or int(entry['Next Hop']) != 63)
verify_within(check_r1_router_table, 120)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
r1_rloc = int(r1.get_rloc16(), 16)
r2_rloc = int(r2.get_rloc16(), 16)
r3_rloc = int(r3.get_rloc16(), 16)
c_rloc = int(c.get_rloc16(), 16)
r1_address = next(addr for addr in r1.get_ip_addrs() if addr.startswith('fd00:abba:'))
r2_address = next(addr for addr in r2.get_ip_addrs() if addr.startswith('fd00:abba:'))
r3_address = next(addr for addr in r3.get_ip_addrs() if addr.startswith('fd00:abba:'))
c_address = next(addr for addr in c.get_ip_addrs() if addr.startswith('fd00:abba:'))
port = 4321
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Send a single UDP message from r2 to c. This adds an address cache
# entry on r2 for c pointing to r3 (the current parent of c).
r2.udp_open()
r2.udp_send(c_address, port, 'hi_from_r2_to_c')
def check_r2_cache_table():
cache_table = r2.get_eidcache()
for entry in cache_table:
fields = entry.strip().split(' ')
if (fields[0] == c_address):
verify(int(fields[1], 16) == r3_rloc)
verify(fields[2] == 'cache')
break
else:
verify(False)
verify_within(check_r2_cache_table, 20)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Force c to switch its parent from r3 to r2
#
# r1 ---- r2 ---- r3
# |
# |
# c
r3.un_allowlist_node(c)
r2.allowlist_node(c)
c.allowlist_node(r2)
c.un_allowlist_node(r3)
c.thread_stop()
c.thread_start()
def check_c_attaches_to_r2():
verify(c.get_state() == 'child')
verify(int(c.get_parent_info()['Rloc'], 16) == r2_rloc)
verify_within(check_c_attaches_to_r2, 10)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Send a single UDP message from r3 to c. This adds an address cache
# entry on r3 for c pointing to r2 (the current parent of c).
r3.udp_open()
r3.udp_send(c_address, port, 'hi_from_r3_to_c')
def check_r3_cache_table():
cache_table = r3.get_eidcache()
for entry in cache_table:
fields = entry.strip().split(' ')
if (fields[0] == c_address):
verify(int(fields[1], 16) == r2_rloc)
verify(fields[2] == 'cache')
break
else:
verify(False)
verify_within(check_r3_cache_table, 20)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Force c to switch its parent again now to r1
#
# r1 ---- r2 ---- r3
# |
# |
# c
r2.un_allowlist_node(c)
r1.allowlist_node(c)
c.allowlist_node(r1)
c.un_allowlist_node(r2)
c.thread_stop()
c.thread_start()
def check_c_attaches_to_r1():
verify(c.get_state() == 'child')
verify(int(c.get_parent_info()['Rloc'], 16) == r1_rloc)
verify_within(check_c_attaches_to_r1, 10)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Now ping c from r2.
#
# If r2 address cache entry is not cleared when c attached to r1, r2
# will still have an entry pointing to r3, and r3 will have an entry
# pointing to r2, thus creating a loop (the msg will not be delivered
# to r3)
r2.ping(c_address)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+214
View File
@@ -0,0 +1,214 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description:
#
# Verify device mode change on children.
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
parent = cli.Node()
child1 = cli.Node()
child2 = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
parent.form('modechange')
child1.join(parent, cli.JOIN_TYPE_END_DEVICE)
child2.join(parent, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
verify(parent.get_state() == 'leader')
verify(child1.get_state() == 'child')
verify(child2.get_state() == 'child')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
child1_rloc = int(child1.get_rloc16(), 16)
child2_rloc = int(child2.get_rloc16(), 16)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Check the mode on children and also on parent child table
verify(parent.get_mode() == 'rdn')
verify(child1.get_mode() == 'rn')
verify(child2.get_mode() == '-')
child_table = parent.get_child_table()
verify(len(child_table) == 2)
for entry in child_table:
if int(entry['RLOC16'], 16) == child1_rloc:
verify(entry['R'] == '1')
verify(entry['D'] == '0')
verify(entry['N'] == '1')
elif int(entry['RLOC16'], 16) == child2_rloc:
verify(entry['R'] == '0')
verify(entry['D'] == '0')
verify(entry['N'] == '0')
else:
verify(False)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Change the network data flag on child 2 (sleepy) and verify that it
# gets changed on parent's child table.
child2.set_mode('n')
def check_child2_n_flag_change():
verify(child2.get_mode() == 'n')
child_table = parent.get_child_table()
verify(len(child_table) == 2)
for entry in child_table:
if int(entry['RLOC16'], 16) == child1_rloc:
verify(entry['R'] == '1')
verify(entry['D'] == '0')
verify(entry['N'] == '1')
elif int(entry['RLOC16'], 16) == child2_rloc:
verify(entry['R'] == '0')
verify(entry['D'] == '0')
verify(entry['N'] == '1')
else:
verify(False)
verify_within(check_child2_n_flag_change, 5)
# Verify that mode change did not cause child2 to detach and re-attach
verify(int(cli.Node.parse_list(child2.get_mle_counter())['Role Detached']) == 1)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Change child1 from rx-on to sleepy and verify the change on
# parent's child table. This mode change should require child
# to detach and attach again.
child1.set_mode('n')
def check_child1_become_sleepy():
verify(child1.get_mode() == 'n')
child_table = parent.get_child_table()
verify(len(child_table) == 2)
for entry in child_table:
if int(entry['RLOC16'], 16) == child1_rloc:
verify(entry['R'] == '0')
verify(entry['D'] == '0')
verify(entry['N'] == '1')
elif int(entry['RLOC16'], 16) == child2_rloc:
verify(entry['R'] == '0')
verify(entry['D'] == '0')
verify(entry['N'] == '1')
else:
verify(False)
verify_within(check_child1_become_sleepy, 5)
verify(int(cli.Node.parse_list(child1.get_mle_counter())['Role Detached']) == 2)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Change child2 from sleepy to rx-on and verify the change on
# parent's child table. Verify that child2 did not detach and
# used MLE "Child Update" exchange.
child2.set_mode('rn')
def check_child2_become_rx_on():
verify(child2.get_mode() == 'rn')
child_table = parent.get_child_table()
verify(len(child_table) == 2)
for entry in child_table:
if int(entry['RLOC16'], 16) == child1_rloc:
verify(entry['R'] == '0')
verify(entry['D'] == '0')
verify(entry['N'] == '1')
elif int(entry['RLOC16'], 16) == child2_rloc:
verify(entry['R'] == '1')
verify(entry['D'] == '0')
verify(entry['N'] == '1')
else:
verify(False)
verify_within(check_child2_become_rx_on, 5)
verify(int(cli.Node.parse_list(child2.get_mle_counter())['Role Detached']) == 1)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Now change child2 to become sleepy again. Since it was originally
# attached as sleepy it should not detach and attach again and
# can do this using MlE "Child Update" exchange with parent.
child2.set_mode('n')
def check_child2_become_sleepy_again():
verify(child2.get_mode() == 'n')
child_table = parent.get_child_table()
verify(len(child_table) == 2)
for entry in child_table:
if int(entry['RLOC16'], 16) == child1_rloc:
verify(entry['R'] == '0')
verify(entry['D'] == '0')
verify(entry['N'] == '1')
elif int(entry['RLOC16'], 16) == child2_rloc:
verify(entry['R'] == '0')
verify(entry['D'] == '0')
verify(entry['N'] == '1')
else:
verify(False)
verify_within(check_child2_become_sleepy_again, 5)
verify(int(cli.Node.parse_list(child2.get_mle_counter())['Role Detached']) == 1)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+346
View File
@@ -0,0 +1,346 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description: Network Data update and version changes (stable only vs. full version).
#
# Network topology
#
# r1 ---- r2 ---- r3
# |
# |
# sed
#
#
# sed is sleepy-end node and also configured to request stable Network Data only
#
# Test covers the following steps:
# - Adding/removing prefixes (stable or temporary) on r1
# - Verifying that Network Data is updated on all nodes
# - Ensuring correct update to version and stable version
#
# The above steps are repeated over many different situations:
# - Where the same prefixes are also added by other nodes
# - Or the same prefixes are added as off-mesh routes by other nodes
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node()
r2 = cli.Node()
r3 = cli.Node()
sed = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
r1.allowlist_node(r2)
r2.allowlist_node(r1)
r2.allowlist_node(r3)
r2.allowlist_node(sed)
r2.allowlist_node(r2)
sed.allowlist_node(r2)
r1.form('netdata')
r2.join(r1)
r3.join(r1)
sed.join(r1, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
sed.set_mode('-')
sed.set_pollperiod(400)
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
r1_rloc = r1.get_rloc16()
r2_rloc = r2.get_rloc16()
r3_rloc = r3.get_rloc16()
versions = r1.get_netdata_versions()
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def verify_versions_incremented():
global versions
new_versions = r1.get_netdata_versions()
verify(new_versions[0] == ((versions[0] + 1) % 256))
verify(new_versions[1] == ((versions[1] + 1) % 256))
versions = new_versions
def verify_stabe_version_incremented():
global versions
new_versions = r1.get_netdata_versions()
verify(new_versions[0] == ((versions[0] + 1) % 256))
verify(new_versions[1] == versions[1])
versions = new_versions
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Add prefix `fd00:1::/64` on r1 as stable and validate
# that entries are updated on all nodes and versions are changed.
r1.add_prefix('fd00:1::/64', 'os')
r1.register_netdata()
def check_r1_prefix_added_on_all_nodes():
for node in [r1, r2, r3]:
verify('fd00:1:0:0::/64 os med ' + r1_rloc in node.get_netdata_prefixes())
verify('fd00:1:0:0::/64 os med fffe' in sed.get_netdata_prefixes())
verify_within(check_r1_prefix_added_on_all_nodes, 2)
verify_versions_incremented()
# Add prefix `fd00:2::/64` on r2 as temporary and ensure it is seen on
# all nodes and not seen on sed.
r2.add_prefix('fd00:2::/64', 'po', 'high')
r2.register_netdata()
def check_r2_prefix_added_on_all_nodes():
for node in [r1, r2, r3]:
verify('fd00:2:0:0::/64 po high ' + r2_rloc in node.get_netdata_prefixes())
verify(not 'fd00:2:0:0::/64 po high fffe' in sed.get_netdata_prefixes())
verify_within(check_r2_prefix_added_on_all_nodes, 2)
verify_stabe_version_incremented()
# Remove prefix `fd00:1::/64` from r1.
r1.remove_prefix('fd00:1::/64')
r1.register_netdata()
def check_r1_prefix_removed_on_all_nodes():
for node in [r1, r2, r3]:
verify(not 'fd00:1:0:0::/64 os med ' + r1_rloc in node.get_netdata_prefixes())
verify(not 'fd00:1:0:0::/64 os med fffe' in sed.get_netdata_prefixes())
verify_within(check_r1_prefix_removed_on_all_nodes, 2)
verify_versions_incremented()
# Remove prefix `fd00:2::/64` from r2.
r2.remove_prefix('fd00:2::/64')
r2.register_netdata()
def check_r2_prefix_removed_on_all_nodes():
for node in [r1, r2, r3]:
verify(not 'fd00:2:0:0::/64 po high ' + r2_rloc in node.get_netdata_prefixes())
verify(not 'fd00:2:0:0::/64 po high fffe' in sed.get_netdata_prefixes())
verify_within(check_r2_prefix_removed_on_all_nodes, 2)
verify_stabe_version_incremented()
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Repeat the same checks but now r3 also adds ``fd00:1::/64` with different
# flags.
r3.add_prefix('fd00:1::/64', 'paos')
r3.register_netdata()
def check_r3_prefix_added_on_all_nodes():
for node in [r1, r2, r3]:
verify('fd00:1:0:0::/64 paos med ' + r3_rloc in node.get_netdata_prefixes())
verify('fd00:1:0:0::/64 paos med fffe' in sed.get_netdata_prefixes())
verify_within(check_r3_prefix_added_on_all_nodes, 2)
verify_versions_incremented()
r1.add_prefix('fd00:1::/64', 'os')
r1.register_netdata()
verify_within(check_r1_prefix_added_on_all_nodes, 2)
verify_versions_incremented()
r2.add_prefix('fd00:2::/64', 'po', 'high')
r2.register_netdata()
verify_within(check_r2_prefix_added_on_all_nodes, 2)
verify_stabe_version_incremented()
r1.remove_prefix('fd00:1::/64')
r1.register_netdata()
verify_within(check_r1_prefix_removed_on_all_nodes, 2)
verify_versions_incremented()
r2.remove_prefix('fd00:2::/64')
r2.register_netdata()
verify_within(check_r2_prefix_removed_on_all_nodes, 2)
verify_stabe_version_incremented()
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Repeat the same checks with r3 also adding ``fd00:2::/64`
r3.add_prefix('fd00:2::/64', 'paos')
r3.register_netdata()
def check_new_r3_prefix_added_on_all_nodes():
for node in [r1, r2, r3]:
verify('fd00:2:0:0::/64 paos med ' + r3_rloc in node.get_netdata_prefixes())
verify('fd00:2:0:0::/64 paos med fffe' in sed.get_netdata_prefixes())
verify_within(check_new_r3_prefix_added_on_all_nodes, 2)
verify_versions_incremented()
r1.add_prefix('fd00:1::/64', 'os')
r1.register_netdata()
verify_within(check_r1_prefix_added_on_all_nodes, 2)
verify_versions_incremented()
r2.add_prefix('fd00:2::/64', 'po', 'high')
r2.register_netdata()
verify_within(check_r2_prefix_added_on_all_nodes, 2)
verify_stabe_version_incremented()
r1.remove_prefix('fd00:1::/64')
r1.register_netdata()
verify_within(check_r1_prefix_removed_on_all_nodes, 2)
verify_versions_incremented()
r2.remove_prefix('fd00:2::/64')
r2.register_netdata()
verify_within(check_r2_prefix_removed_on_all_nodes, 2)
verify_stabe_version_incremented()
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Repeat the same checks with r3 adding the two prefixes as temporary.
r3.remove_prefix('fd00:1::/64')
r3.remove_prefix('fd00:2::/64')
r3.add_prefix('fd00:1::/64', 'pao')
r3.add_prefix('fd00:2::/64', 'pao')
r3.register_netdata()
def check_r3_prefixes_as_temp_added_on_all_nodes():
for node in [r1, r2, r3]:
prefixes = node.get_netdata_prefixes()
verify('fd00:1:0:0::/64 pao med ' + r3_rloc in prefixes)
verify('fd00:1:0:0::/64 pao med ' + r3_rloc in prefixes)
verify(len(sed.get_netdata_prefixes()) == 0)
verify_within(check_r3_prefixes_as_temp_added_on_all_nodes, 2)
verify_versions_incremented()
r1.add_prefix('fd00:1::/64', 'os')
r1.register_netdata()
verify_within(check_r1_prefix_added_on_all_nodes, 2)
verify_versions_incremented()
r2.add_prefix('fd00:2::/64', 'po', 'high')
r2.register_netdata()
verify_within(check_r2_prefix_added_on_all_nodes, 2)
verify_stabe_version_incremented()
r1.remove_prefix('fd00:1::/64')
r1.register_netdata()
verify_within(check_r1_prefix_removed_on_all_nodes, 2)
verify_versions_incremented()
r2.remove_prefix('fd00:2::/64')
r2.register_netdata()
verify_within(check_r2_prefix_removed_on_all_nodes, 2)
verify_stabe_version_incremented()
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Finally repeat the same checks with r3 adding the two prefixes
# as off-mesh route prefix.
r3.remove_prefix('fd00:1::/64')
r3.remove_prefix('fd00:2::/64')
r3.add_route('fd00:1::/64', 's')
r3.add_route('fd00:2::/64', '-')
r3.register_netdata()
def check_r3_routes_added_on_all_nodes():
for node in [r1, r2, r3]:
routes = node.get_netdata_routes()
verify('fd00:1:0:0::/64 s med ' + r3_rloc in routes)
verify('fd00:2:0:0::/64 med ' + r3_rloc in routes)
verify('fd00:1:0:0::/64 s med fffe' in sed.get_netdata_routes())
verify(not 'fd00:1:0:0::/64 med fffe' in sed.get_netdata_routes())
verify_within(check_r3_routes_added_on_all_nodes, 2)
verify_versions_incremented()
r1.add_prefix('fd00:1::/64', 'os')
r1.register_netdata()
verify_within(check_r1_prefix_added_on_all_nodes, 2)
verify_versions_incremented()
r2.add_prefix('fd00:2::/64', 'po', 'high')
r2.register_netdata()
verify_within(check_r2_prefix_added_on_all_nodes, 2)
verify_stabe_version_incremented()
r1.remove_prefix('fd00:1::/64')
r1.register_netdata()
verify_within(check_r1_prefix_removed_on_all_nodes, 2)
verify_versions_incremented()
r2.remove_prefix('fd00:2::/64')
r2.register_netdata()
verify_within(check_r2_prefix_removed_on_all_nodes, 2)
verify_stabe_version_incremented()
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+166
View File
@@ -0,0 +1,166 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description:
#
# Verifies `ChannelManager` channel change process.
#
# Network Topology:
#
# r1 --- r2 --- r3
# /\ | |
# / \ | |
# sc1 ec1 sc2 sc3
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
speedup = 20
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node()
r2 = cli.Node()
r3 = cli.Node()
sc1 = cli.Node()
ec1 = cli.Node()
sc2 = cli.Node()
sc3 = cli.Node()
nodes = [r1, r2, r3, sc1, ec1, sc2, sc3]
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
r1.allowlist_node(r2)
r1.allowlist_node(sc1)
r1.allowlist_node(ec1)
r2.allowlist_node(r1)
r2.allowlist_node(r3)
r2.allowlist_node(sc2)
r3.allowlist_node(r2)
r3.allowlist_node(sc3)
sc1.allowlist_node(r1)
ec1.allowlist_node(r1)
sc2.allowlist_node(r2)
sc3.allowlist_node(r3)
r1.form('chan-man', channel=11)
r2.join(r1)
r3.join(r1)
sc1.join(r1, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
ec1.join(r1, cli.JOIN_TYPE_END_DEVICE)
sc2.join(r1, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
sc3.join(r1, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
sc1.set_pollperiod(500)
sc2.set_pollperiod(500)
sc3.set_pollperiod(500)
verify(r1.get_state() == 'leader')
verify(r2.get_state() == 'router')
verify(r3.get_state() == 'router')
verify(ec1.get_state() == 'child')
verify(sc1.get_state() == 'child')
verify(sc2.get_state() == 'child')
verify(sc3.get_state() == 'child')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
result = cli.Node.parse_list(r1.cli('channel manager'))
verify(result['channel'] == '0')
verify(result['auto'] == '0')
r1.cli('channel manager delay 4')
verify(int(r1.cli('channel manager delay')[0]) == 4)
channel = 11
def check_channel_on_all_nodes():
verify(all(int(node.get_channel()) == channel for node in nodes))
verify_within(check_channel_on_all_nodes, 10)
# Request a channel change to channel 13 from router r1. Verify
# that all nodes switch to the new channel.
channel = 13
r1.cli('channel manager change', channel)
verify_within(check_channel_on_all_nodes, 10)
# Request same channel change on multiple routers at the same time.
channel = 14
r1.cli('channel manager change', channel)
r2.cli('channel manager change', channel)
r3.cli('channel manager change', channel)
verify_within(check_channel_on_all_nodes, 10)
# Request different channel changes from same router back-to-back.
channel = 15
r1.cli('channel manager change', channel)
time.sleep(1 / speedup)
channel = 16
r1.cli('channel manager change', channel)
verify_within(check_channel_on_all_nodes, 10)
# Request different channels from two routers (r1 and r2).
# We increase delay on r1 to make sure r1 is in middle of
# channel when new change is requested from r2.
r1.cli('channel manager delay 20')
r1.cli('channel manager change 17')
time.sleep(5 / speedup)
verify_within(check_channel_on_all_nodes, 10)
channael = 18
r2.cli('channel manager change', channel)
verify_within(check_channel_on_all_nodes, 10)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+174
View File
@@ -0,0 +1,174 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description:
#
# Verifies `ChannelManager` channel selection procedure
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
# Run the test with 10,000 time speedup factor
speedup = 10000
cli.Node.set_time_speedup_factor(speedup)
node = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
node.form('chan-sel', channel=24)
verify(node.get_state() == 'leader')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
channel = 24
def check_channel():
verify(int(node.get_channel()) == channel)
check_channel()
all_channels_mask = int('0x7fff800', 0)
chan_12_to_15_mask = int('0x000f000', 0)
chan_15_to_17_mask = int('0x0038000', 0)
# Set supported channel mask to be all channels
node.cli('channel manager supported', all_channels_mask)
# Sleep for 4.5 second with speedup factor of 10,000 this is more than 12
# hours. We sleep instead of immediately checking the sample counter in
# order to not add more actions/events into simulation (specially since
# we are running at very high speedup).
time.sleep(4.5)
result = cli.Node.parse_list(node.cli('channel monitor')[:5])
verify(result['enabled'] == '1')
verify(int(result['count']) > 970)
# Issue a channel-select with quality check enabled, and verify that no
# action is taken.
node.cli('channel manager select 0')
result = cli.Node.parse_list(node.cli('channel manager'))
verify(result['channel'] == '0')
# Issue a channel-select with quality check disabled, verify that channel
# is switched to channel 11.
node.cli('channel manager select 1')
result = cli.Node.parse_list(node.cli('channel manager'))
verify(result['channel'] == '11')
channel = 11
verify_within(check_channel, 2)
# Set channels 12-15 as favorable and request a channel select, verify
# that channel is switched to 12.
#
# Even though 11 would be best, quality difference between 11 and 12
# is not high enough for selection algorithm to pick an unfavored
# channel.
node.cli('channel manager favored', chan_12_to_15_mask)
channel = 25
node.cli('channel manager change', channel)
verify_within(check_channel, 2)
node.cli('channel manager select 1')
result = cli.Node.parse_list(node.cli('channel manager'))
verify(result['channel'] == '12')
channel = 12
verify_within(check_channel, 2)
# Set channels 15-17 as favorables and request a channel select,
# verify that channel is switched to 11.
#
# This time the quality difference between 11 and 15 should be high
# enough for selection algorithm to pick the best though unfavored
# channel (i.e., channel 11).
channel = 25
node.cli('channel manager change', channel)
verify_within(check_channel, 2)
node.cli('channel manager favored', chan_15_to_17_mask)
node.cli('channel manager select 1')
result = cli.Node.parse_list(node.cli('channel manager'))
verify(result['channel'] == '11')
channel = 11
verify_within(check_channel, 2)
# Set channels 12-15 as favorable and request a channel select, verify
# that channel is not switched.
node.cli('channel manager favored', chan_12_to_15_mask)
node.cli('channel manager select 1')
result = cli.Node.parse_list(node.cli('channel manager'))
verify(result['channel'] == '11')
channel = 11
verify_within(check_channel, 2)
# Starting from channel 12 and issuing a channel select (which would
# pick 11 as best channel). However, since quality difference between
# current channel 12 and new best channel 11 is not large enough, no
# action should be taken.
channel = 12
node.cli('channel manager change', channel)
verify_within(check_channel, 2)
node.cli('channel manager favored', all_channels_mask)
node.cli('channel manager select 1')
result = cli.Node.parse_list(node.cli('channel manager'))
verify(result['channel'] == '12')
verify_within(check_channel, 2)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+108
View File
@@ -0,0 +1,108 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description:
#
# Orphaned node attach through MLE Announcement after channel change.
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
speedup = 40
cli.Node.set_time_speedup_factor(speedup)
router = cli.Node()
c1 = cli.Node()
c2 = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
router.form('announce-tst', channel=11)
c1.join(router, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
c2.join(router, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
c1.set_pollperiod(500)
c2.set_pollperiod(500)
verify(router.get_state() == 'leader')
verify(c1.get_state() == 'child')
verify(c2.get_state() == 'child')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
# Stop c2
c2.thread_stop()
# Switch the rest of network to channel 26
router.cli('channel manager change 26')
def check_channel_changed_to_26_on_r1_c1():
for node in [router, c1]:
verify(int(node.get_channel()) == 26)
verify_within(check_channel_changed_to_26_on_r1_c1, 10)
# Now re-enable c2 and verify that it does attach to router and is on
# channel 26. c2 would go through the ML Announce recovery.
c2.thread_start()
verify(int(c2.get_channel()) == 11)
# wait for 20s for c2 to be attached/associated
def check_c2_is_attched():
verify(c2.get_state() == 'child')
verify_within(check_c2_is_attched, 20)
# Check that c2 is now on channel 26.
verify(int(c2.get_channel()) == 26)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+127
View File
@@ -0,0 +1,127 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description: This test covers joining of nodes with different radio links
#
# Parent node with trel and 15.4 with 3 children.
#
# parent
# (trel + 15.4)
# / | \
# / | \
# / | \
# c1 c2 c3
# (15.4) (trel) (trel+15.4)
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
parent = cli.Node(cli.RADIO_15_4_TREL)
c1 = cli.Node(cli.RADIO_15_4)
c2 = cli.Node(cli.RADIO_TREL)
c3 = cli.Node(cli.RADIO_15_4_TREL)
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
# Verify that each node supports the correct radio links.
verify(parent.multiradio_get_radios() == '[15.4, TREL]')
verify(c1.multiradio_get_radios() == '[15.4]')
verify(c2.multiradio_get_radios() == '[TREL]')
verify(c3.multiradio_get_radios() == '[15.4, TREL]')
parent.form("multi-radio")
c1.join(parent, cli.JOIN_TYPE_END_DEVICE)
c2.join(parent, cli.JOIN_TYPE_END_DEVICE)
c3.join(parent, cli.JOIN_TYPE_END_DEVICE)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Verify that parent correctly learns about all the children and their
# supported radio links.
c1_ext_addr = c1.get_ext_addr()
c2_ext_addr = c2.get_ext_addr()
c3_ext_addr = c3.get_ext_addr()
neighbor_radios = parent.multiradio_get_neighbor_list()
verify(len(neighbor_radios) == 3)
for entry in neighbor_radios:
info = cli.Node.parse_multiradio_neighbor_entry(entry)
radios = info['Radios']
if info['ExtAddr'] == c1_ext_addr:
verify(int(info['RLOC16'], 16) == int(c1.get_rloc16(), 16))
verify(len(radios) == 1)
verify('15.4' in radios)
elif info['ExtAddr'] == c2_ext_addr:
verify(int(info['RLOC16'], 16) == int(c2.get_rloc16(), 16))
verify(len(radios) == 1)
verify('TREL' in radios)
elif info['ExtAddr'] == c3_ext_addr:
verify(int(info['RLOC16'], 16) == int(c3.get_rloc16(), 16))
verify(len(radios) == 2)
verify('15.4' in radios)
verify('TREL' in radios)
else:
verify(False)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Reset the parent and check that all children are attached back successfully
del parent
parent = cli.Node(cli.RADIO_15_4_TREL, index=1)
parent.interface_up()
parent.thread_start()
def check_all_children_are_attached():
verify(len(parent.get_child_table()) == 3)
verify_within(check_all_children_are_attached, 10)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+184
View File
@@ -0,0 +1,184 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description: This test covers behavior of device after trel network is temporarily disabled
# and rediscovery of trel radio using probe mechanism.
#
# r1 ---------- r2
# (15.4+trel) (15.4+trel)
#
# On r2 we disable trel temporarily.
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node(cli.RADIO_15_4_TREL)
r2 = cli.Node(cli.RADIO_15_4_TREL)
# -----------------------------------------------------------------------------------------------------------------------
# Build network topology
r1.form("prove-discover")
r2.join(r1)
verify(r1.get_state() == 'leader')
verify(r2.get_state() == 'router')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
high_preference_threshold = 220
min_preference_threshold = 0
verify(r1.multiradio_get_radios() == '[15.4, TREL]')
verify(r2.multiradio_get_radios() == '[15.4, TREL]')
r1_rloc = int(r1.get_rloc16(), 16)
r2_rloc = int(r2.get_rloc16(), 16)
r1_ml_addr = r1.get_mleid_ip_addr()
r2_ml_addr = r2.get_mleid_ip_addr()
# Wait till routes are discovered.
def check_r1_router_table():
table = r1.get_router_table()
verify(len(table) == 2)
for entry in table:
verify(int(entry['RLOC16'], 0) == r1_rloc or int(entry['Link']) == 1)
verify_within(check_r1_router_table, 120)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Verify that r1 detected both TREL & 15.4 as supported radios by r2
def check_r1_sees_r2_has_two_radio_links():
neighbor_radios = r1.multiradio_get_neighbor_list()
verify(len(neighbor_radios) == 1)
info = cli.Node.parse_multiradio_neighbor_entry(neighbor_radios[0])
verify(int(info['RLOC16'], 16) == r2_rloc)
radios = info['Radios']
verify(len(radios) == 2)
verify('15.4' in radios)
verify('TREL' in radios)
cli.verify_within(check_r1_sees_r2_has_two_radio_links, 10)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Ping r2 from r1 and verify that r1 prefers trel radio link for
# sending to r2.
r1.ping(r2_ml_addr, count=5)
neighbor_radios = r1.multiradio_get_neighbor_list()
verify(len(neighbor_radios) == 1)
info = cli.Node.parse_multiradio_neighbor_entry(neighbor_radios[0])
radios = info['Radios']
verify(radios['TREL'] >= high_preference_threshold)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Now temporary filter trel link on r2 and ping again. We expect that
# r1 to quickly detect that trel is no longer supported by r2 and
# prefer 15.4 for tx to r2.
r2.cli('trel filter enable')
verify(r2.cli('trel filter')[0] == 'Enabled')
r1.udp_open()
for count in range(5):
r1.udp_send(r2_ml_addr, 12345, 'hi_r2_from_r1')
def check_r1_does_not_prefer_trel_for_r2():
neighbor_radios = r1.multiradio_get_neighbor_list()
verify(len(neighbor_radios) == 1)
info = cli.Node.parse_multiradio_neighbor_entry(neighbor_radios[0])
radios = info['Radios']
verify(radios['TREL'] <= min_preference_threshold)
verify_within(check_r1_does_not_prefer_trel_for_r2, 10)
# Check that we can send between r1 and r2 (now all tx should use 15.4)
r1.ping(r2_ml_addr, count=5, verify_success=False)
r1.ping(r2_ml_addr, count=5)
neighbor_radios = r1.multiradio_get_neighbor_list()
verify(len(neighbor_radios) == 1)
info = cli.Node.parse_multiradio_neighbor_entry(neighbor_radios[0])
radios = info['Radios']
verify(radios['TREL'] <= min_preference_threshold)
verify(radios['15.4'] >= high_preference_threshold)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Enable trel back on r2, start sending traffic from r1 to r2
# The probe mechanism should kick and detect that r2 has trel again.
r2.cli('trel filter disable')
verify(r2.cli('trel filter')[0] == 'Disabled')
r2.udp_open()
for count in range(80):
r2.udp_send(r1_ml_addr, 12345, 'hi_r1_from_r2')
def check_r1_again_prefers_trel_for_r2():
neighbor_radios = r1.multiradio_get_neighbor_list()
verify(len(neighbor_radios) == 1)
info = cli.Node.parse_multiradio_neighbor_entry(neighbor_radios[0])
radios = info['Radios']
verify(radios['TREL'] >= high_preference_threshold)
verify_within(check_r1_again_prefers_trel_for_r2, 10)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+184
View File
@@ -0,0 +1,184 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description: This test covers behavior of device after TREL network is temporarily disabled
# and rediscovery of TREL by receiving message over that radio from the neighbor.
#
# r1 ---------- r2
# (15.4+trel) (15.4+trel)
#
# On r2 we disable trel temporarily.
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node(cli.RADIO_15_4_TREL)
r2 = cli.Node(cli.RADIO_15_4_TREL)
# -----------------------------------------------------------------------------------------------------------------------
# Build network topology
r1.form("discover-by-rx")
r2.join(r1)
verify(r1.get_state() == 'leader')
verify(r2.get_state() == 'router')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
high_preference_threshold = 220
min_preference_threshold = 0
verify(r1.multiradio_get_radios() == '[15.4, TREL]')
verify(r2.multiradio_get_radios() == '[15.4, TREL]')
r1_rloc = int(r1.get_rloc16(), 16)
r2_rloc = int(r2.get_rloc16(), 16)
r1_ml_addr = r1.get_mleid_ip_addr()
r2_ml_addr = r2.get_mleid_ip_addr()
# Wait till routes are discovered.
def check_r1_router_table():
table = r1.get_router_table()
verify(len(table) == 2)
for entry in table:
verify(int(entry['RLOC16'], 0) == r1_rloc or int(entry['Link']) == 1)
verify_within(check_r1_router_table, 120)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Verify that r1 detected both TREL & 15.4 as supported radios by r2
def check_r1_sees_r2_has_two_radio_links():
neighbor_radios = r1.multiradio_get_neighbor_list()
verify(len(neighbor_radios) == 1)
info = cli.Node.parse_multiradio_neighbor_entry(neighbor_radios[0])
verify(int(info['RLOC16'], 16) == r2_rloc)
radios = info['Radios']
verify(len(radios) == 2)
verify('15.4' in radios)
verify('TREL' in radios)
cli.verify_within(check_r1_sees_r2_has_two_radio_links, 10)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Ping r2 from r1 and verify that r1 prefers trel radio link for
# sending to r2.
r1.ping(r2_ml_addr, count=5)
neighbor_radios = r1.multiradio_get_neighbor_list()
verify(len(neighbor_radios) == 1)
info = cli.Node.parse_multiradio_neighbor_entry(neighbor_radios[0])
radios = info['Radios']
verify(radios['TREL'] >= high_preference_threshold)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Now temporary filter trel link on r2 and ping again. We expect that
# r1 to quickly detect that trel is no longer supported by r2 and
# prefer 15.4 for tx to r2.
r2.cli('trel filter enable')
verify(r2.cli('trel filter')[0] == 'Enabled')
r1.udp_open()
for count in range(5):
r1.udp_send(r2.get_mleid_ip_addr(), 12345, 'hi_r2_from_r1')
def check_r1_does_not_prefer_trel_for_r2():
neighbor_radios = r1.multiradio_get_neighbor_list()
verify(len(neighbor_radios) == 1)
info = cli.Node.parse_multiradio_neighbor_entry(neighbor_radios[0])
radios = info['Radios']
verify(radios['TREL'] <= min_preference_threshold)
verify_within(check_r1_does_not_prefer_trel_for_r2, 10)
# Check that we can send between r1 and r2 (now all tx should use 15.4)
r1.ping(r2.get_mleid_ip_addr(), count=5, verify_success=False)
r1.ping(r2.get_mleid_ip_addr(), count=5)
neighbor_radios = r1.multiradio_get_neighbor_list()
verify(len(neighbor_radios) == 1)
info = cli.Node.parse_multiradio_neighbor_entry(neighbor_radios[0])
radios = info['Radios']
verify(radios['TREL'] <= min_preference_threshold)
verify(radios['15.4'] >= high_preference_threshold)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Enable trel back on r2, start sending traffic from r2 to r1.
# r2 would use probe mechanism to discover that trel is enabled again
# r1 should notice new rx on trel and update trel preference for r1
r2.cli('trel filter disable')
verify(r2.cli('trel filter')[0] == 'Disabled')
for count in range(80):
r1.udp_send(r2.get_mleid_ip_addr(), 12345, 'hi_r2_from_r1_probe')
def check_r1_again_prefers_trel_for_r2():
neighbor_radios = r1.multiradio_get_neighbor_list()
verify(len(neighbor_radios) == 1)
info = cli.Node.parse_multiradio_neighbor_entry(neighbor_radios[0])
radios = info['Radios']
verify(radios['TREL'] >= high_preference_threshold)
verify_within(check_r1_again_prefers_trel_for_r2, 10)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+138
View File
@@ -0,0 +1,138 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description: This test covers behavior of MeshHeader messages (message sent over multi-hop) when the
# underlying devices (in the path) support different radio types with different frame MTU length.
#
# r1 --------- r2 ---------- r3
# (trel) (15.4+trel) (15.4)
# |
# |
# c2 (15.4)
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node(cli.RADIO_TREL)
r2 = cli.Node(cli.RADIO_15_4_TREL)
r3 = cli.Node(cli.RADIO_15_4)
c2 = cli.Node(cli.RADIO_15_4)
# -----------------------------------------------------------------------------------------------------------------------
# Build network topology
r1.allowlist_node(r2)
r2.allowlist_node(r1)
r2.allowlist_node(r3)
r2.allowlist_node(c2)
r3.allowlist_node(r2)
c2.allowlist_node(r2)
r1.form("mesh-header")
r2.join(r1)
r3.join(r2)
c2.join(r2, cli.JOIN_TYPE_END_DEVICE)
verify(r1.get_state() == 'leader')
verify(r2.get_state() == 'router')
verify(r3.get_state() == 'router')
verify(c2.get_state() == 'child')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
verify(r1.multiradio_get_radios() == '[TREL]')
verify(r2.multiradio_get_radios() == '[15.4, TREL]')
verify(r3.multiradio_get_radios() == '[15.4]')
r1_rloc = int(r1.get_rloc16(), 16)
r1_ml_addr = r1.get_mleid_ip_addr()
r3_ml_addr = r3.get_mleid_ip_addr()
# Wait till routes are discovered.
def check_r1_router_table():
table = r1.get_router_table()
verify(len(table) == 3)
for entry in table:
verify(int(entry['RLOC16'], 0) == r1_rloc or int(entry['Link']) == 1 or int(entry['Next Hop']) != 63)
verify_within(check_r1_router_table, 120)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Ping r3 from r2 using large message size. Such a message would be sent as a
# Mesh Header message. Note that the origin r1 is a trel-only device
# whereas the final destination r3 is a 15.4-only device. The originator
# should use smaller MTU size (which then fragment the message into
# multiple frames) otherwise the 15.4 link won't be able to handle it.
r1.ping(r3_ml_addr, size=1000, count=2)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Ping r1 from c2 using large message size.
c2.ping(r1_ml_addr, size=1000, count=2)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Ping r1 from r3
r3.ping(r1_ml_addr, size=1000, count=2)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Finally ping r1 and r3 from r2.
r2.ping(r1_ml_addr, size=1000, count=2)
r2.ping(r3_ml_addr, size=1000, count=2)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description: Test active scan with nodes supporting different radios
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
n1 = cli.Node(cli.RADIO_15_4)
n2 = cli.Node(cli.RADIO_TREL)
n3 = cli.Node(cli.RADIO_15_4_TREL)
s1 = cli.Node(cli.RADIO_15_4)
s2 = cli.Node(cli.RADIO_TREL)
s3 = cli.Node(cli.RADIO_15_4_TREL)
# -----------------------------------------------------------------------------------------------------------------------
# Build network topology
n1.form("n1", channel='20')
n2.form("n2", channel='21')
n3.form("n3", channel='22')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
verify(n1.multiradio_get_radios() == '[15.4]')
verify(n2.multiradio_get_radios() == '[TREL]')
verify(n3.multiradio_get_radios() == '[15.4, TREL]')
verify(s1.multiradio_get_radios() == '[15.4]')
verify(s2.multiradio_get_radios() == '[TREL]')
verify(s3.multiradio_get_radios() == '[15.4, TREL]')
def verify_scan_result_conatins_nodes(scan_result, nodes):
table = cli.Node.parse_table(scan_result)
verify(len(table) >= len(nodes))
for node in nodes:
ext_addr = node.get_ext_addr()
for entry in table:
if entry['MAC Address'] == ext_addr:
verify(int(entry['PAN'], 16) == int(node.get_panid(), 16))
verify(int(entry['Ch']) == int(node.get_channel()))
break
else:
verify(False)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Scan by scanner nodes (no network)
# Scan by s1 (15.4 only), expect to see n1(15.4) and n3(15.4+trel)
verify_scan_result_conatins_nodes(s1.cli('scan'), [n1, n3])
# Scan by s2 (trel only), expect to see n2(trel) and n3(15.4+trel)
verify_scan_result_conatins_nodes(s2.cli('scan'), [n2, n3])
# Scan by s3 (trel+15.4), expect to see all nodes
verify_scan_result_conatins_nodes(s3.cli('scan'), [n1, n2, n3])
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description: Test MLE discover scan with nodes supporting different radios
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
speedup = 10
cli.Node.set_time_speedup_factor(speedup)
n1 = cli.Node(cli.RADIO_15_4)
n2 = cli.Node(cli.RADIO_TREL)
n3 = cli.Node(cli.RADIO_15_4_TREL)
s1 = cli.Node(cli.RADIO_15_4)
s2 = cli.Node(cli.RADIO_TREL)
s3 = cli.Node(cli.RADIO_15_4_TREL)
# -----------------------------------------------------------------------------------------------------------------------
# Build network topology
n1.form("n1", channel='20')
n2.form("n2", channel='21')
n3.form("n3", channel='22')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
verify(n1.multiradio_get_radios() == '[15.4]')
verify(n2.multiradio_get_radios() == '[TREL]')
verify(n3.multiradio_get_radios() == '[15.4, TREL]')
verify(s1.multiradio_get_radios() == '[15.4]')
verify(s2.multiradio_get_radios() == '[TREL]')
verify(s3.multiradio_get_radios() == '[15.4, TREL]')
def verify_scan_result_conatins_nodes(scan_result, nodes):
table = cli.Node.parse_table(scan_result)
verify(len(table) >= len(nodes))
for node in nodes:
ext_addr = node.get_ext_addr()
for entry in table:
if entry['MAC Address'] == ext_addr:
verify(int(entry['PAN'], 16) == int(node.get_panid(), 16))
verify(int(entry['Ch']) == int(node.get_channel()))
break
else:
verify(False)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Scan by scanner nodes (no network)
s1.interface_up()
s2.interface_up()
s3.interface_up()
# Scan by s1 (15.4 only), expect to see n1(15.4) and n3(15.4+trel)
verify_scan_result_conatins_nodes(s1.cli('discover'), [n1, n3])
# Scan by s2 (trel only), expect to see n2(trel) and n3(15.4+trel)
verify_scan_result_conatins_nodes(s2.cli('discover'), [n2, n3])
# Scan by s3 (trel+15.4), expect to see all nodes
verify_scan_result_conatins_nodes(s3.cli('discover'), [n1, n2, n3])
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Scan by the nodes
# Scan by n1 (15.4 only), expect to see only n3(15.4+trel)
verify_scan_result_conatins_nodes(n1.cli('discover'), [n3])
# Scan by n2 (trel only), expect to see only n3(15.4+trel)
verify_scan_result_conatins_nodes(n2.cli('discover'), [n3])
# Scan by n3 (15.4+trel), expect to see n1(15.4) and n2(trel)
verify_scan_result_conatins_nodes(n3.cli('discover'), [n1, n2])
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
@@ -75,6 +75,14 @@
*/
#define OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE 1
/**
* @def OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE
*
* Define as 1 to enable IPv6 Border Routing counters.
*
*/
#define OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE 1
/**
* @def OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
*
@@ -546,6 +554,17 @@
*/
#define OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE 1
/**
* @def OPENTHREAD_CONFIG_CLI_REGISTER_IP6_RECV_CALLBACK
*
* Define as 1 to have CLI register an IPv6 receive callback using `otIp6SetReceiveCallback()`.
*
* This is intended for testing only. Receive callback should be registered for the `otIp6GetBorderRoutingCounters()`
* to count the messages being passed to the callback.
*
*/
#define OPENTHREAD_CONFIG_CLI_REGISTER_IP6_RECV_CALLBACK 1
#if OPENTHREAD_RADIO
/**
* @def OPENTHREAD_CONFIG_MAC_SOFTWARE_ACK_TIMEOUT_ENABLE
+40 -24
View File
@@ -39,32 +39,18 @@ cleanup()
sudo rm tmp/*.flash tmp/*.data tmp/*.swap >/dev/null 2>&1
sudo rm ./*.log >/dev/null 2>&1
# Clear any wpantund instances
sudo killall wpantund >/dev/null 2>&1
if [ "$TORANJ_CLI" != 1 ]; then
# Clear any wpantund instances
sudo killall wpantund >/dev/null 2>&1
while read -r interface; do
sudo ip link delete "$interface" >/dev/null 2>&1
done < <(ifconfig 2>/dev/null | grep -o "wpan[0-9]*")
sudo ip address flush trel
while read -r interface; do
sudo ip link delete "$interface" >/dev/null 2>&1
done < <(ifconfig 2>/dev/null | grep -o "wpan[0-9]*")
fi
sleep 0.3
}
prepare_trel_link()
{
# Prepares a netif "trel" as virtual eth to use for
# testing "TREL" radio link in POSIX platform.
echo "Preparing trel netif"
sudo ip link delete trel
sudo ip link add trel type veth peer name trel-peer || die
sudo ip link set trel multicast on || die
sudo ip link set trel up || die
sudo ip link set trel-peer multicast on || die
sudo ip link set trel-peer up || die
}
run()
{
counter=0
@@ -114,11 +100,8 @@ fi
if [ "$TORANJ_RADIO" = "multi" ]; then
# Build all combinations
./build.sh "${coverage_option}" "${app_name}"-15.4 || die "${app_name}-15.4 build failed"
(cd ${top_builddir} && make clean) || die "cd and clean failed"
./build.sh "${coverage_option}" "${app_name}"-trel || die "${app_name}-trel build failed"
(cd ${top_builddir} && make clean) || die "cd and clean failed"
./build.sh "${coverage_option}" "${app_name}"-15.4+trel || die "${app_name}-15.4+trel build failed"
(cd ${top_builddir} && make clean) || die "cd and clean failed"
else
./build.sh "${coverage_option}" "${app_name}"-"${TORANJ_RADIO}" || die "build failed"
fi
@@ -126,10 +109,43 @@ fi
cleanup
if [ "$TORANJ_CLI" = 1 ]; then
if [ "$TORANJ_RADIO" = "multi" ]; then
run cli/test-700-multi-radio-join.py
run cli/test-701-multi-radio-probe.py
run cli/test-702-multi-radio-discover-by-rx.py
run cli/test-703-multi-radio-mesh-header-msg.py
run cli/test-704-multi-radio-scan.py
run cli/test-705-multi-radio-discover-scan.py
exit 0
fi
run cli/test-001-get-set.py
run cli/test-002-form.py
run cli/test-003-join.py
run cli/test-004-scan.py
run cli/test-005-traffic-router-to-child.py
run cli/test-006-traffic-multi-hop.py
run cli/test-007-off-mesh-route-traffic.py
run cli/test-008-multicast-traffic.py
run cli/test-009-router-table.py
run cli/test-010-partition-merge.py
run cli/test-011-network-data-timeout.py
run cli/test-012-reset-recovery.py
run cli/test-013-address-cache-parent-switch.py
run cli/test-014-address-resolver.py
run cli/test-015-clear-addresss-cache-for-sed.py
run cli/test-016-child-mode-change.py
run cli/test-017-network-data-versions.py
run cli/test-400-srp-client-server.py
run cli/test-601-channel-manager-channel-change.py
# Skip the "channel-select" test on a TREL only radio link, since it
# requires energy scan which is not supported in this case.
if [ "$TORANJ_RADIO" != "trel" ]; then
run cli/test-602-channel-manager-channel-select.py
fi
run cli/test-603-channel-announce-recovery.py
exit 0
fi