[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:
kangping
2021-08-12 09:28:27 -07:00
committed by Jonathan Hui
parent 5598848980
commit 4e717060fd
9 changed files with 353 additions and 189 deletions
@@ -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 -9
View File
@@ -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