[nat64] fetch NAT64 prefix from infrastructure interface and advertise it to netdata (#7619)

This commit fetches the NAT64 prefix on infrastructure interface and
advertise it to Network Data at medium preference.

- Use `getaddrinfo_a()` function to asynchronously lookup the ipv6
  address of the special domain `ipv4only.arpa`. The infrastructure
  NAT64 prefix is extracted from the domain answer.

- `mInfraIfNat64PrefixStaleTimer` is scheduled to monitor the presence
  and change of infrastructure NAT64 prefix.

- `EvaluateNat64Prefix` evaluates whether to advertise the
  infrastructure prefix or the local ULA prefix or neither. When there
  is a new infrastructure prefix, it will withdraw the legacy one and
  add the new one. When the infrastructure prefix no longer exists, it
  will withdraw the legacy one and add the local ULA prefix. When the
  infrastructure prefix presents again, it will add the infrastructure
  prefix and withdraw the local ULA prefix.

New tests are added to test the scenarios when infrastructure NAT64
prefix exists.  `DNS64` on OTBR is turned on to enable `bind9` with
NAT64 prefix on infrastructure interface for these tests. `bind9` is
explicitly turned off when testing local ULA prefix.  Since bind9 is
conflict with other components like dnssd, all nat64 tests are moved
under /nat64 directory and configured separately.

The case that two or more BRs have same infrastructure NAT64 prefix is
not covered by this commit and will be followed up later.
This commit is contained in:
Yi
2022-08-12 07:37:05 -07:00
committed by GitHub
parent 034fc826f3
commit 80565c58a9
30 changed files with 801 additions and 93 deletions
@@ -51,6 +51,8 @@ ROUTER = 2
BR2 = 3
HOST = 4
NAT64_PREFIX_REFRESH_DELAY = 305
class Nat64MultiBorderRouter(thread_cert.TestCase):
USE_MESSAGE_FACTORY = False
@@ -90,6 +92,8 @@ class Nat64MultiBorderRouter(thread_cert.TestCase):
br1.start()
self.simulator.go(config.LEADER_STARTUP_DELAY)
br1.bash("service bind9 stop")
self.simulator.go(NAT64_PREFIX_REFRESH_DELAY)
self.assertEqual('leader', br1.get_state())
router.start()
@@ -97,44 +101,65 @@ class Nat64MultiBorderRouter(thread_cert.TestCase):
self.assertEqual('router', router.get_state())
#
# Case 1. BR2 joins the network later and it will not add
# its local nat64 prefix to Network Data.
# Case 1. BR2 with an infrastructure prefix joins the network later and
# it will add the infrastructure nat64 prefix to Network Data.
#
br2.start()
self.simulator.go(config.BORDER_ROUTER_STARTUP_DELAY)
self.assertEqual('router', br2.get_state())
# Only 1 NAT64 prefix in Network Data.
self.simulator.go(30)
self.assertEqual(len(br1.get_netdata_nat64_prefix()), 1)
self.assertEqual(len(br2.get_netdata_nat64_prefix()), 1)
self.assertEqual(br1.get_netdata_nat64_prefix()[0], br2.get_netdata_nat64_prefix()[0])
nat64_prefix = br1.get_netdata_nat64_prefix()[0]
self.simulator.go(10)
self.assertNotEqual(br1.get_br_favored_nat64_prefix(), br2.get_br_favored_nat64_prefix())
br1_local_nat64_prefix = br1.get_br_nat64_prefix()
br2_infra_nat64_prefix = br2.get_br_favored_nat64_prefix()
# The NAT64 prefix in Network Data is same as BR1's local NAT64 prefix.
br1_nat64_prefix = br1.get_br_nat64_prefix()
br2_nat64_prefix = br2.get_br_nat64_prefix()
self.assertEqual(nat64_prefix, br1_nat64_prefix)
self.assertNotEqual(nat64_prefix, br2_nat64_prefix)
self.assertEqual(len(br1.get_netdata_nat64_prefix()), 1)
nat64_prefix = br1.get_netdata_nat64_prefix()[0]
self.assertEqual(nat64_prefix, br2_infra_nat64_prefix)
self.assertNotEqual(nat64_prefix, br1_local_nat64_prefix)
br2.disable_br()
#
# Case 2. Disable and re-enable border routing on BR1.
# Case 2. Re-enables BR2 with a local prefix and it will not add
# its local nat64 prefix to Network Data.
#
br2.bash("service bind9 stop")
self.simulator.go(5)
br2.enable_br()
self.simulator.go(10)
self.assertNotEqual(br2_infra_nat64_prefix, br2.get_br_favored_nat64_prefix())
br2_local_nat64_prefix = br2.get_br_nat64_prefix()
self.assertEqual(len(br1.get_netdata_nat64_prefix()), 1)
nat64_prefix = br1.get_netdata_nat64_prefix()[0]
self.assertEqual(nat64_prefix, br1_local_nat64_prefix)
self.assertNotEqual(nat64_prefix, br2_local_nat64_prefix)
#
# Case 3. Disable border routing on BR1.
# BR1 withdraws its prefix and BR2 advertises its prefix.
#
br1.disable_br()
self.simulator.go(30)
# BR1 withdraws its prefix and BR2 advertises its prefix.
self.simulator.go(10)
self.assertEqual(len(br1.get_netdata_nat64_prefix()), 1)
self.assertEqual(br2_nat64_prefix, br1.get_netdata_nat64_prefix()[0])
self.assertNotEqual(br1_nat64_prefix, br1.get_netdata_nat64_prefix()[0])
nat64_prefix = br1.get_netdata_nat64_prefix()[0]
self.assertEqual(br2_local_nat64_prefix, nat64_prefix)
self.assertNotEqual(br1_local_nat64_prefix, nat64_prefix)
#
# Case 4. Re-enable border routing on BR1.
# NAT64 prefix in Network Data is still advertised by BR2.
#
br1.enable_br()
self.simulator.go(config.BORDER_ROUTER_STARTUP_DELAY)
# NAT64 prefix in Network Data is still advertised by BR2.
self.simulator.go(10)
self.assertEqual(len(br1.get_netdata_nat64_prefix()), 1)
self.assertEqual(br2_nat64_prefix, br1.get_netdata_nat64_prefix()[0])
self.assertNotEqual(br1_nat64_prefix, br1.get_netdata_nat64_prefix()[0])
nat64_prefix = br1.get_netdata_nat64_prefix()[0]
self.assertEqual(br2_local_nat64_prefix, nat64_prefix)
self.assertNotEqual(br1_local_nat64_prefix, nat64_prefix)
if __name__ == '__main__':
@@ -32,7 +32,8 @@ import config
import thread_cert
# Test description:
# This test verifies the advertisement of NAT64 prefix in Thread network.
# This test verifies the advertisement of local NAT64 prefix in Thread network
# when no NAT64 prefix found on infrastructure interface.
#
# TODO: add checks for outbound connectivity from Thread device to IPv4 host
# after OTBR change is ready.
@@ -49,9 +50,12 @@ BR = 1
ROUTER = 2
HOST = 3
# The prefix is set small enough that a random-generated NAT64 prefix is very
# likely greater than it. So that the BR will remove the random-generated one.
# The prefix is set small enough that a random-generated ULA NAT64 prefix is very
# likely greater than it. So the BR will remove the random-generated one.
SMALL_NAT64_PREFIX = "fd00:00:00:01:00:00::/96"
# The prefix is set larger than a random-generated ULA NAT64 prefix.
# So the BR will remove the random-generated one.
LARGE_NAT64_PREFIX = "ff00:00:00:01:00:00::/96"
class Nat64SingleBorderRouter(thread_cert.TestCase):
@@ -85,6 +89,8 @@ class Nat64SingleBorderRouter(thread_cert.TestCase):
br.start()
self.simulator.go(config.LEADER_STARTUP_DELAY)
br.bash("service bind9 stop")
self.simulator.go(330)
self.assertEqual('leader', br.get_state())
router.start()
@@ -103,10 +109,10 @@ class Nat64SingleBorderRouter(thread_cert.TestCase):
#
# Case 2.
# User adds a smaller NAT64 prefix and the local prefix is withdrawn.
# User adds a smaller NAT64 prefix (same preference) and the local prefix is withdrawn.
# User removes the smaller NAT64 prefix and the local prefix is re-added.
#
br.add_route(SMALL_NAT64_PREFIX, stable=False, nat64=True)
br.add_route(SMALL_NAT64_PREFIX, stable=False, nat64=True, prf='low')
br.register_netdata()
self.simulator.go(5)
@@ -115,13 +121,32 @@ class Nat64SingleBorderRouter(thread_cert.TestCase):
br.remove_route(SMALL_NAT64_PREFIX)
br.register_netdata()
self.simulator.go(5)
self.simulator.go(10)
self.assertEqual(len(br.get_netdata_nat64_prefix()), 1)
self.assertEqual(local_nat64_prefix, br.get_netdata_nat64_prefix()[0])
#
# Case 3. Disable and re-enable border routing on the border router.
# Case 3.
# User adds a larger NAT64 prefix (higher preference) and the local prefix is withdrawn.
# User removes the larger NAT64 prefix and the local prefix is re-added.
#
br.add_route(LARGE_NAT64_PREFIX, stable=False, nat64=True, prf='med')
br.register_netdata()
self.simulator.go(5)
self.assertEqual(len(br.get_netdata_nat64_prefix()), 1)
self.assertNotEqual(local_nat64_prefix, br.get_netdata_nat64_prefix()[0])
br.remove_route(LARGE_NAT64_PREFIX)
br.register_netdata()
self.simulator.go(10)
self.assertEqual(len(br.get_netdata_nat64_prefix()), 1)
self.assertEqual(local_nat64_prefix, br.get_netdata_nat64_prefix()[0])
#
# Case 4. Disable and re-enable border routing on the border router.
#
br.disable_br()
self.simulator.go(5)
@@ -137,7 +162,7 @@ class Nat64SingleBorderRouter(thread_cert.TestCase):
self.assertEqual(nat64_prefix, br.get_netdata_nat64_prefix()[0])
#
# Case 4. Disable and re-enable ethernet on the border router.
# Case 5. Disable and re-enable ethernet on the border router.
#
br.disable_ether()
self.simulator.go(5)
@@ -0,0 +1,149 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022, 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 unittest
import config
import thread_cert
# Test description:
# This test verifies the advertisement of infrastructure NAT64 prefix in Thread network.
#
#
# Topology:
#
# ----------------(eth)--------------------
# |
# BR (with DNS64 on infrastructure interface)
# |
# ROUTER
#
BR = 1
ROUTER = 2
# The prefix is set smaller than the default infrastructure NAT64 prefix.
SMALL_NAT64_PREFIX = "2000:0:0:1:0:0::/96"
NAT64_PREFIX_REFRESH_DELAY = 305
class Nat64SingleBorderRouter(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',
},
}
def test(self):
br = self.nodes[BR]
router = self.nodes[ROUTER]
br.start()
self.simulator.go(config.LEADER_STARTUP_DELAY)
self.assertEqual('leader', br.get_state())
router.start()
self.simulator.go(config.ROUTER_STARTUP_DELAY)
self.assertEqual('router', router.get_state())
# Case 1 BR advertise the infrastructure prefix
infra_nat64_prefix = br.get_br_favored_nat64_prefix()
self.assertEqual(len(br.get_netdata_nat64_prefix()), 1)
nat64_prefix = br.get_netdata_nat64_prefix()[0]
self.assertEqual(nat64_prefix, infra_nat64_prefix)
# Case 2 Withdraw infrastructure prefix when a smaller prefix in medium
# preference is present
br.add_route(SMALL_NAT64_PREFIX, stable=False, nat64=True, prf='med')
br.register_netdata()
self.simulator.go(5)
self.assertEqual(len(br.get_netdata_nat64_prefix()), 1)
self.assertNotEqual(infra_nat64_prefix, br.get_netdata_nat64_prefix()[0])
br.remove_route(SMALL_NAT64_PREFIX)
br.register_netdata()
self.simulator.go(10)
self.assertEqual(len(br.get_netdata_nat64_prefix()), 1)
self.assertEqual(nat64_prefix, infra_nat64_prefix)
# Case 3 No change when a smaller prefix in low preference is present
br.add_route(SMALL_NAT64_PREFIX, stable=False, nat64=True, prf='low')
br.register_netdata()
self.simulator.go(5)
self.assertEqual(len(br.get_netdata_nat64_prefix()), 2)
self.assertEqual(br.get_netdata_nat64_prefix(), [infra_nat64_prefix, SMALL_NAT64_PREFIX])
br.remove_route(SMALL_NAT64_PREFIX)
br.register_netdata()
self.simulator.go(5)
# Case 4 Infrastructure nat64 prefix no longer presents
br.bash("service bind9 stop")
self.simulator.go(NAT64_PREFIX_REFRESH_DELAY)
local_nat64_prefix = br.get_br_nat64_prefix()
self.assertNotEqual(local_nat64_prefix, infra_nat64_prefix)
self.assertEqual(len(br.get_netdata_nat64_prefix()), 1)
self.assertEqual(br.get_netdata_nat64_prefix()[0], local_nat64_prefix)
# Case 5 Infrastructure nat64 prefix is recovered
br.bash("service bind9 start")
self.simulator.go(NAT64_PREFIX_REFRESH_DELAY)
self.assertEqual(br.get_br_favored_nat64_prefix(), infra_nat64_prefix)
self.assertEqual(len(br.get_netdata_nat64_prefix()), 1)
self.assertEqual(br.get_netdata_nat64_prefix()[0], infra_nat64_prefix)
# Case 6 Change infrastructure nat64 prefix
br.bash("sed -i 's/dns64 /\/\/dns64 /' /etc/bind/named.conf.options")
br.bash("sed -i '/\/\/dns64 /a dns64 " + SMALL_NAT64_PREFIX + " {};' /etc/bind/named.conf.options")
br.bash("service bind9 restart")
self.simulator.go(NAT64_PREFIX_REFRESH_DELAY)
self.assertEqual(br.get_br_favored_nat64_prefix(), SMALL_NAT64_PREFIX)
self.assertEqual(len(br.get_netdata_nat64_prefix()), 1)
self.assertEqual(br.get_netdata_nat64_prefix()[0], SMALL_NAT64_PREFIX)
if __name__ == '__main__':
unittest.main()
+15 -5
View File
@@ -51,6 +51,8 @@ import thread_cert
PORT_OFFSET = int(os.getenv('PORT_OFFSET', "0"))
INFRA_DNS64 = int(os.getenv('NAT64', 0))
class OtbrDocker:
RESET_DELAY = 3
@@ -107,13 +109,17 @@ class OtbrDocker:
logging.info(f'Docker image: {config.OTBR_DOCKER_IMAGE}')
subprocess.check_call(f"docker rm -f {self._docker_name} || true", shell=True)
CI_ENV = os.getenv('CI_ENV', '').split()
dns = ['--dns=127.0.0.1'] if INFRA_DNS64 == 1 else []
nat64_prefix = ['--nat64-prefix', '2001:db8:1:ffff::/96'] if INFRA_DNS64 == 1 else []
os.makedirs('/tmp/coverage/', exist_ok=True)
self._docker_proc = subprocess.Popen(['docker', 'run'] + CI_ENV + [
cmd = ['docker', 'run'] + CI_ENV + [
'--rm',
'--name',
self._docker_name,
'--network',
config.BACKBONE_DOCKER_NETWORK_NAME,
] + dns + [
'-i',
'--sysctl',
'net.ipv6.conf.all.disable_ipv6=0 net.ipv4.conf.all.forwarding=1 net.ipv6.conf.all.forwarding=1',
@@ -128,10 +134,9 @@ class OtbrDocker:
config.BACKBONE_IFNAME,
'--trel-url',
f'trel://{config.BACKBONE_IFNAME}',
],
stdin=subprocess.DEVNULL,
stdout=sys.stdout,
stderr=sys.stderr)
] + nat64_prefix
logging.info(' '.join(cmd))
self._docker_proc = subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=sys.stdout, stderr=sys.stderr)
launch_docker_deadline = time.time() + 300
launch_ok = False
@@ -1989,6 +1994,11 @@ class NodeImpl:
self.send_command(cmd)
return self._expect_command_output()[0]
def get_br_favored_nat64_prefix(self):
cmd = 'br favorednat64prefix'
self.send_command(cmd)
return self._expect_command_output()[0].split(' ')[0]
def get_netdata_nat64_prefix(self):
prefixes = []
routes = self.get_routes()