From 77b05ce0ab95e0a7426e4175374df6c0897dc760 Mon Sep 17 00:00:00 2001 From: Simon Lin Date: Fri, 5 Nov 2021 23:18:08 +0800 Subject: [PATCH] [dua] PBBR should resolve DUA dest for packet initiated from its MTD child (#7133) This commit fixes a bug that PBBR does not send Address Query and Backbone Query on behalf of MED Children, and add a test to make sure DUA forwarding works on PBBR: - Make sure PBBR sends Address Query and Backbone Query on behalf of MED Child - Make sure PBBR does not send Address Query when the destination is MED Child - Make sure MED Children do not send Address Query by itself - Verifiy DUA reachability between PBBR and MED Child - Make sure PBBR reduce IPv6 TTL by 1 when forwarding DUA destined messages --- src/core/backbone_router/bbr_manager.cpp | 8 +- src/core/thread/mesh_forwarder.hpp | 4 + src/core/thread/mesh_forwarder_ftd.cpp | 37 ++- .../backbone/test_dua_routing_med.py | 275 ++++++++++++++++++ tests/scripts/thread-cert/pktverify/consts.py | 2 + 5 files changed, 321 insertions(+), 5 deletions(-) create mode 100644 tests/scripts/thread-cert/backbone/test_dua_routing_med.py diff --git a/src/core/backbone_router/bbr_manager.cpp b/src/core/backbone_router/bbr_manager.cpp index f5c3c6018..4e86a10f8 100644 --- a/src/core/backbone_router/bbr_manager.cpp +++ b/src/core/backbone_router/bbr_manager.cpp @@ -514,12 +514,14 @@ bool Manager::ShouldForwardDuaToBackbone(const Ip6::Address &aAddress) VerifyOrExit(Get().IsPrimary()); VerifyOrExit(Get().IsDomainUnicast(aAddress)); + // Do not forward to Backbone if the DUA is registered on PBBR VerifyOrExit(!mNdProxyTable.IsRegistered(aAddress.GetIid())); - + // Do not forward to Backbone if the DUA belongs to a MTD Child (which may have failed in DUA registration) + VerifyOrExit(Get().FindNeighbor(aAddress) == nullptr); + // Forawrd to Backbone only if the DUA is resolved to the PBBR's RLOC16 error = Get().Resolve(aAddress, rloc16, /* aAllowAddressQuery */ false); - VerifyOrExit(error != kErrorNone || rloc16 == Get().GetRloc16()); + VerifyOrExit(error == kErrorNone && rloc16 == Get().GetRloc16()); - // TODO: check if the DUA is an address of any Child? forwardToBackbone = true; exit: diff --git a/src/core/thread/mesh_forwarder.hpp b/src/core/thread/mesh_forwarder.hpp index f6a9e89a6..2d849f7dd 100644 --- a/src/core/thread/mesh_forwarder.hpp +++ b/src/core/thread/mesh_forwarder.hpp @@ -517,6 +517,10 @@ private: uint16_t & aSourcePort, uint16_t & aDestPort); +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + otError ForwardDuaToBackboneLink(Message &aMessage, const Ip6::Address &aDst); +#endif + #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_MAC == 1) const char *MessageActionToString(MessageAction aAction, Error aError); const char *MessagePriorityToString(const Message &aMessage); diff --git a/src/core/thread/mesh_forwarder_ftd.cpp b/src/core/thread/mesh_forwarder_ftd.cpp index f6d36876e..1fab06d7d 100644 --- a/src/core/thread/mesh_forwarder_ftd.cpp +++ b/src/core/thread/mesh_forwarder_ftd.cpp @@ -160,8 +160,14 @@ void MeshForwarder::HandleResolved(const Ip6::Address &aEid, Error aError) if (aError == kErrorNone) { - mSendQueue.Enqueue(*cur); - enqueuedMessage = true; +#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + // Pass back to IPv6 layer for DUA destination resolved by Backbone Query + if (ForwardDuaToBackboneLink(*cur, ip6Dst) != kErrorNone) +#endif + { + mSendQueue.Enqueue(*cur); + enqueuedMessage = true; + } } else { @@ -175,8 +181,35 @@ void MeshForwarder::HandleResolved(const Ip6::Address &aEid, Error aError) { mScheduleTransmissionTask.Post(); } + + return; } +#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE +Error MeshForwarder::ForwardDuaToBackboneLink(Message &aMessage, const Ip6::Address &aDst) +{ + Error error = kErrorNone; + uint16_t dstRloc16; + uint8_t ttl; + + VerifyOrExit(Get().IsPrimary() && Get().IsDomainUnicast(aDst), + error = kErrorNoRoute); + + IgnoreError(Get().Resolve(aDst, dstRloc16, /* aAllowAddressQuery */ false)); + VerifyOrExit(dstRloc16 == Get().GetRloc16(), error = kErrorNoRoute); + + // Avoid decreasing TTL twice + IgnoreError(aMessage.Read(Ip6::Header::kHopLimitFieldOffset, ttl)); + ttl++; + aMessage.Write(Ip6::Header::kHopLimitFieldOffset, ttl); + + IgnoreError(Get().HandleDatagram(aMessage, nullptr, nullptr, false)); + +exit: + return error; +} +#endif + Error MeshForwarder::EvictMessage(Message::Priority aPriority) { Error error = kErrorNotFound; diff --git a/tests/scripts/thread-cert/backbone/test_dua_routing_med.py b/tests/scripts/thread-cert/backbone/test_dua_routing_med.py new file mode 100644 index 000000000..d6a5fc79b --- /dev/null +++ b/tests/scripts/thread-cert/backbone/test_dua_routing_med.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2021, 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 sets DUA routes correctly. +# +import ipaddress +import unittest + +import config +import thread_cert +from pktverify.consts import ADDR_QRY_URI, BACKBONE_QUERY_URI, BACKBONE_ANSWER_URI, ADDR_NTF_URI +from pktverify.packet_verifier import PacketVerifier + +# Use two channels +CH1 = 11 +CH2 = 22 + +PBBR = 1 +ROUTER1 = 2 +MED1 = 3 +HOST = 4 +PBBR2 = 5 +MED2 = 6 + +REREG_DELAY = 5 # Seconds +MLR_TIMEOUT = 300 # Seconds +WAIT_REDUNDANCE = 3 + + +class TestDuaRoutingMed(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + + # Topology: + # ------(eth)---------------------- + # | | | + # PBBR HOST PBBR2 + # / CH1\ | CH2 + # MED1 ROUTER1 MED2 + # + # PBBR2 is in the secondary channel + # + TOPOLOGY = { + PBBR: { + 'name': 'PBBR', + 'allowlist': [MED1, ROUTER1], + 'is_otbr': True, + 'version': '1.2', + 'channel': CH1, + }, + ROUTER1: { + 'name': 'ROUTER1', + 'allowlist': [PBBR], + 'version': '1.2', + 'channel': CH1, + }, + MED1: { + 'name': 'MED1', + 'allowlist': [PBBR], + 'version': '1.2', + 'channel': CH1, + 'mode': 'rn', + }, + HOST: { + 'name': 'HOST', + 'is_host': True + }, + PBBR2: { + 'name': 'PBBR2', + 'is_otbr': True, + 'version': '1.2', + 'channel': CH2, + }, + MED2: { + 'name': 'MED2', + 'version': '1.2', + 'channel': CH2, + 'mode': 'rn', + }, + } + + def _bootstrap(self): + # Bring up HOST + self.nodes[HOST].start() + + # Bring up PBBR + self.nodes[PBBR].start() + self.simulator.go(5) + self.assertEqual('leader', self.nodes[PBBR].get_state()) + 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.nodes[PBBR].set_domain_prefix(config.DOMAIN_PREFIX, 'prosD') + self.simulator.go(5) + self.assertTrue(self.nodes[PBBR].is_primary_backbone_router) + self.assertIsNotNone(self.nodes[PBBR].get_ip6_address(config.ADDRESS_TYPE.DUA)) + + # Bring up ROUTER1 + self.nodes[ROUTER1].start() + self.simulator.go(5) + self.assertEqual('router', self.nodes[ROUTER1].get_state()) + self.assertIsNotNone(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.DUA)) + + # Bring up MED1 + self.nodes[MED1].start() + self.simulator.go(5) + self.assertEqual('child', self.nodes[MED1].get_state()) + self.assertIsNotNone(self.nodes[MED1].get_ip6_address(config.ADDRESS_TYPE.DUA)) + + # Bring up PBBR2 + self.nodes[PBBR2].start() + self.simulator.go(5) + self.assertEqual('leader', self.nodes[PBBR2].get_state()) + self.wait_node_state(PBBR2, 'leader', 5) + + self.nodes[PBBR2].set_backbone_router(reg_delay=REREG_DELAY, mlr_timeout=MLR_TIMEOUT) + self.nodes[PBBR2].enable_backbone_router() + self.nodes[PBBR2].set_domain_prefix(config.DOMAIN_PREFIX, 'prosD') + self.simulator.go(5) + self.assertTrue(self.nodes[PBBR2].is_primary_backbone_router) + self.assertIsNotNone(self.nodes[PBBR2].get_ip6_address(config.ADDRESS_TYPE.DUA)) + + # Bring up MED2 + self.nodes[MED2].start() + self.simulator.go(5) + self.assertEqual('child', self.nodes[MED2].get_state()) + self.assertIsNotNone(self.nodes[MED2].get_ip6_address(config.ADDRESS_TYPE.DUA)) + + def test(self): + self._bootstrap() + + self.collect_ipaddrs() + self.collect_rloc16s() + + self.simulator.go(10) + + # PBBR2 pings MED2 + self.assertTrue(self.nodes[PBBR2].ping(self.nodes[MED2].get_ip6_address(config.ADDRESS_TYPE.DUA))) + self.simulator.go(WAIT_REDUNDANCE) + + # MED2 pings PBBR2 + self.assertTrue(self.nodes[MED2].ping(self.nodes[PBBR2].get_ip6_address(config.ADDRESS_TYPE.DUA))) + self.simulator.go(WAIT_REDUNDANCE) + + # PBBR pings PBBR2 + self.nodes[PBBR].ping(self.nodes[PBBR2].get_ip6_address(config.ADDRESS_TYPE.DUA)) + self.simulator.go(WAIT_REDUNDANCE) + + # MED1 pings ROUTER1 should succeed + self.assertTrue(self.nodes[MED1].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.DUA))) + self.simulator.go(WAIT_REDUNDANCE) + + # MED1 pings MED2 which should succeed + MED2_DUA = self.nodes[MED2].get_ip6_address(config.ADDRESS_TYPE.DUA) + self.assertTrue(self.nodes[MED1].ping(MED2_DUA)) + self.simulator.go(WAIT_REDUNDANCE) + + def _get_mliid(self, nodeid: int) -> str: + mleid = self.nodes[nodeid].get_ip6_address(config.ADDRESS_TYPE.ML_EID) + mliid = ipaddress.IPv6Address(mleid).packed[-8:] + return ''.join(['%02x' % b for b in mliid]) + + def verify(self, pv: PacketVerifier): + pkts = pv.pkts + pv.add_common_vars() + pv.summary.show() + + PBBR = pv.vars['PBBR'] + ROUTER1 = pv.vars['ROUTER1'] + PBBR2 = pv.vars['PBBR2'] + MED1 = pv.vars['MED1'] + MED2 = pv.vars['MED2'] + MED1_DUA = pv.vars['MED1_DUA'] + ROUTER1_DUA = pv.vars['ROUTER1_DUA'] + PBBR_DUA = pv.vars['PBBR_DUA'] + PBBR2_DUA = pv.vars['PBBR2_DUA'] + MED2_DUA = pv.vars['MED2_DUA'] + PBBR_ETH = pv.vars['PBBR_ETH'] + PBBR2_ETH = pv.vars['PBBR2_ETH'] + + # PBBR should never send Address Query for MED1 (Child) + pkts.filter_wpan_src64(PBBR).filter_RLARMA().filter_coap_request(ADDR_QRY_URI) \ + .filter('thread_address.tlv.target_eid == {eid}', eid=MED1_DUA) \ + .must_not_next() + + # PBBR2 should never send Address Query for MED2 (Child) + pkts.filter_wpan_src64(PBBR2).filter_RLARMA().filter_coap_request(ADDR_QRY_URI) \ + .filter('thread_address.tlv.target_eid == {eid}', eid=MED2_DUA) \ + .must_not_next() + + # MEDs should never send Address Query + pkts.filter_wpan_src64(MED1).filter_RLARMA().filter_coap_request(ADDR_QRY_URI).must_not_next() + pkts.filter_wpan_src64(MED2).filter_RLARMA().filter_coap_request(ADDR_QRY_URI).must_not_next() + + # PBBR pings PBBR2 should succeed + ping_id = pkts.filter_ipv6_src_dst( + PBBR_DUA, PBBR2_DUA).filter_eth_src(PBBR_ETH).filter_ping_request().must_next().icmpv6.echo.identifier + pkts.filter_ipv6_src_dst(PBBR2_DUA, + PBBR_DUA).filter_eth_src(PBBR2_ETH).filter_ping_reply(identifier=ping_id).must_next() + + # MED1 pings ROUTER1 + ping_request_pkts = pkts.filter_ipv6_src_dst(MED1_DUA, ROUTER1_DUA) + # MED1 sends the Ping Request + ping_pkt = ping_request_pkts.filter_wpan_src64(MED1).filter_ping_request().must_next() + ping_id = ping_pkt.icmpv6.echo.identifier + # PBBR sends Address Query for ROUTER1_DUA + pkts.filter_wpan_src64(PBBR).filter_RLARMA().filter_coap_request(ADDR_QRY_URI) \ + .filter('thread_address.tlv.target_eid == {eid}', eid=ROUTER1_DUA) \ + .must_next() + # PBBR sends Backbone Query for ROUTER1_DUA + pkts.filter_eth_src(PBBR_ETH).filter_coap_request(BACKBONE_QUERY_URI) \ + .filter('thread_bl.tlv.target_eid == {eid}', eid=ROUTER1_DUA) \ + .must_next() + # ROUTER1 sends Address Answer for ROUTER1_DUA + pkts.filter_wpan_src64(ROUTER1).filter_coap_request(ADDR_NTF_URI, confirmable=True) \ + .filter('thread_address.tlv.target_eid == {eid}', eid=ROUTER1_DUA) \ + .must_next() + # PBBR forwards Ping Request to ROUTER1, decreasing TTL by 1 + ping_request_pkts.filter_wpan_src64(PBBR).filter_ping_request(identifier=ping_id).must_next().must_verify( + 'ipv6.hlim == {hlim}', hlim=ping_pkt.ipv6.hlim - 1) + + # MED1 pings MED2 + # Verify Ping Request: MED1 -> PBBR -> PBBR2 -> MED2 + ping_request_pkts = pkts.filter_ipv6_src_dst(MED1_DUA, MED2_DUA) + # MED1 sends the Ping Request + ping_pkt = ping_request_pkts.filter_wpan_src64(MED1).filter_ping_request().must_next() + ping_id = ping_pkt.icmpv6.echo.identifier + # PBBR sends Address Query for MED2_DUA + pkts.filter_wpan_src64(PBBR).filter_RLARMA().filter_coap_request(ADDR_QRY_URI) \ + .filter('thread_address.tlv.target_eid == {eid}', eid=MED2_DUA) \ + .must_next() + # PBBR sends Backbone Query for MED2_DUA + pkts.filter_eth_src(PBBR_ETH).filter_coap_request(BACKBONE_QUERY_URI) \ + .filter('thread_bl.tlv.target_eid == {eid}', eid=MED2_DUA) \ + .must_next() + # PBBR2 sends Backbone Answer for MED2_DUA + pkts.filter_eth_src(PBBR2_ETH).filter_coap_request(BACKBONE_ANSWER_URI, confirmable=True) \ + .filter('thread_bl.tlv.target_eid == {eid}', eid=MED2_DUA) \ + .must_next() + # PBBR forwards Ping Request to Backbone link, decreasing TTL by 1 + ping_request_pkts.filter_eth_src(PBBR_ETH).filter_ping_request(identifier=ping_id).must_next().must_verify( + 'ipv6.hlim == {hlim}', hlim=ping_pkt.ipv6.hlim - 1) + ping_request_pkts.filter_wpan_src64(PBBR2).filter_ping_request(identifier=ping_id).must_next() + # Verify Ping Reply: MED2 -> PBBR2 -> PBBR -> MED1 + ping_reply_pkts = pkts.filter_ipv6_src_dst(MED2_DUA, MED1_DUA).filter_ping_reply(identifier=ping_id) + ping_reply_pkts.filter_eth_src(PBBR2_ETH).must_next() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/pktverify/consts.py b/tests/scripts/thread-cert/pktverify/consts.py index 4266ad9e9..efdad09dc 100644 --- a/tests/scripts/thread-cert/pktverify/consts.py +++ b/tests/scripts/thread-cert/pktverify/consts.py @@ -127,6 +127,8 @@ LEAD_PET_URI = '/c/lp' LEAD_KA_URI = '/c/la' DIAG_GET_QRY_URI = '/d/dq' DIAG_GET_ANS_URI = '/d/da' +BACKBONE_QUERY_URI = '/b/bq' +BACKBONE_ANSWER_URI = '/b/ba' # ADDR SOL Status ADDR_SOL_SUCCESS = 0