mirror of
https://github.com/espressif/openthread.git
synced 2026-07-28 06:37:46 +00:00
[routing-manager] delay removing external route for on-link prefixes (#6878)
Per RFC 4862 section 5.5.3, the SLAAC address of a on-link prefix will not immediately be invalidated even if the on-link prefix is sent with zero PIO valid lifetime but we currently removes the external route for the on-link prefix when we see zero valid lifetime. This results in an error case that a Thread device can not reach a valid SLAAC address of a Wi-Fi host on the same infra link because there is no routes for this address. This commit fixes this issue by delay removing the external route for the discovered on-link prefix untill its valid lifetime expires. Meanwhile, the Border Router now considers a deprecated (zero preferred lifetime) on-link prefix not usable. Thus, the Border Router will start advertising its own on-link prefix when current on-link prefix is deprecated.
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
#
|
||||
import logging
|
||||
import unittest
|
||||
from ipaddress import IPv6Network
|
||||
|
||||
import config
|
||||
import thread_cert
|
||||
@@ -108,6 +109,8 @@ class MultiBorderRouters(thread_cert.TestCase):
|
||||
self.simulator.go(5)
|
||||
self.assertEqual('router', router1.get_state())
|
||||
|
||||
self.simulator.go(5)
|
||||
|
||||
br2.start()
|
||||
self.simulator.go(5)
|
||||
self.assertEqual('router', br2.get_state())
|
||||
@@ -129,27 +132,29 @@ class MultiBorderRouters(thread_cert.TestCase):
|
||||
logging.info("ROUTER2 addrs: %r", router2.get_addrs())
|
||||
logging.info("HOST addrs: %r", host.get_addrs())
|
||||
|
||||
self.assertTrue(len(br1.get_prefixes()) == 1)
|
||||
self.assertTrue(len(router1.get_prefixes()) == 1)
|
||||
self.assertTrue(len(br2.get_prefixes()) == 1)
|
||||
self.assertTrue(len(router2.get_prefixes()) == 1)
|
||||
self.assertEqual(len(br1.get_prefixes()), 1)
|
||||
self.assertEqual(len(router1.get_prefixes()), 1)
|
||||
self.assertEqual(len(br2.get_prefixes()), 1)
|
||||
self.assertEqual(len(router2.get_prefixes()), 1)
|
||||
|
||||
br1_omr_prefix = br1.get_prefixes()[0]
|
||||
br1_omr_prefix = br1.get_omr_prefix()
|
||||
self.assertEqual(br1_omr_prefix, br1.get_prefixes()[0].split(' ')[0])
|
||||
|
||||
# Each BR should independently register an external route for the on-link prefix.
|
||||
self.assertTrue(len(br1.get_routes()) == 2)
|
||||
self.assertTrue(len(router1.get_routes()) == 2)
|
||||
self.assertTrue(len(br2.get_routes()) == 2)
|
||||
self.assertTrue(len(router2.get_routes()) == 2)
|
||||
self.assertEqual(len(br1.get_routes()), 2)
|
||||
self.assertEqual(len(router1.get_routes()), 2)
|
||||
self.assertEqual(len(br2.get_routes()), 2)
|
||||
self.assertEqual(len(router2.get_routes()), 2)
|
||||
|
||||
external_route = br1.get_routes()[0]
|
||||
br1_on_link_prefix = external_route.split(' ')[0]
|
||||
br1_on_link_prefix = br1.get_on_link_prefix()
|
||||
self.assertEqual(br1_on_link_prefix, br1.get_routes()[0].split(' ')[0])
|
||||
self.assertEqual(br1_on_link_prefix, br1.get_routes()[1].split(' ')[0])
|
||||
|
||||
self.assertTrue(len(br1.get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
|
||||
self.assertTrue(len(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
|
||||
self.assertTrue(len(br2.get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
|
||||
self.assertTrue(len(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
|
||||
self.assertTrue(len(host.get_matched_ula_addresses(br1_on_link_prefix)) == 1)
|
||||
self.assertEqual(len(br1.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
|
||||
self.assertEqual(len(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
|
||||
self.assertEqual(len(br2.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
|
||||
self.assertEqual(len(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
|
||||
self.assertEqual(len(host.get_matched_ula_addresses(br1_on_link_prefix)), 1)
|
||||
|
||||
# Router1 and Router2 can ping each other inside the Thread network.
|
||||
self.assertTrue(router1.ping(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
|
||||
@@ -176,31 +181,35 @@ class MultiBorderRouters(thread_cert.TestCase):
|
||||
logging.info("ROUTER2 addrs: %r", router2.get_addrs())
|
||||
logging.info("HOST addrs: %r", host.get_addrs())
|
||||
|
||||
self.assertGreaterEqual(len(host.get_addrs()), 2)
|
||||
self.assertGreaterEqual(len(host.get_addrs()), 3)
|
||||
|
||||
self.assertTrue(len(br1.get_prefixes()) == 1)
|
||||
self.assertTrue(len(router1.get_prefixes()) == 1)
|
||||
self.assertTrue(len(br2.get_prefixes()) == 1)
|
||||
self.assertTrue(len(router2.get_prefixes()) == 1)
|
||||
self.assertEqual(len(br1.get_prefixes()), 1)
|
||||
self.assertEqual(len(router1.get_prefixes()), 1)
|
||||
self.assertEqual(len(br2.get_prefixes()), 1)
|
||||
self.assertEqual(len(router2.get_prefixes()), 1)
|
||||
|
||||
br2_omr_prefix = br1.get_prefixes()[0]
|
||||
self.assertNotEqual(br1_omr_prefix, br2_omr_prefix)
|
||||
br2_omr_prefix = br2.get_omr_prefix()
|
||||
self.assertEqual(br2_omr_prefix, br2.get_prefixes()[0].split(' ')[0])
|
||||
|
||||
# Only BR2 will register external route for the on-link prefix.
|
||||
self.assertTrue(len(br1.get_routes()) == 1)
|
||||
self.assertTrue(len(router1.get_routes()) == 1)
|
||||
self.assertTrue(len(br2.get_routes()) == 1)
|
||||
self.assertTrue(len(router2.get_routes()) == 1)
|
||||
# Only BR2 will keep the route for BR1's on-link prefix
|
||||
# and add route for on-link prefix of its own.
|
||||
self.assertEqual(len(br1.get_routes()), 2)
|
||||
self.assertEqual(len(router1.get_routes()), 2)
|
||||
self.assertEqual(len(br2.get_routes()), 2)
|
||||
self.assertEqual(len(router2.get_routes()), 2)
|
||||
|
||||
br2_external_route = br2.get_routes()[0]
|
||||
br2_on_link_prefix = br2_external_route.split(' ')[0]
|
||||
br2_external_routes = [route.split(' ')[0] for route in br2.get_routes()]
|
||||
|
||||
self.assertTrue(len(br1.get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
|
||||
self.assertTrue(len(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
|
||||
self.assertTrue(len(br2.get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
|
||||
self.assertTrue(len(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
|
||||
br2_on_link_prefix = br2.get_on_link_prefix()
|
||||
self.assertEqual(set(map(IPv6Network, br2_external_routes)),
|
||||
set(map(IPv6Network, [br1_on_link_prefix, br2_on_link_prefix])))
|
||||
|
||||
self.assertTrue(len(host.get_matched_ula_addresses(br2_on_link_prefix)) == 1)
|
||||
self.assertEqual(len(br1.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
|
||||
self.assertEqual(len(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
|
||||
self.assertEqual(len(br2.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
|
||||
self.assertEqual(len(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
|
||||
|
||||
self.assertEqual(len(host.get_matched_ula_addresses(br2_on_link_prefix)), 1)
|
||||
|
||||
# Router1 and Router2 can ping each other inside the Thread network.
|
||||
self.assertTrue(router1.ping(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
|
||||
|
||||
@@ -120,8 +120,8 @@ class MultiThreadNetworks(thread_cert.TestCase):
|
||||
self.assertTrue(len(br2.get_prefixes()) == 1)
|
||||
self.assertTrue(len(router2.get_prefixes()) == 1)
|
||||
|
||||
br1_omr_prefix = br1.get_prefixes()[0]
|
||||
br2_omr_prefix = br2.get_prefixes()[0]
|
||||
br1_omr_prefix = br1.get_omr_prefix()
|
||||
br2_omr_prefix = br2.get_omr_prefix()
|
||||
|
||||
self.assertNotEqual(br1_omr_prefix, br2_omr_prefix)
|
||||
|
||||
|
||||
@@ -107,6 +107,9 @@ class SingleBorderRouter(thread_cert.TestCase):
|
||||
|
||||
self.assertEqual(len(br.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
|
||||
self.assertEqual(len(router.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
|
||||
|
||||
# radvd doesn't deprecates the PIO so that the Border Router will not
|
||||
# advertise its own on-link prefix immediately.
|
||||
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]))
|
||||
|
||||
@@ -318,12 +318,11 @@ class SingleBorderRouter(thread_cert.TestCase):
|
||||
br.start_radvd_service(prefix=config.ONLINK_GUA_PREFIX, slaac=True)
|
||||
self.simulator.go(5)
|
||||
|
||||
self.assertEqual(len(br.get_routes()), 1)
|
||||
self.assertTrue(br.get_routes()[0].startswith(config.ONLINK_GUA_PREFIX.split('::/')[0]))
|
||||
self.assertEqual(len(router.get_routes()), 1)
|
||||
self.assertTrue(router.get_routes()[0].startswith(config.ONLINK_GUA_PREFIX.split('::/')[0]))
|
||||
self.assertEqual(len(br.get_routes()), 2)
|
||||
self.assertEqual(len(router.get_routes()), 2)
|
||||
|
||||
self.assertTrue(router.ping(host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_GUA)[0]))
|
||||
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))
|
||||
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
import ipaddress
|
||||
import logging
|
||||
import unittest
|
||||
from ipaddress import IPv6Network
|
||||
|
||||
import config
|
||||
import thread_cert
|
||||
@@ -124,7 +124,7 @@ class MultiThreadNetworks(thread_cert.TestCase):
|
||||
|
||||
self.assertEqual(len(br1.get_routes()), 1)
|
||||
br1_on_link_prefix = br1.get_routes()[0].split(' ')[0]
|
||||
self.assertEqual(ipaddress.IPv6Network(br1_on_link_prefix), ipaddress.IPv6Network(ON_LINK_PREFIX))
|
||||
self.assertEqual(IPv6Network(br1_on_link_prefix), IPv6Network(ON_LINK_PREFIX))
|
||||
|
||||
host_on_link_addr = host.get_matched_ula_addresses(br1_on_link_prefix)[0]
|
||||
self.assertTrue(router1.ping(host_on_link_addr))
|
||||
@@ -156,14 +156,15 @@ class MultiThreadNetworks(thread_cert.TestCase):
|
||||
self.assertTrue(len(br2.get_prefixes()) == 1)
|
||||
self.assertTrue(len(router2.get_prefixes()) == 1)
|
||||
|
||||
br1_omr_prefix = br1.get_prefixes()[0].split(' ')[0]
|
||||
br2_omr_prefix = br2.get_prefixes()[0].split(' ')[0]
|
||||
br1_omr_prefix = br1.get_omr_prefix()
|
||||
br2_omr_prefix = br2.get_omr_prefix()
|
||||
self.assertNotEqual(br1_omr_prefix, br2_omr_prefix)
|
||||
|
||||
# Verify that BR1 removed ON_LINK_PREFIX from its
|
||||
# external routes list.
|
||||
self.assertEqual(len(br1.get_routes()), 2)
|
||||
self.assertEqual(len(router1.get_routes()), 2)
|
||||
# Verify that the Border Routers starts advertsing new on-link prefix
|
||||
# but don't remove the external routes for the radvd on-link prefix
|
||||
# immediately, because the SLAAC addresses are still valid.
|
||||
self.assertEqual(len(br1.get_routes()), 3)
|
||||
self.assertEqual(len(router1.get_routes()), 3)
|
||||
self.assertEqual(len(br2.get_routes()), 2)
|
||||
self.assertEqual(len(router2.get_routes()), 2)
|
||||
|
||||
@@ -172,21 +173,39 @@ class MultiThreadNetworks(thread_cert.TestCase):
|
||||
|
||||
on_link_prefixes = list(set(br1_external_routes).intersection(br2_external_routes))
|
||||
self.assertEqual(len(on_link_prefixes), 1)
|
||||
on_link_prefix = on_link_prefixes[0]
|
||||
self.assertEqual(IPv6Network(on_link_prefixes[0]), IPv6Network(br2.get_on_link_prefix()))
|
||||
|
||||
# Verify that both BR1 and BR2 are using a new on-link prefix.
|
||||
# BR1 is depending on the Vicarious Router Solicitation to find
|
||||
# out that the ON_LINK_PREFIX is stale and starts Router Solicitation
|
||||
# on its own.
|
||||
self.assertNotEqual(ipaddress.IPv6Network(on_link_prefix), ipaddress.IPv6Network(ON_LINK_PREFIX))
|
||||
router1_omr_addr = router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]
|
||||
router2_omr_addr = router2.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]
|
||||
|
||||
host_on_link_addr = host.get_matched_ula_addresses(on_link_prefix)[0]
|
||||
self.assertTrue(router1.ping(host_on_link_addr))
|
||||
self.assertTrue(
|
||||
host.ping(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], backbone=True, interface=host_on_link_addr))
|
||||
self.assertTrue(router2.ping(host.get_matched_ula_addresses(on_link_prefix)[0]))
|
||||
self.assertTrue(
|
||||
host.ping(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], backbone=True, interface=host_on_link_addr))
|
||||
# Make sure that addresses of both the deprecated radvd `ON_LINK_PREFIX`
|
||||
# and preferred Border Router on-link prefix can be reached by Thread
|
||||
# devices in network of Border Router 1.
|
||||
for host_on_link_addr in [
|
||||
host.get_matched_ula_addresses(on_link_prefixes[0])[0],
|
||||
host.get_matched_ula_addresses(ON_LINK_PREFIX)[0]
|
||||
]:
|
||||
self.assertTrue(router1.ping(host_on_link_addr))
|
||||
self.assertTrue(host.ping(router1_omr_addr, backbone=True, interface=host_on_link_addr))
|
||||
|
||||
host_on_link_addr = host.get_matched_ula_addresses(ON_LINK_PREFIX)[0]
|
||||
|
||||
# Make sure that addresses of the deprecated radvd `ON_LINK_PREFIX`
|
||||
# can't be reached by Thread devices in network of Border Router 2.
|
||||
self.assertFalse(router2.ping(host_on_link_addr))
|
||||
self.assertFalse(host.ping(router2_omr_addr, backbone=True, interface=host_on_link_addr))
|
||||
|
||||
# Wait 30 seconds for the radvd `ON_LINK_PREFIX` to be invalidated
|
||||
# and make sure that Thread devices in both networks can't reach
|
||||
# the on-link address.
|
||||
self.simulator.go(30) # Valid Lifetime of radvd PIO is set to 60 seconds.
|
||||
self.assertEqual(len(host.get_matched_ula_addresses(ON_LINK_PREFIX)), 0)
|
||||
self.assertFalse(router1.ping(host_on_link_addr))
|
||||
self.assertFalse(host.ping(router1_omr_addr, backbone=True, interface=host_on_link_addr))
|
||||
self.assertFalse(router2.ping(host_on_link_addr))
|
||||
self.assertFalse(host.ping(router2_omr_addr, backbone=True, interface=host_on_link_addr))
|
||||
|
||||
# Verify connectivity between the two networks.
|
||||
self.assertTrue(router1.ping(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
|
||||
self.assertTrue(router2.ping(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import sys
|
||||
import time
|
||||
import traceback
|
||||
import unittest
|
||||
from ipaddress import IPv6Address, IPv6Network
|
||||
from typing import Union, Dict, Optional, List
|
||||
|
||||
import pexpect
|
||||
@@ -1149,7 +1150,7 @@ class NodeImpl:
|
||||
self.send_command(cmd)
|
||||
self._expect_done()
|
||||
|
||||
def multicast_listener_list(self) -> Dict[ipaddress.IPv6Address, int]:
|
||||
def multicast_listener_list(self) -> Dict[IPv6Address, int]:
|
||||
cmd = 'bbr mgmt mlr listener'
|
||||
self.send_command(cmd)
|
||||
|
||||
@@ -1157,7 +1158,7 @@ class NodeImpl:
|
||||
for line in self._expect_results("\S+ \d+"):
|
||||
line = line.split()
|
||||
assert len(line) == 2, line
|
||||
ip = ipaddress.IPv6Address(line[0])
|
||||
ip = IPv6Address(line[0])
|
||||
timeout = int(line[1])
|
||||
assert ip not in table
|
||||
|
||||
@@ -1170,9 +1171,9 @@ class NodeImpl:
|
||||
self.send_command(cmd)
|
||||
self._expect_done()
|
||||
|
||||
def multicast_listener_add(self, ip: Union[ipaddress.IPv6Address, str], timeout: int = 0):
|
||||
if not isinstance(ip, ipaddress.IPv6Address):
|
||||
ip = ipaddress.IPv6Address(ip)
|
||||
def multicast_listener_add(self, ip: Union[IPv6Address, str], timeout: int = 0):
|
||||
if not isinstance(ip, IPv6Address):
|
||||
ip = IPv6Address(ip)
|
||||
|
||||
cmd = f'bbr mgmt mlr listener add {ip.compressed} {timeout}'
|
||||
self.send_command(cmd)
|
||||
@@ -1183,7 +1184,7 @@ class NodeImpl:
|
||||
self.send_command(cmd)
|
||||
self._expect_done()
|
||||
|
||||
def register_multicast_listener(self, *ipaddrs: Union[ipaddress.IPv6Address, str], timeout=None):
|
||||
def register_multicast_listener(self, *ipaddrs: Union[IPv6Address, str], timeout=None):
|
||||
assert len(ipaddrs) > 0, ipaddrs
|
||||
|
||||
ipaddrs = map(str, ipaddrs)
|
||||
@@ -1198,7 +1199,7 @@ class NodeImpl:
|
||||
status = int(m.group(1))
|
||||
failed_num = int(m.group(2))
|
||||
assert failed_num == len(lines) - 1
|
||||
failed_ips = list(map(ipaddress.IPv6Address, lines[1:]))
|
||||
failed_ips = list(map(IPv6Address, lines[1:]))
|
||||
print(f"register_multicast_listener {ipaddrs} => status: {status}, failed ips: {failed_ips}")
|
||||
return status, failed_ips
|
||||
|
||||
@@ -1516,7 +1517,7 @@ class NodeImpl:
|
||||
return None
|
||||
|
||||
def get_mleid_iid(self):
|
||||
ml_eid = ipaddress.IPv6Address(self.get_mleid())
|
||||
ml_eid = IPv6Address(self.get_mleid())
|
||||
return ml_eid.packed[8:].hex()
|
||||
|
||||
def get_eidcaches(self):
|
||||
@@ -1606,6 +1607,22 @@ class NodeImpl:
|
||||
|
||||
return None
|
||||
|
||||
def get_ip6_address_by_prefix(self, prefix: Union[str, IPv6Network]) -> List[IPv6Address]:
|
||||
"""Get addresses matched with given prefix.
|
||||
|
||||
Args:
|
||||
prefix: the prefix to match against.
|
||||
Can be either a string or ipaddress.IPv6Network.
|
||||
|
||||
Returns:
|
||||
The IPv6 address list.
|
||||
"""
|
||||
if isinstance(prefix, str):
|
||||
prefix = IPv6Network(prefix)
|
||||
addrs = map(IPv6Address, self.get_addrs())
|
||||
|
||||
return [addr for addr in addrs if addr in prefix]
|
||||
|
||||
def get_ip6_address(self, address_type):
|
||||
"""Get specific type of IPv6 address configured on thread device.
|
||||
|
||||
@@ -1661,6 +1678,16 @@ class NodeImpl:
|
||||
self.send_command('br disable')
|
||||
self._expect_done()
|
||||
|
||||
def get_omr_prefix(self):
|
||||
cmd = 'br omrprefix'
|
||||
self.send_command(cmd)
|
||||
return self._expect_command_output(cmd)[0]
|
||||
|
||||
def get_on_link_prefix(self):
|
||||
cmd = 'br onlinkprefix'
|
||||
self.send_command(cmd)
|
||||
return self._expect_command_output(cmd)[0]
|
||||
|
||||
def get_prefixes(self):
|
||||
return self.get_netdata()['Prefixes']
|
||||
|
||||
@@ -2936,6 +2963,8 @@ interface eth0
|
||||
AdvOnLink on;
|
||||
AdvAutonomous %s;
|
||||
AdvRouterAddr off;
|
||||
AdvPreferredLifetime 40;
|
||||
AdvValidLifetime 60;
|
||||
};
|
||||
};
|
||||
EOF
|
||||
@@ -3009,7 +3038,7 @@ class HostNode(LinuxHost, OtbrDocker):
|
||||
|
||||
addrs = []
|
||||
for addr in self.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA):
|
||||
if ipaddress.IPv6Address(addr) in ipaddress.IPv6Network(prefix):
|
||||
if IPv6Address(addr) in IPv6Network(prefix):
|
||||
addrs.append(addr)
|
||||
|
||||
return addrs
|
||||
|
||||
Reference in New Issue
Block a user