[netdata] add version number to DNS/SRP service entries (#10752)

This commit adds a version field (`uint8_t`) to DNS/SRP Anycast and
Unicast Service entries in `NetworkData::Service::Manager`.

For Unicast entries, the version the version field is placed after
the existing fields, specifically after the IPv6 address and port number fields.
For Anycast entries it is added as the in server data as part of the
Server TLV.

When processing Network Data service entries, the version field is
optional and if absent, version number zero is assumed.

The `NetworkData::Publisher` now considers entries with the same or
higher version number when deciding whether to add or remove its own
entry, preferring those with a higher version.

In SRP client, when `AutoStart` mode is used and if there are multiple
Unicast, Service entries, the client prefers the one with larger
version number.

When selecting an anycast entry, the existing rules regarding sequence
numbers are still used. If multiple entries with the same sequence
number exist, the client will assume the minimum version number among
all such entries.

This commit also updates the `test_network_data` unit test, validating
the new format and related methods.

`test_netdata_publisher.py` is also updated to check service entries
with different version numbers.
This commit is contained in:
Abtin Keshavarzian
2024-12-23 18:59:06 -08:00
committed by GitHub
parent 588e93cd15
commit 4c378f798d
16 changed files with 611 additions and 241 deletions
+6 -6
View File
@@ -2515,16 +2515,16 @@ class NodeImpl:
self.send_command('netdata register')
self._expect_done()
def netdata_publish_dnssrp_anycast(self, seqnum):
self.send_command(f'netdata publish dnssrp anycast {seqnum}')
def netdata_publish_dnssrp_anycast(self, seqnum, version=0):
self.send_command(f'netdata publish dnssrp anycast {seqnum} {version}')
self._expect_done()
def netdata_publish_dnssrp_unicast(self, address, port):
self.send_command(f'netdata publish dnssrp unicast {address} {port}')
def netdata_publish_dnssrp_unicast(self, address, port, version=0):
self.send_command(f'netdata publish dnssrp unicast {address} {port} {version}')
self._expect_done()
def netdata_publish_dnssrp_unicast_mleid(self, port):
self.send_command(f'netdata publish dnssrp unicast {port}')
def netdata_publish_dnssrp_unicast_mleid(self, port, version=0):
self.send_command(f'netdata publish dnssrp unicast {port} {version}')
self._expect_done()
def netdata_unpublish_dnssrp(self):
@@ -221,7 +221,7 @@ class NetDataPublisher(thread_cert.TestCase):
self.assertEqual(end_dev.get_state(), 'child')
#---------------------------------------------------------------------------------
# DNS/SRP anycast entries
# DNS/SRP anycast entries - equal version number
# Publish DNS/SRP anycast on leader and all routers (6 nodes).
@@ -263,7 +263,57 @@ class NetDataPublisher(thread_cert.TestCase):
self.verify_anycast_services(services)
#---------------------------------------------------------------------------------
# DNS/SRP service data unicast entries
# DNS/SRP anycast entries - different version numbers
# Publish DNS/SRP anycast on leader and all routers (6 nodes).
version = 0
leader.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM, version)
num = 1
for node in routers:
version += 1
node.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM, version)
num += 1
self.simulator.go(WAIT_TIME)
# Check all entries are present in the network data
services = leader.get_services()
self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_ANYCAST))
self.verify_anycast_services(services)
# Publish same entry with same version on all end-devices (5 nodes).
for node in end_devs:
node.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM, version)
num += 1
print(node.name)
self.simulator.go(WAIT_TIME)
# Check number of entries in the network data is limited
# to the desired number (8 entries). All new entries use
# higher version and should be preferred. Validate that
# the 'services' list contains the new services.
services = leader.get_services()
self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_ANYCAST))
self.verify_anycast_services(services)
node_rloc16 = node.get_addr16()
self.assertTrue(any(int(service[4], 16) == node_rloc16 for service in services))
# Unpublish the entry from nodes one by one starting from leader
# and check that number of entries is correct in each step.
for node in nodes:
node.netdata_unpublish_dnssrp()
self.simulator.go(WAIT_TIME)
num -= 1
services = leader.get_services()
self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_ANYCAST))
self.verify_anycast_services(services)
#---------------------------------------------------------------------------------
# DNS/SRP service data unicast entries - equal version number
num = 0
for node in routers:
@@ -295,7 +345,33 @@ class NetDataPublisher(thread_cert.TestCase):
self.assertEqual(node.srp_server_get_state(), 'disabled')
#---------------------------------------------------------------------------------
# DNS/SRP server data unicast entries
# DNS/SRP service data unicast entries - different version numbers
num = 0
for node in routers:
# Use `num` as version.
node.netdata_publish_dnssrp_unicast(DNSSRP_ADDRESS, DNSSRP_PORT, num)
self.simulator.go(WAIT_TIME)
num += 1
services = leader.get_services()
self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST))
self.verify_unicast_services(services)
# The most recent service should win as it uses a higher version
# number. Validate that the 'services' list contains the service
# from this node by checking the service RLOC16.
node_rloc16 = node.get_addr16()
self.assertTrue(any(int(service[4], 16) == node_rloc16 for service in services))
for node in reversed(routers):
node.netdata_unpublish_dnssrp()
self.simulator.go(WAIT_TIME)
num -= 1
services = leader.get_services()
self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST))
self.verify_unicast_services(services)
#---------------------------------------------------------------------------------
# DNS/SRP server data unicast entries - equal version number
num = 0
for node in routers:
@@ -325,6 +401,55 @@ class NetDataPublisher(thread_cert.TestCase):
node.srp_server_set_enabled(False)
self.assertEqual(node.srp_server_get_state(), 'disabled')
#---------------------------------------------------------------------------------
# DNS/SRP server data unicast entries - different version numbers
num = 0
for node in routers:
node.netdata_publish_dnssrp_unicast_mleid(DNSSRP_PORT, num)
self.simulator.go(WAIT_TIME)
num += 1
services = leader.get_services()
self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST))
self.verify_unicast_services(services)
# The most recent service should win as it uses a higher version
# number. Validate that the 'services' list contains the service
# from this node by checking the service RLOC16.
node_rloc16 = node.get_addr16()
self.assertTrue(any(int(service[4], 16) == node_rloc16 for service in services))
for node in reversed(routers):
node.netdata_unpublish_dnssrp()
self.simulator.go(WAIT_TIME)
num -= 1
services = leader.get_services()
self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST))
self.verify_unicast_services(services)
# Repeat the same test steps, but start with larger version
# numbers first.
num = 0
for node in routers:
node.netdata_publish_dnssrp_unicast_mleid(DNSSRP_PORT, 20 - num)
self.simulator.go(WAIT_TIME)
num += 1
services = leader.get_services()
self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST))
self.verify_unicast_services(services)
# The service from first router should win as it uses the highest
# version number.
first_router_rloc16 = routers[0].get_addr16()
self.assertTrue(any(int(service[4], 16) == first_router_rloc16 for service in services))
for node in routers:
node.netdata_unpublish_dnssrp()
self.simulator.go(WAIT_TIME)
num -= 1
services = leader.get_services()
self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST))
self.verify_unicast_services(services)
#---------------------------------------------------------------------------------
# DNS/SRP server data unicast vs anycast