From 809947e1f9912832f2a217082cc537d4cf88f351 Mon Sep 17 00:00:00 2001 From: Simon Lin Date: Wed, 30 Sep 2020 13:37:33 +0800 Subject: [PATCH] [mlr] send BMLR.ntf on Backbone link (#5388) This commit enhances Backbone Router to send BMLR.ntf on the backbone link for Multicast Listeners. - Send BMLR.ntf to the Backbone link - Add Backbone test with packet verification - Includes some revision to the Thread 1.2 Backbone CI scripts to make it more stable --- src/core/backbone_router/bbr_manager.cpp | 68 +++++- src/core/backbone_router/bbr_manager.hpp | 3 + src/core/thread/mle_types.hpp | 1 + src/core/thread/uri_paths.cpp | 1 + src/core/thread/uri_paths.hpp | 1 + src/posix/platform/backbone.cpp | 5 +- .../scripts/thread-cert/backbone/test_bmlr.py | 197 ++++++++++++++++++ tests/scripts/thread-cert/config.py | 1 + tests/scripts/thread-cert/node.py | 21 +- tests/scripts/thread-cert/pcap.py | 3 +- .../thread-cert/pktverify/layer_fields.py | 5 +- tests/scripts/thread-cert/run_bbr_tests.py | 3 +- tests/scripts/thread-cert/thread_cert.py | 16 +- 13 files changed, 301 insertions(+), 24 deletions(-) create mode 100644 tests/scripts/thread-cert/backbone/test_bmlr.py diff --git a/src/core/backbone_router/bbr_manager.cpp b/src/core/backbone_router/bbr_manager.cpp index 658f14ed5..427a0af69 100644 --- a/src/core/backbone_router/bbr_manager.cpp +++ b/src/core/backbone_router/bbr_manager.cpp @@ -129,14 +129,14 @@ void Manager::HandleMulticastListenerRegistration(const Coap::Message &aMessage, uint16_t addressesOffset, addressesLength; Ip6::Address address; - Ip6::Address failedAddresses[kIPv6AddressesNumMax]; - uint8_t failedAddressNum = 0; + Ip6::Address addresses[kIPv6AddressesNumMax]; + uint8_t failedAddressNum = 0; + uint8_t successAddressNum = 0; TimeMilli expireTime; - - uint32_t timeout; - uint16_t commissionerSessionId; - bool hasCommissionerSessionIdTlv = false; - bool processTimeoutTlv = false; + uint32_t timeout; + uint16_t commissionerSessionId; + bool hasCommissionerSessionIdTlv = false; + bool processTimeoutTlv = false; VerifyOrExit(aMessage.IsConfirmablePostRequest(), error = OT_ERROR_PARSE); @@ -237,7 +237,12 @@ void Manager::HandleMulticastListenerRegistration(const Coap::Message &aMessage, if (failed) { - failedAddresses[failedAddressNum++] = address; + addresses[failedAddressNum++] = address; + } + else + { + // Put successfully registered addresses at the end of `addresses`. + addresses[kIPv6AddressesNumMax - (++successAddressNum)] = address; } } } @@ -245,8 +250,13 @@ void Manager::HandleMulticastListenerRegistration(const Coap::Message &aMessage, exit: if (error == OT_ERROR_NONE) { - SendMulticastListenerRegistrationResponse(aMessage, aMessageInfo, status, failedAddresses, failedAddressNum); - // TODO: (MLR) send BMLR.req + SendMulticastListenerRegistrationResponse(aMessage, aMessageInfo, status, addresses, failedAddressNum); + } + + if (successAddressNum > 0) + { + SendBackboneMulticastListenerRegistration(&addresses[kIPv6AddressesNumMax - successAddressNum], + successAddressNum, timeout); } } @@ -287,6 +297,44 @@ exit: otLogInfoBbr("Sent MLR.rsp (status=%d): %s", aStatus, otThreadErrorToString(error)); } +void Manager::SendBackboneMulticastListenerRegistration(const Ip6::Address *aAddresses, + uint8_t aAddressNum, + uint32_t aTimeout) +{ + otError error = OT_ERROR_NONE; + Coap::Message * message = nullptr; + Ip6::MessageInfo messageInfo; + IPv6AddressesTlv addressesTlv; + BackboneTmfAgent &backboneTmf = Get(); + + OT_ASSERT(aAddressNum >= kIPv6AddressesNumMin && aAddressNum <= kIPv6AddressesNumMax); + + VerifyOrExit((message = backboneTmf.NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS); + + SuccessOrExit(error = message->InitAsNonConfirmablePost(UriPath::kBackboneMlr)); + SuccessOrExit(error = message->SetPayloadMarker()); + + addressesTlv.Init(); + addressesTlv.SetLength(sizeof(Ip6::Address) * aAddressNum); + SuccessOrExit(error = message->Append(&addressesTlv, sizeof(addressesTlv))); + SuccessOrExit(error = message->Append(aAddresses, sizeof(Ip6::Address) * aAddressNum)); + + SuccessOrExit(error = ThreadTlv::AppendUint32Tlv(*message, ThreadTlv::kTimeout, aTimeout)); + + messageInfo.SetPeerAddr(Get().GetAllNetworkBackboneRoutersAddress()); + messageInfo.SetPeerPort(BackboneRouter::kBackboneUdpPort); // TODO: Provide API for configuring Backbone COAP port. + + messageInfo.SetSockAddr(Get().GetMeshLocal16()); + messageInfo.SetHopLimit(Mle::kDefaultBackboneHoplimit); + messageInfo.SetIsHostInterface(true); + + SuccessOrExit(error = backboneTmf.SendMessage(*message, messageInfo, nullptr, nullptr)); + +exit: + FreeMessageOnError(message, error); + otLogInfoBbr("Sent BMLR.ntf: %s", otThreadErrorToString(error)); +} + void Manager::HandleDuaRegistration(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { otError error = OT_ERROR_NONE; diff --git a/src/core/backbone_router/bbr_manager.hpp b/src/core/backbone_router/bbr_manager.hpp index 09d649adc..fa97a1d4d 100644 --- a/src/core/backbone_router/bbr_manager.hpp +++ b/src/core/backbone_router/bbr_manager.hpp @@ -151,6 +151,9 @@ private: ThreadStatusTlv::MlrStatus aStatus, Ip6::Address * aFailedAddresses, uint8_t aFailedAddressNum); + void SendBackboneMulticastListenerRegistration(const Ip6::Address *aAddresses, + uint8_t aAddressNum, + uint32_t aTimeout); static void HandleDuaRegistration(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) { diff --git a/src/core/thread/mle_types.hpp b/src/core/thread/mle_types.hpp index 82f64817a..f0cf72666 100644 --- a/src/core/thread/mle_types.hpp +++ b/src/core/thread/mle_types.hpp @@ -269,6 +269,7 @@ enum kDuaDadPeriod = 100, ///< In seconds. Time period after which the address ///< becomes "Preferred" if no duplicate address error. kTimeSinceLastTransactionMax = 10 * 86400, ///< In seconds (10 days). + kDefaultBackboneHoplimit = 1, ///< default hoplimit for Thread Backbone Link Protocol messages }; static_assert(kMlrTimeoutDefault >= kMlrTimeoutMin && kMlrTimeoutDefault <= kMlrTimeoutMax, diff --git a/src/core/thread/uri_paths.cpp b/src/core/thread/uri_paths.cpp index 51bec71b8..f5a794da5 100644 --- a/src/core/thread/uri_paths.cpp +++ b/src/core/thread/uri_paths.cpp @@ -72,5 +72,6 @@ const char UriPath::kDuaRegistrationRequest[] = "n/dr"; const char UriPath::kDuaRegistrationNotify[] = "n/dn"; const char UriPath::kBackboneQuery[] = "b/bq"; const char UriPath::kBackboneAnswer[] = "b/ba"; +const char UriPath::kBackboneMlr[] = "b/bmr"; } // namespace ot diff --git a/src/core/thread/uri_paths.hpp b/src/core/thread/uri_paths.hpp index 0d08e5de9..4450d7dd7 100644 --- a/src/core/thread/uri_paths.hpp +++ b/src/core/thread/uri_paths.hpp @@ -82,6 +82,7 @@ struct UriPath static const char kDuaRegistrationNotify[]; ///< The URI Path for DUA Registration Notification ("n/dn"). static const char kBackboneQuery[]; ///< The URI Path for Backbone Query ("b/bq"). static const char kBackboneAnswer[]; ///< The URI Path for Backbone Answer ("b/ba"). + static const char kBackboneMlr[]; ///< The URI Path for Backbone Multicast Listener Report ("b/bmr"). }; } // namespace ot diff --git a/src/posix/platform/backbone.cpp b/src/posix/platform/backbone.cpp index d3d7fb8bc..7c62078f6 100644 --- a/src/posix/platform/backbone.cpp +++ b/src/posix/platform/backbone.cpp @@ -45,13 +45,16 @@ void platformBackboneInit(otInstance *aInstance, const char *aInterfaceName) { OT_UNUSED_VARIABLE(aInstance); - VerifyOrExit(aInterfaceName != nullptr, OT_NOOP); + VerifyOrExit(aInterfaceName != nullptr, otLogInfoPlat("Backbone interface is not configured")); VerifyOrDie(strnlen(aInterfaceName, IFNAMSIZ) <= IFNAMSIZ - 1, OT_EXIT_INVALID_ARGUMENTS); strcpy(gBackboneNetifName, aInterfaceName); gBackboneNetifIndex = if_nametoindex(gBackboneNetifName); VerifyOrDie(gBackboneNetifIndex > 0, OT_EXIT_FAILURE); + + otLogInfoPlat("Backbone interface is configured to %s (%d)", gBackboneNetifName, gBackboneNetifIndex); + exit: return; } diff --git a/tests/scripts/thread-cert/backbone/test_bmlr.py b/tests/scripts/thread-cert/backbone/test_bmlr.py new file mode 100644 index 000000000..eb50e2504 --- /dev/null +++ b/tests/scripts/thread-cert/backbone/test_bmlr.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, 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. +# +# This test verifies that PBBR sends BMLR.ntf correctly when multicast addresses are registered. +# +# Topology: +# ---- -(eth)------- +# | | +# PBBR----SBBR +# \ / +# Router1---Commissioner +# + +import unittest + +import thread_cert +from pktverify.packet_verifier import PacketVerifier + +PBBR = 1 +SBBR = 2 +ROUTER1 = 3 +COMMISSIONER = 4 + +REREG_DELAY = 4 # Seconds +MLR_TIMEOUT = 300 # Seconds +CUSTOM_MLR_TIMEOUT = 1000 # Seconds + +MA1 = 'ff04::1' +MA2 = 'ff04::2' +MA3 = 'ff04::3' +MA4 = 'ff04::4' +MA5 = 'ff04::5' + + +class BBR_5_11_01(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + + TOPOLOGY = { + PBBR: { + 'name': 'PBBR', + 'allowlist': [SBBR, ROUTER1], + 'is_otbr': True, + 'version': '1.2', + 'router_selection_jitter': 1, + }, + SBBR: { + 'name': 'SBBR', + 'allowlist': [PBBR, ROUTER1], + 'is_otbr': True, + 'version': '1.2', + 'router_selection_jitter': 1, + }, + ROUTER1: { + 'name': 'ROUTER1', + 'allowlist': [PBBR, SBBR, COMMISSIONER], + 'version': '1.2', + 'router_selection_jitter': 1, + }, + COMMISSIONER: { + 'name': 'COMMISSIONER', + 'allowlist': [ROUTER1], + 'version': '1.2', + 'router_selection_jitter': 1, + } + } + + def test(self): + self.nodes[PBBR].start() + self.wait_node_state(PBBR, 'leader', 5) + self.nodes[PBBR].set_backbone_router(reg_delay=REREG_DELAY, mlr_timeout=MLR_TIMEOUT) + self.nodes[PBBR].enable_backbone_router() + self.wait_until(lambda: self.nodes[PBBR].is_primary_backbone_router, 5) + + self.nodes[SBBR].start() + self.wait_node_state(SBBR, 'router', 5) + self.nodes[SBBR].set_backbone_router(reg_delay=REREG_DELAY, mlr_timeout=MLR_TIMEOUT) + self.nodes[SBBR].enable_backbone_router() + self.simulator.go(5) + self.assertFalse(self.nodes[SBBR].is_primary_backbone_router) + + self.nodes[ROUTER1].start() + self.wait_node_state(ROUTER1, 'router', 5) + + self.nodes[COMMISSIONER].start() + self.wait_node_state(COMMISSIONER, 'router', 5) + + self.nodes[COMMISSIONER].commissioner_start() + self.simulator.go(10) + self.assertEqual('active', self.nodes[COMMISSIONER].commissioner_state()) + + self.nodes[PBBR].add_ipmaddr(MA1) + self.simulator.go(REREG_DELAY) + self.nodes[ROUTER1].add_ipmaddr(MA2) + self.simulator.go(REREG_DELAY) + + # Commissioner registers MA3 with default timeout + self.assertEqual((0, []), self.nodes[COMMISSIONER].register_multicast_listener(MA3, timeout=None)) + # Commissioner registers MA4 with a custom timeout + self.assertEqual((0, []), self.nodes[COMMISSIONER].register_multicast_listener(MA4, + timeout=CUSTOM_MLR_TIMEOUT)) + # Commissioner unregisters MA5 + self.assertEqual((0, []), self.nodes[COMMISSIONER].register_multicast_listener(MA5, timeout=0)) + + self.collect_ipaddrs() + self.collect_rloc16s() + + def verify(self, pv: PacketVerifier): + pkts = pv.pkts + pv.add_common_vars() + pv.summary.show() + pv.verify_attached('ROUTER1') + + ROUTER1 = pv.vars['ROUTER1'] + COMMISSIONER = pv.vars['COMMISSIONER'] + PBBR_ETH = pv.vars['PBBR_ETH'] + SBBR_ETH = pv.vars['SBBR_ETH'] + + # Verify SBBR must not send `/b/bmr` during the test. + pkts.filter_eth_src(SBBR_ETH).filter_coap_request('/b/bmr').must_not_next() + + # Verify PBBR sends `/b/bmr` on the Backbone link for MA1 with default timeout. + pkts.filter_eth_src(PBBR_ETH).filter_coap_request('/b/bmr').must_next().must_verify(f""" + thread_meshcop.tlv.ipv6_addr == ['{MA1}'] + and thread_bl.tlv.timeout == {MLR_TIMEOUT} + """) + + # Router registers MA2 with default timeout + pkts.filter_wpan_src64(ROUTER1).filter_coap_request('/n/mr').must_next().must_verify(f""" + thread_meshcop.tlv.ipv6_addr == ['{MA2}'] + and thread_bl.tlv.timeout is null + """) + # Verify PBBR sends `/b/bmr` on the Backbone link for MA2 with default timeout. + pkts.filter_eth_src(PBBR_ETH).filter_coap_request('/b/bmr').must_next().must_verify(f""" + thread_meshcop.tlv.ipv6_addr == ['{MA2}'] + and thread_bl.tlv.timeout == {MLR_TIMEOUT} + """) + + # Commissioner registers MA3 with deafult timeout + pkts.filter_wpan_src64(COMMISSIONER).filter_coap_request('/n/mr').must_next().must_verify(f""" + thread_meshcop.tlv.ipv6_addr == ['{MA3}'] + and thread_bl.tlv.timeout is null + """) + # Verify PBBR sends `/b/bmr` on the Backbone link for MA3 with default timeout. + pkts.filter_eth_src(PBBR_ETH).filter_coap_request('/b/bmr').must_next().must_verify(f""" + thread_meshcop.tlv.ipv6_addr == ['{MA3}'] + and thread_bl.tlv.timeout == {MLR_TIMEOUT} + """) + + # Commissioner registers MA4 with custom timeout + pkts.filter_wpan_src64(COMMISSIONER).filter_coap_request('/n/mr').must_next().must_verify(f""" + thread_meshcop.tlv.ipv6_addr == ['{MA4}'] + and thread_nm.tlv.timeout == {CUSTOM_MLR_TIMEOUT} + """) + # Verify PBBR sends `/b/bmr` on the Backbone link for MA4 with custom timeout. + pkts.filter_eth_src(PBBR_ETH).filter_coap_request('/b/bmr').must_next().must_verify(f""" + thread_meshcop.tlv.ipv6_addr == ['{MA4}'] + and thread_bl.tlv.timeout == {CUSTOM_MLR_TIMEOUT} + """) + + # Commissioner unregisters MA5 + pkts.filter_wpan_src64(COMMISSIONER).filter_coap_request('/n/mr').must_next().must_verify(f""" + thread_meshcop.tlv.ipv6_addr == ['{MA5}'] + and thread_nm.tlv.timeout == 0 + """) + # Verify PBBR not sends `/b/bmr` on the Backbone link for MA5. + pkts.filter_eth_src(PBBR_ETH).filter_coap_request('/b/bmr').filter(f""" + thread_meshcop.tlv.ipv6_addr == ['{MA5}'] + """).must_not_next() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/config.py b/tests/scripts/thread-cert/config.py index ce182541a..2e032f611 100644 --- a/tests/scripts/thread-cert/config.py +++ b/tests/scripts/thread-cert/config.py @@ -67,6 +67,7 @@ PORT_OFFSET = int(os.getenv('PORT_OFFSET', '0')) BACKBONE_PREFIX = f'{0x9100 + PORT_OFFSET:04x}::/64' BACKBONE_PREFIX_REGEX_PATTERN = f'^{0x9100 + PORT_OFFSET:04x}:' BACKBONE_DOCKER_NETWORK_NAME = f'backbone{PORT_OFFSET}' +BACKBONE_IFNAME = 'eth0' OTBR_DOCKER_IMAGE = os.getenv('OTBR_DOCKER_IMAGE', 'otbr-ot12-backbone-ci') OTBR_DOCKER_NAME_PREFIX = f'otbr_{PORT_OFFSET}_' diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 97d1e5f47..a2ea0a91a 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -105,13 +105,17 @@ class OtbrDocker: '--cap-add=NET_ADMIN', '--volume', f'{self._rcp_device}:/dev/ttyUSB0', + '-v', + '/tmp/codecov.bash:/tmp/codecov.bash', config.OTBR_DOCKER_IMAGE, + '-B', + config.BACKBONE_IFNAME, ], stdin=subprocess.DEVNULL, stdout=sys.stdout, stderr=sys.stderr) - launch_docker_deadline = time.time() + 60 + launch_docker_deadline = time.time() + 300 launch_ok = False while time.time() < launch_docker_deadline: @@ -121,7 +125,7 @@ class OtbrDocker: logging.info("OTBR Docker %s Is Ready!", self._docker_name) break except subprocess.CalledProcessError: - time.sleep(0.2) + time.sleep(5) continue assert launch_ok @@ -155,8 +159,7 @@ class OtbrDocker: if COVERAGE or OTBR_COVERAGE: self.bash('service otbr-agent stop') - self.bash('curl https://codecov.io/bash -o codecov_bash --retry 5') - codecov_cmd = 'bash codecov_bash -Z' + codecov_cmd = 'bash /tmp/codecov.bash -Z' # Upload OTBR code coverage if OTBR_COVERAGE=1, otherwise OpenThread code coverage. if not OTBR_COVERAGE: codecov_cmd += ' -R third_party/openthread/repo' @@ -381,9 +384,6 @@ class OtCli: serialPort = '/dev/ttyUSB%d' % ((nodeid - 1) * 2) self.pexpect = fdpexpect.fdspawn(os.open(serialPort, os.O_RDWR | os.O_NONBLOCK | os.O_NOCTTY)) - def __del__(self): - self.destroy() - def destroy(self): if not self._initialized: return @@ -609,6 +609,11 @@ class NodeImpl: self.send_command(cmd) self._expect('Done') + def commissioner_state(self): + states = [r'disabled', r'petitioning', r'active'] + self.send_command('commissioner state') + return self._expect_result(states) + def commissioner_add_joiner(self, addr, psk): cmd = 'commissioner joiner add %s %s' % (addr, psk) self.send_command(cmd) @@ -1904,7 +1909,7 @@ class Node(NodeImpl, OtCli): class LinuxHost(): PING_RESPONSE_PATTERN = re.compile(r'\d+ bytes from .*:.*') - ETH_DEV = 'eth0' + ETH_DEV = config.BACKBONE_IFNAME def get_ether_addrs(self): output = self.bash(f'ip -6 addr list dev {self.ETH_DEV}') diff --git a/tests/scripts/thread-cert/pcap.py b/tests/scripts/thread-cert/pcap.py index 911f96ec6..905a5dd7b 100644 --- a/tests/scripts/thread-cert/pcap.py +++ b/tests/scripts/thread-cert/pcap.py @@ -43,7 +43,6 @@ class PcapCodec(object): def __init__(self, filename): self._pcap_file = open('%s.pcap' % filename, 'wb') self._pcap_file.write(self.encode_header()) - self._epoch = time.time() def encode_header(self): """ Returns a pcap file header. """ @@ -68,7 +67,7 @@ class PcapCodec(object): def _get_timestamp(self): """ Returns the internal timestamp. """ - timestamp = time.time() - self._epoch + timestamp = time.time() timestamp_sec = int(timestamp) timestamp_usec = int((timestamp - timestamp_sec) * 1000000) return timestamp_sec, timestamp_usec diff --git a/tests/scripts/thread-cert/pktverify/layer_fields.py b/tests/scripts/thread-cert/pktverify/layer_fields.py index 86bcc4881..72b62f919 100644 --- a/tests/scripts/thread-cert/pktverify/layer_fields.py +++ b/tests/scripts/thread-cert/pktverify/layer_fields.py @@ -510,11 +510,13 @@ _LAYER_FIELDS = { 'thread_bl.tlv.target_eid': _ipv6_addr, 'thread_bl.tlv.ml_eid': _ext_addr, 'thread_bl.tlv.last_transaction_time': _auto, + 'thread_bl.tlv.timeout': _auto, # THEAD NM 'thread_nm.tlv.type': _list(_auto), 'thread_nm.tlv.ml_eid': _ext_addr, 'thread_nm.tlv.target_eid': _ipv6_addr, 'thread_nm.tlv.status': _auto, + 'thread_nm.tlv.timeout': _auto, # thread_meshcop is not a real layer 'thread_meshcop.len_size_mismatch': _str, 'thread_meshcop.tlv.type': _list(_auto), @@ -544,6 +546,7 @@ _LAYER_FIELDS = { 'thread_meshcop.tlv.unknown': _bytes, 'thread_meshcop.tlv.ba_locator': _auto, 'thread_meshcop.tlv.active_tstamp': _auto, + 'thread_meshcop.tlv.ipv6_addr': _list(_ipv6_addr), # THREAD NWD 'thread_nwd.tlv.type': _list(_auto), @@ -668,7 +671,7 @@ def check_layer_field_exists(packet, field_uri): def _get_candidate_layers(packet, layer_name): if layer_name == 'thread_meshcop': - candidate_layer_names = ['thread_meshcop', 'mle', 'coap', 'thread_bl'] + candidate_layer_names = ['thread_meshcop', 'mle', 'coap', 'thread_bl', 'thread_nm'] elif layer_name == 'thread_nwd': candidate_layer_names = ['mle', 'thread_address'] elif layer_name == 'wpan': diff --git a/tests/scripts/thread-cert/run_bbr_tests.py b/tests/scripts/thread-cert/run_bbr_tests.py index c9255cfe3..4701f86df 100644 --- a/tests/scripts/thread-cert/run_bbr_tests.py +++ b/tests/scripts/thread-cert/run_bbr_tests.py @@ -86,7 +86,8 @@ def cleanup_env(): def setup_env(): bash(f'docker image inspect {config.OTBR_DOCKER_IMAGE} >/dev/null') - bash('mkdir build || true') + # Download codecov bash to be used for OTBR Dockers + bash('curl -L https://codecov.io/bash -o /tmp/codecov.bash --retry 5') def parse_args(): diff --git a/tests/scripts/thread-cert/thread_cert.py b/tests/scripts/thread-cert/thread_cert.py index e311fa8b3..1bd35248e 100644 --- a/tests/scripts/thread-cert/thread_cert.py +++ b/tests/scripts/thread-cert/thread_cert.py @@ -36,7 +36,7 @@ import sys import time import traceback import unittest -from typing import Optional +from typing import Optional, Callable import config import debug @@ -474,3 +474,17 @@ class TestCase(NcpSupportMixin, unittest.TestCase): mergecap = pvutils.which_mergecap() self.assure_run_ok(f'{mergecap} -w {merged_pcap} {thread_pcap} {backbone_pcap}', shell=True) return merged_pcap + + def wait_until(self, cond: Callable[[], bool], timeout: int, go_interval: int = 1): + while True: + self.simulator.go(go_interval) + + if cond(): + break + + timeout -= go_interval + if timeout <= 0: + raise RuntimeError(f'wait failed after {timeout} seconds') + + def wait_node_state(self, nodeid: int, state: str, timeout: int): + self.wait_until(lambda: self.nodes[nodeid].get_state() == state, timeout)