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 <