mirror of
https://github.com/espressif/openthread.git
synced 2026-08-10 20:57:47 +00:00
[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).
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -2812,18 +2812,21 @@ class LinuxHost():
|
||||
self.bash("""cat >/etc/radvd.conf <<EOF
|
||||
interface eth0
|
||||
{
|
||||
AdvSendAdvert on;
|
||||
AdvSendAdvert on;
|
||||
|
||||
MinRtrAdvInterval 3;
|
||||
MaxRtrAdvInterval 30;
|
||||
AdvDefaultPreference low;
|
||||
AdvReachableTime 200;
|
||||
AdvRetransTimer 200;
|
||||
AdvDefaultLifetime 1800;
|
||||
MinRtrAdvInterval 3;
|
||||
MaxRtrAdvInterval 30;
|
||||
AdvDefaultPreference low;
|
||||
|
||||
prefix %s
|
||||
{
|
||||
AdvOnLink on;
|
||||
AdvAutonomous %s;
|
||||
AdvRouterAddr off;
|
||||
};
|
||||
prefix %s
|
||||
{
|
||||
AdvOnLink on;
|
||||
AdvAutonomous %s;
|
||||
AdvRouterAddr off;
|
||||
};
|
||||
};
|
||||
EOF
|
||||
""" % (prefix, 'on' if slaac else 'off'))
|
||||
|
||||
@@ -615,6 +615,9 @@ class PacketFilter(object):
|
||||
return self.filter(lambda p:
|
||||
(p.icmpv6.is_neighbor_advertisement and p.icmpv6.nd.na.target_address == target_address))
|
||||
|
||||
def filter_icmpv6_nd_ra(self):
|
||||
return self.filter(lambda p: p.icmpv6.is_router_advertisement)
|
||||
|
||||
def filter_has_bbr_dataset(self):
|
||||
return self.filter("""
|
||||
thread_nwd.tlv.server.has('16')
|
||||
|
||||
Reference in New Issue
Block a user