From 5db938dbed179cd550530fe466df3bc4dd48b5d5 Mon Sep 17 00:00:00 2001 From: kangping Date: Fri, 30 Apr 2021 03:56:12 +0800 Subject: [PATCH] [border-router] learn RA header and parameters from infra interface (#6431) The commit enhances the Routing Manager to learn RA header and parameters from RA messages initiated from infra interface. This addresses the issue that the BR may be using different RA header against another RA daemon which is working on the same infra interface (Have BR and another RA daemon working on the same interface is common for Wi-Fi + Thread routers). We also changed to accept RS messages initiated from the infra interface: this is for case that multiple Border Routers run on the same host (as we are trying to support multiple ot-daemons on the same host). --- include/openthread/instance.h | 2 +- include/openthread/platform/infra_if.h | 3 +- .../border_router/router_advertisement.cpp | 22 ++- .../border_router/router_advertisement.hpp | 32 ++- src/core/border_router/routing_manager.cpp | 38 ++-- src/core/border_router/routing_manager.hpp | 5 + .../border_router/test_radvd_coexist.py | 187 ++++++++++++++++++ .../test_single_border_router.py | 4 +- tests/scripts/thread-cert/node.py | 23 ++- .../thread-cert/pktverify/packet_filter.py | 3 + 10 files changed, 284 insertions(+), 35 deletions(-) create mode 100755 tests/scripts/thread-cert/border_router/test_radvd_coexist.py diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 0b70d419c..500291c17 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (108) +#define OPENTHREAD_API_VERSION (109) /** * @addtogroup api-instance diff --git a/include/openthread/platform/infra_if.h b/include/openthread/platform/infra_if.h index 41b916980..7bd18e099 100644 --- a/include/openthread/platform/infra_if.h +++ b/include/openthread/platform/infra_if.h @@ -64,7 +64,8 @@ bool otPlatInfraIfHasAddress(uint32_t aInfraIfIndex, const otIp6Address *aAddres * * @param[in] aInfraIfIndex The index of the infrastructure interface this message is sent to. * @param[in] aDestAddress The destination address this message is sent to. - * @param[in] aBuffer The ICMPv6 message buffer. + * @param[in] aBuffer The ICMPv6 message buffer. The ICMPv6 checksum is left zero and the + * platform should do the checksum calculate. * @param[in] aBufferLength The length of the message buffer. * * @note Per RFC 4861, the implementation should send the message with IPv6 link-local source address diff --git a/src/core/border_router/router_advertisement.cpp b/src/core/border_router/router_advertisement.cpp index ea3d11a66..d727ce9a6 100644 --- a/src/core/border_router/router_advertisement.cpp +++ b/src/core/border_router/router_advertisement.cpp @@ -187,15 +187,25 @@ bool RouteInfoOption::IsValid(void) const (pref == OT_ROUTE_PREFERENCE_LOW || pref == OT_ROUTE_PREFERENCE_MED || pref == OT_ROUTE_PREFERENCE_HIGH); } -RouterAdvMessage::RouterAdvMessage(void) - : mReachableTime(0) - , mRetransTimer(0) +void RouterAdvMessage::SetToDefault(void) { - OT_UNUSED_VARIABLE(mReachableTime); - OT_UNUSED_VARIABLE(mRetransTimer); - mHeader.Clear(); mHeader.SetType(Ip6::Icmp::Header::kTypeRouterAdvert); + mReachableTime = 0; + mRetransTimer = 0; +} + +const RouterAdvMessage &RouterAdvMessage::operator=(const RouterAdvMessage &aOther) +{ + mHeader = aOther.mHeader; + + // Set zero value and let platform do the calculation. + mHeader.SetChecksum(0); + + mReachableTime = aOther.mReachableTime; + mRetransTimer = aOther.mRetransTimer; + + return *this; } RouterSolicitMessage::RouterSolicitMessage(void) diff --git a/src/core/border_router/router_advertisement.hpp b/src/core/border_router/router_advertisement.hpp index b8bf203e9..1ac271e5c 100644 --- a/src/core/border_router/router_advertisement.hpp +++ b/src/core/border_router/router_advertisement.hpp @@ -394,7 +394,21 @@ public: * zero router lifetime, reachable time and retransmission timer. * */ - RouterAdvMessage(void); + RouterAdvMessage(void) { SetToDefault(); } + + /** + * This method sets the RA message to default values. + * + */ + void SetToDefault(void); + + /** + * This method sets the checksum value. + * + * @param[in] aChecksum The checksum value. + * + */ + void SetChecksum(uint16_t aChecksum) { mHeader.SetChecksum(aChecksum); } /** * This method sets the Router Lifetime in seconds. @@ -409,6 +423,16 @@ public: mHeader.mData.m16[kRouteLifetimeIdx] = HostSwap16(aRouterLifetime); } + /** + * This method returns the Router Lifetime. + * + * Zero Router Lifetime means we are not a default router. + * + * @returns The router lifetime in seconds. + * + */ + uint16_t GetRouterLifetime(void) const { return HostSwap16(mHeader.mData.m16[kRouteLifetimeIdx]); } + /** * This method returns the Managed Address Configuration ('m') flag. * @@ -417,6 +441,12 @@ public: */ bool GetManagedAddrConfig(void) const { return (mHeader.mData.m8[kReservedIdx] & kManagedAddressConfigMask) != 0; } + /** + * This method overloads the assignment operator. + * + */ + const RouterAdvMessage &operator=(const RouterAdvMessage &aOther); + private: enum : uint8_t { diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index 1c2f59326..128854418 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -253,8 +253,6 @@ void RoutingManager::RecvIcmp6Message(uint32_t aInfraIfIndex, HandleRouterAdvertisement(aSrcAddress, aBuffer, aBufferLength); break; case Ip6::Icmp::Header::kTypeRouterSolicit: - // Drop Router Solicitations initiated from infra interface. - VerifyOrExit(!otPlatInfraIfHasAddress(mInfraIfIndex, &aSrcAddress), error = kErrorDrop); HandleRouterSolicit(aSrcAddress, aBuffer, aBufferLength); break; default: @@ -623,6 +621,8 @@ void RoutingManager::StartRouterSolicitationDelay(void) uint32_t randomDelay; + mRouterAdvMessage.SetToDefault(); + mRouterSolicitCount = 0; static_assert(kMaxRtrSolicitationDelay > 0, "invalid maximum Router Solicitation delay"); @@ -655,18 +655,12 @@ void RoutingManager::SendRouterAdvertisement(const Ip6::Prefix *aNewOmrPrefixes, uint8_t aNewOmrPrefixNum, const Ip6::Prefix *aNewOnLinkPrefix) { - uint8_t buffer[kMaxRouterAdvMessageLength]; - uint16_t bufferLength = 0; - RouterAdv::RouterAdvMessage routerAdv; + uint8_t buffer[kMaxRouterAdvMessageLength]; + uint16_t bufferLength = 0; - // Set zero Router Lifetime to indicate that the Border Router is not the default - // router for infra link so that hosts on infra link will not create default route - // to the Border Router when received RA. - routerAdv.SetRouterLifetime(0); - - OT_ASSERT(bufferLength + sizeof(routerAdv) <= sizeof(buffer)); - memcpy(buffer, &routerAdv, sizeof(routerAdv)); - bufferLength += sizeof(routerAdv); + static_assert(sizeof(mRouterAdvMessage) <= sizeof(buffer)); + memcpy(buffer, &mRouterAdvMessage, sizeof(mRouterAdvMessage)); + bufferLength += sizeof(mRouterAdvMessage); if (aNewOnLinkPrefix != nullptr) { @@ -750,7 +744,7 @@ void RoutingManager::SendRouterAdvertisement(const Ip6::Prefix *aNewOmrPrefixes, } // Send the message only when there are options. - if (bufferLength > sizeof(routerAdv)) + if (bufferLength > sizeof(mRouterAdvMessage)) { Error error; Ip6::Address destAddress; @@ -961,6 +955,22 @@ void RoutingManager::HandleRouterAdvertisement(const Ip6::Address &aSrcAddress, } } + // Remember the header and parameters of RA messages which are + // initiated from the infra interface. + if (otPlatInfraIfHasAddress(mInfraIfIndex, &aSrcAddress)) + { + if (routerAdvMessage->GetRouterLifetime() == 0) + { + mRouterAdvMessage.SetToDefault(); + } + else + { + mRouterAdvMessage = *routerAdvMessage; + // TODO: add a timer for invalidating the learned RA parameters + // for cases that the other RA daemon crashed or is force killed. + } + } + if (needReevaluate) { StartRoutingPolicyEvaluationDelay(); diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index 6269495f6..e70ed3400 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -282,6 +282,11 @@ private: ExternalPrefix mDiscoveredPrefixes[kMaxDiscoveredPrefixNum]; uint8_t mDiscoveredPrefixNum; + // The RA header and parameters for the infra interface. + // This value is initialized with `RouterAdvMessage::SetToDefault` + // and updated with RA messages initiated from infra interface. + RouterAdv::RouterAdvMessage mRouterAdvMessage; + TimerMilli mDiscoveredPrefixInvalidTimer; TimerMilli mRouterAdvertisementTimer; diff --git a/tests/scripts/thread-cert/border_router/test_radvd_coexist.py b/tests/scripts/thread-cert/border_router/test_radvd_coexist.py new file mode 100755 index 000000000..3cf391dab --- /dev/null +++ b/tests/scripts/thread-cert/border_router/test_radvd_coexist.py @@ -0,0 +1,187 @@ +#!/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. +# +import logging +import unittest + +import config +import pktverify +from pktverify import packet_verifier, packet_filter, consts +import thread_cert + +# Test description: +# This test verifies that the Border Router will use the same RA parameters +# when there is already a running radvd service on the same host. +# +# Topology: +# ----------------(eth)-------------------- +# | | +# BR (Leader) HOST +# | +# ROUTER +# + +BR = 1 +ROUTER = 2 +HOST = 3 + +ON_LINK_PREFIX = 'fd00::/64' + + +class SingleBorderRouter(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + + TOPOLOGY = { + BR: { + 'name': 'BR', + 'allowlist': [ROUTER], + 'is_otbr': True, + 'version': '1.2', + }, + ROUTER: { + 'name': 'Router', + 'allowlist': [BR], + 'version': '1.2', + 'router_selection_jitter': 1, + }, + HOST: { + 'name': 'Host', + 'is_host': True + }, + } + + def test(self): + br = self.nodes[BR] + router = self.nodes[ROUTER] + host = self.nodes[HOST] + + host.start(start_radvd=False) + self.simulator.go(5) + + br.start_radvd_service(prefix=ON_LINK_PREFIX, slaac=True) + self.simulator.go(5) + + br.start() + self.simulator.go(5) + self.assertEqual('leader', br.get_state()) + + router.start() + self.simulator.go(5) + self.assertEqual('router', router.get_state()) + + self.simulator.go(10) + self.collect_ipaddrs() + + logging.info("BR addrs: %r", br.get_addrs()) + logging.info("ROUTER addrs: %r", router.get_addrs()) + logging.info("HOST addrs: %r", host.get_addrs()) + + self.assertEqual(len(br.get_prefixes()), 1) + self.assertEqual(len(router.get_prefixes()), 1) + self.assertEqual(len(br.get_routes()), 1) + self.assertEqual(len(router.get_routes()), 1) + + self.assertEqual(len(br.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1) + self.assertEqual(len(router.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1) + self.assertEqual(len(host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)), 1) + + self.assertTrue(router.ping(host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) + self.assertTrue(host.ping(router.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], backbone=True)) + + # Stop the radvd service and wait for the Border Router + # to start adverting on-link prefix on its own. + br.stop_radvd_service() + self.simulator.go(15) + + self.assertEqual(len(br.get_prefixes()), 1) + self.assertEqual(len(router.get_prefixes()), 1) + self.assertEqual(len(br.get_routes()), 1) + self.assertEqual(len(router.get_routes()), 1) + + self.assertEqual(len(br.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1) + self.assertEqual(len(router.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1) + self.assertEqual(len(host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)), 1) + + self.assertTrue(router.ping(host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) + self.assertTrue(host.ping(router.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], backbone=True)) + + self.collect_ipaddrs() + self.collect_rloc16s() + self.collect_rlocs() + self.collect_extra_vars() + + def verify(self, pv: pktverify.packet_verifier.PacketVerifier): + RA_OPT_TYPE_PIO = 3 + RA_OPT_TYPE_RIO = 24 + + pkts = pv.pkts + vars = pv.vars + pv.summary.show() + logging.info(f'vars = {vars}') + + # Ensure the topology is formed correctly + pv.verify_attached('Router', 'BR') + + # verify that radvd sends RA messages with PIO. + radvd_ra = pkts.filter_eth_src(vars['BR_ETH']) \ + .filter_icmpv6_nd_ra() \ + .filter(lambda p: RA_OPT_TYPE_PIO in p.icmpv6.opt.type) \ + .must_next() + + # Verify that the BR sends RA messages with the same parameters. + br_ra = pkts.filter_eth_src(vars['BR_ETH']) \ + .filter_icmpv6_nd_ra() \ + .filter(lambda p: RA_OPT_TYPE_RIO in p.icmpv6.opt.type) \ + .filter(lambda p: p.icmpv6.nd.ra.router_lifetime == radvd_ra.icmpv6.nd.ra.router_lifetime) \ + .filter(lambda p: p.icmpv6.nd.ra.retrans_timer == radvd_ra.icmpv6.nd.ra.retrans_timer) \ + .filter(lambda p: p.icmpv6.nd.ra.reachable_time == radvd_ra.icmpv6.nd.ra.reachable_time) \ + .must_next() + + # Verify that radvd sends at lease one RA message with zero Router Lifetime + # when it is stopped. + pkts.filter_eth_src(vars['BR_ETH']) \ + .filter_icmpv6_nd_ra() \ + .filter(lambda p: RA_OPT_TYPE_PIO in p.icmpv6.opt.type) \ + .filter(lambda p: p.icmpv6.nd.ra.router_lifetime == 0) \ + .must_next() + + # Verify that the BR forgets radvd's RA parameters. + pkts.filter_eth_src(vars['BR_ETH']) \ + .filter_icmpv6_nd_ra() \ + .filter(lambda p: RA_OPT_TYPE_RIO in p.icmpv6.opt.type) \ + .filter(lambda p: p.icmpv6.nd.ra.router_lifetime == 0) \ + .filter(lambda p: p.icmpv6.nd.ra.retrans_timer == 0) \ + .filter(lambda p: p.icmpv6.nd.ra.reachable_time == 0) \ + .must_next() + + # TODO: verify that the BR forgets the radvd's RA parameters in + # a period after radvd is force killed. + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/border_router/test_single_border_router.py b/tests/scripts/thread-cert/border_router/test_single_border_router.py index 55a93b4c0..041c29bcd 100755 --- a/tests/scripts/thread-cert/border_router/test_single_border_router.py +++ b/tests/scripts/thread-cert/border_router/test_single_border_router.py @@ -99,8 +99,8 @@ class SingleBorderRouter(thread_cert.TestCase): self.simulator.go(10) self.collect_ipaddrs() - logging.info("BR addrs: %r", br.get_addrs()) - logging.info("ROUTER addrs: %r", router.get_addrs()) + logging.info("BR addrs: %r", br.get_addrs()) + logging.info("ROUTER addrs: %r", router.get_addrs()) logging.info("HOST addrs: %r", host.get_addrs()) self.assertEqual(len(br.get_prefixes()), 1) diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 61f58a989..06c1bac54 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -2812,18 +2812,21 @@ class LinuxHost(): self.bash("""cat >/etc/radvd.conf <