[border-router] add external route for on-link prefix and external OMR prefixes (#6008)

This commit includes the enhancement that

1. Adds non-default external route for on-link prefixes.

2. Add external route for OMR prefixes advertised by BRs in other
   Thread Networks.

3. A new API to disable/enable the Border Routing manager at run
   time. CLI commands are added for testing.

4. A new platform API to get the link-local address of the infra
   interface so that we can filter out the ICMPv6 packets from myself
   by the Border Routing Manager rather than the platform
   implementation.
This commit is contained in:
kangping
2021-01-20 08:23:24 -08:00
committed by GitHub
parent ba97f96fc7
commit 5fa21ddbbd
18 changed files with 999 additions and 277 deletions
@@ -41,14 +41,15 @@ import thread_cert
# ----------------(eth)------------------
# | | |
# BR1 (Leader) ----- BR2 HOST
# |
# ED1
# | |
# ROUTER1 ROUTER2
#
BR1 = 1
ROUTER1 = 2
BR2 = 3
HOST = 4
ROUTER2 = 4
HOST = 5
CHANNEL1 = 18
@@ -74,12 +75,19 @@ class MultiBorderRouters(thread_cert.TestCase):
},
BR2: {
'name': 'BR_2',
'allowlist': [BR1],
'allowlist': [BR1, ROUTER2],
'is_otbr': True,
'version': '1.2',
'channel': CHANNEL1,
'router_selection_jitter': 2,
},
ROUTER2: {
'name': 'Router_2',
'allowlist': [BR2],
'version': '1.2',
'channel': CHANNEL1,
'router_selection_jitter': 2,
},
HOST: {
'name': 'Host',
'is_host': True
@@ -87,7 +95,7 @@ class MultiBorderRouters(thread_cert.TestCase):
}
def test(self):
self.nodes[HOST].start(start_radvd=True, prefix=config.ONLINK_PREFIX, slaac=True)
self.nodes[HOST].start(start_radvd=False)
self.simulator.go(5)
self.nodes[BR1].start()
@@ -102,12 +110,70 @@ class MultiBorderRouters(thread_cert.TestCase):
self.simulator.go(5)
self.assertEqual('router', self.nodes[BR2].get_state())
self.nodes[ROUTER2].start()
self.simulator.go(5)
self.assertEqual('router', self.nodes[ROUTER2].get_state())
#
# Case 1. bi-directional connectivity when there are two BRs.
#
self.simulator.go(10)
self.collect_ipaddrs()
logging.info("BR1 addrs: %r", self.nodes[BR1].get_addrs())
logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs())
logging.info("BR2 addrs: %r", self.nodes[BR2].get_addrs())
logging.info("ROUTER2 addrs: %r", self.nodes[ROUTER2].get_addrs())
logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs())
self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1)
self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1)
self.assertTrue(len(self.nodes[BR2].get_prefixes()) == 1)
self.assertTrue(len(self.nodes[ROUTER2].get_prefixes()) == 1)
br1_omr_prefix = self.nodes[BR1].get_prefixes()[0]
# Each BR should independently register an external route for the on-link prefix.
self.assertTrue(len(self.nodes[BR1].get_routes()) == 2)
self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 2)
self.assertTrue(len(self.nodes[BR2].get_routes()) == 2)
self.assertTrue(len(self.nodes[ROUTER2].get_routes()) == 2)
external_route = self.nodes[BR1].get_routes()[0]
br1_on_link_prefix = external_route.split(' ')[0]
self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(self.nodes[BR2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(self.nodes[HOST].get_matched_ula_addresses(br1_on_link_prefix)) == 1)
# Router1 and Router2 can ping each other inside the Thread network.
self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
self.assertTrue(self.nodes[ROUTER2].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
# Both Router1 and Router2 can ping to/from the Host on infra link.
self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_matched_ula_addresses(br1_on_link_prefix)[0]))
self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
backbone=True))
self.assertTrue(self.nodes[ROUTER2].ping(self.nodes[HOST].get_matched_ula_addresses(br1_on_link_prefix)[0]))
self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
backbone=True))
#
# Case 2. Another BR continues providing Border Routing when current one is disabled.
#
self.nodes[BR1].disable_br()
self.simulator.go(15)
self.collect_ipaddrs()
logging.info("BR1 addrs: %r", self.nodes[BR1].get_addrs())
logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs())
logging.info("BR2 addrs: %r", self.nodes[BR2].get_addrs())
logging.info("ROUTER2 addrs: %r", self.nodes[ROUTER2].get_addrs())
logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs())
self.assertGreaterEqual(len(self.nodes[HOST].get_addrs()), 2)
@@ -115,23 +181,36 @@ class MultiBorderRouters(thread_cert.TestCase):
self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1)
self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1)
self.assertTrue(len(self.nodes[BR2].get_prefixes()) == 1)
self.assertTrue(len(self.nodes[ROUTER2].get_prefixes()) == 1)
br2_omr_prefix = self.nodes[BR1].get_prefixes()[0]
self.assertNotEqual(br1_omr_prefix, br2_omr_prefix)
# Only BR2 will register external route for the on-link prefix.
self.assertTrue(len(self.nodes[BR1].get_routes()) == 1)
self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1)
self.assertTrue(len(self.nodes[BR2].get_routes()) == 1)
self.assertTrue(len(self.nodes[ROUTER2].get_routes()) == 1)
br2_external_route = self.nodes[BR2].get_routes()[0]
br2_on_link_prefix = br2_external_route.split(' ')[0]
self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(self.nodes[BR2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1)
self.assertTrue(len(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
# Router1 and BR2 can ping each other inside the Thread network.
self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[BR2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
self.assertTrue(self.nodes[BR2].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
self.assertTrue(len(self.nodes[HOST].get_matched_ula_addresses(br2_on_link_prefix)) == 1)
# Both Router1 and BR2 can ping to/from the Host on infra link.
self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
backbone=True))
self.assertTrue(self.nodes[BR2].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
self.assertTrue(self.nodes[HOST].ping(self.nodes[BR2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
backbone=True))
# Router1 and Router2 can ping each other inside the Thread network.
self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
self.assertTrue(self.nodes[ROUTER2].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
# Both Router1 and Router2 can ping to/from the Host on infra link.
for router in [ROUTER1, ROUTER2]:
self.assertTrue(self.nodes[router].ping(self.nodes[HOST].get_matched_ula_addresses(br2_on_link_prefix)[0]))
self.assertTrue(self.nodes[HOST].ping(self.nodes[router].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
backbone=True))
if __name__ == '__main__':
@@ -40,7 +40,7 @@ import thread_cert
# | |
# BR1 BR2
# | |
# ED1 ED2
# ROUTER1 ROUTER2
#
# Thread Net1 Thread Net2
#
@@ -119,6 +119,25 @@ class MultiThreadNetworks(thread_cert.TestCase):
self.assertTrue(len(self.nodes[BR2].get_prefixes()) == 1)
self.assertTrue(len(self.nodes[ROUTER2].get_prefixes()) == 1)
br1_omr_prefix = self.nodes[BR1].get_prefixes()[0]
br2_omr_prefix = self.nodes[BR2].get_prefixes()[0]
self.assertNotEqual(br1_omr_prefix, br2_omr_prefix)
# Each BR should independently register an external route for the on-link prefix
# and OMR prefix in another Thread Network.
self.assertTrue(len(self.nodes[BR1].get_routes()) == 2)
self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 2)
self.assertTrue(len(self.nodes[BR2].get_routes()) == 2)
self.assertTrue(len(self.nodes[ROUTER2].get_routes()) == 2)
br1_external_routes = self.nodes[BR1].get_routes()
br2_external_routes = self.nodes[BR2].get_routes()
br1_external_routes.sort()
br2_external_routes.sort()
self.assertNotEqual(br1_external_routes, br2_external_routes)
self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
@@ -93,6 +93,10 @@ class SingleBorderRouter(thread_cert.TestCase):
self.simulator.go(5)
self.assertEqual('router', self.nodes[ROUTER1].get_state())
#
# Case 1. There is no OMR prefix or on-link prefix.
#
self.simulator.go(10)
self.collect_ipaddrs()
@@ -100,22 +104,34 @@ class SingleBorderRouter(thread_cert.TestCase):
logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs())
logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs())
self.assertGreaterEqual(len(self.nodes[HOST].get_addrs()), 2)
self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1)
self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1)
self.assertTrue(len(self.nodes[BR1].get_routes()) == 1)
self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1)
omr_prefix = self.nodes[BR1].get_prefixes()[0]
external_route = self.nodes[BR1].get_routes()[0]
self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1)
br1_omr_address = self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]
router1_omr_address = self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]
host_ula_address = self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]
# Router1 can ping to/from the Host on infra link.
self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
backbone=True))
# Add two on-mesh prefix on BR1, so that
# it will deregister its random-generated OMR prefix.
#
# Case 2. User adds smaller on-mesh prefix.
# 1. Should deregister our local OMR prefix.
# 2. Should re-register our local OMR prefix when user prefix
# is removed.
#
self.nodes[BR1].add_prefix(ON_MESH_PREFIX1)
self.nodes[BR1].add_prefix(ON_MESH_PREFIX2)
self.nodes[BR1].register_netdata()
@@ -131,6 +147,8 @@ class SingleBorderRouter(thread_cert.TestCase):
self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 2)
self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 2)
self.assertTrue(len(self.nodes[BR1].get_routes()) == 1)
self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1)
self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 2)
self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 2)
@@ -143,6 +161,100 @@ class SingleBorderRouter(thread_cert.TestCase):
self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[1],
backbone=True))
# Remove user prefixes, should re-register local OMR prefix.
self.nodes[BR1].remove_prefix(ON_MESH_PREFIX1)
self.nodes[BR1].remove_prefix(ON_MESH_PREFIX2)
self.nodes[BR1].register_netdata()
self.simulator.go(10)
self.collect_ipaddrs()
logging.info("BR1 addrs: %r", self.nodes[BR1].get_addrs())
logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs())
logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs())
self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1)
self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1)
self.assertTrue(len(self.nodes[BR1].get_routes()) == 1)
self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1)
# The same local OMR and on-link prefix should be re-register.
self.assertEqual(omr_prefix, self.nodes[BR1].get_prefixes()[0])
self.assertEqual(omr_prefix, self.nodes[ROUTER1].get_prefixes()[0])
self.assertEqual(external_route, self.nodes[BR1].get_routes()[0])
self.assertEqual(external_route, self.nodes[ROUTER1].get_routes()[0])
self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1)
self.assertEqual(br1_omr_address, self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])
self.assertEqual(router1_omr_address, self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])
self.assertEqual(host_ula_address, self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])
# Router1 can ping to/from the Host on infra link.
self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
backbone=True))
#
# Case 3. OMR and on-link prefixes should be removed when Border Routing is
# explicitly disabled and added when Border Routing is enabled again.
#
self.nodes[BR1].disable_br()
self.simulator.go(10)
self.collect_ipaddrs()
logging.info("BR1 addrs: %r", self.nodes[BR1].get_addrs())
logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs())
logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs())
self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 0)
self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 0)
self.assertTrue(len(self.nodes[BR1].get_routes()) == 0)
self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 0)
self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 0)
self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 0)
# Per RFC 4862, the host will not immediately remove the ULA address, but deprecate it.
self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1)
self.nodes[BR1].enable_br()
# It takes around 10 seconds to start sending RA messages.
self.simulator.go(15)
self.collect_ipaddrs()
logging.info("BR1 addrs: %r", self.nodes[BR1].get_addrs())
logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs())
logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs())
self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1)
self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1)
self.assertTrue(len(self.nodes[BR1].get_routes()) == 1)
self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1)
# The same local OMR and on-link prefix should be re-registered.
self.assertEqual(omr_prefix, self.nodes[BR1].get_prefixes()[0])
self.assertEqual(omr_prefix, self.nodes[ROUTER1].get_prefixes()[0])
self.assertEqual(external_route, self.nodes[BR1].get_routes()[0])
self.assertEqual(external_route, self.nodes[ROUTER1].get_routes()[0])
self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1)
self.assertEqual(br1_omr_address, self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])
self.assertEqual(router1_omr_address, self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])
self.assertEqual(host_ula_address, self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])
# Router1 can ping to/from the Host on infra link.
self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
backbone=True))
if __name__ == '__main__':
unittest.main()
+37 -8
View File
@@ -1281,21 +1281,39 @@ class NodeImpl:
self.send_command(cmd)
self._expect_done()
def enable_br(self):
self.send_command('br enable')
self._expect('Done')
def disable_br(self):
self.send_command('br disable')
self._expect('Done')
def get_prefixes(self):
netdata = self.netdata_show()
prefixes = []
return self.get_netdata()['Prefixes']
for i in range(1, len(netdata)):
if netdata[i].startswith("Routes:"):
break
prefixes.append(netdata[i])
return prefixes
def get_routes(self):
return self.get_netdata()['Routes']
def netdata_show(self):
self.send_command('netdata show')
return self._expect_command_output('netdata show')
def get_netdata(self):
raw_netdata = self.netdata_show()
netdata = {'Prefixes': [], 'Routes': [], 'Services': []}
key_list = ['Prefixes', 'Routes', 'Services']
key = None
for i in range(0, len(raw_netdata)):
keys = list(filter(raw_netdata[i].startswith, key_list))
if keys != []:
key = keys[0]
elif key is not None:
netdata[key].append(raw_netdata[i])
return netdata
def add_route(self, prefix, stable=False, prf='med'):
cmd = 'route add %s ' % prefix
if stable:
@@ -2157,6 +2175,17 @@ class HostNode(LinuxHost, OtbrDocker):
def __repr__(self):
return f'Host<{self.nodeid}>'
def get_matched_ula_addresses(self, prefix):
"""Get the IPv6 addresses that matches given prefix.
"""
addrs = []
for addr in self.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA):
if addr.startswith(prefix.split('::')[0]):
addrs.append(addr)
return addrs
def get_ip6_address(self, address_type: config.ADDRESS_TYPE):
"""Get specific type of IPv6 address configured on thread device.