mirror of
https://github.com/espressif/openthread.git
synced 2026-09-02 15:20:07 +00:00
[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:
@@ -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
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Executable
+107
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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))
|
||||
Executable
+251
@@ -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))
|
||||
Executable
+154
@@ -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
@@ -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))
|
||||
Executable
+143
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user