[srp-client] new feature to allow lease and key lease per service (#8211)

This commit add a new feature in `Srp::Client` to allow user to
specify the lease and/or key lease intervals explicitly per service.
The lease intervals in a service can be left unspecified(set to zero)
in which case the default lease intervals will be used.

In an SRP Update message, the lease info is included in additional
section in an Update Lease Option record. Therefore the lease values
are applicable to the entire message and all its records. If we have
services with different lease intervals, they need to be registered
in separate messages. This commit updates `Srp::Client` to implement
such a mechanism. When preparing an SRP message, we first determine
the lease and key lease intervals and ensure the services using
matching intervals are included in the message.

This commit also updates and simplifies the "single service mode"
functionality to use the newly added mechanism for tracking which
services are included in the message.

This commit adds `test_srp_register_services_diff_lease` which covers
the behavior of the newly added mechanisms.
This commit is contained in:
Abtin Keshavarzian
2022-10-06 18:31:25 -07:00
committed by GitHub
parent a34d1176b9
commit c4b6206f16
13 changed files with 871 additions and 156 deletions
+2
View File
@@ -198,6 +198,7 @@ EXTRA_DIST = \
test_srp_many_services_mtu_check.py \
test_srp_name_conflicts.py \
test_srp_register_single_service.py \
test_srp_register_services_diff_lease.py \
test_srp_server_anycast_mode.py \
test_srp_server_reboot_port.py \
test_srp_sub_type.py \
@@ -278,6 +279,7 @@ check_SCRIPTS = \
test_srp_many_services_mtu_check.py \
test_srp_name_conflicts.py \
test_srp_register_single_service.py \
test_srp_register_services_diff_lease.py \
test_srp_server_anycast_mode.py \
test_srp_server_reboot_port.py \
test_srp_sub_type.py \
+28 -4
View File
@@ -1023,6 +1023,8 @@ class NodeImpl:
'priority': '0',
'weight': '0',
'ttl': '7200',
'lease': '7200',
'key-lease': '7200',
'TXT': ['abc=010203'],
'host_fullname': 'my-host.default.service.arpa.',
'host': 'my-host',
@@ -1035,6 +1037,7 @@ class NodeImpl:
cmd = 'srp server service'
self.send_command(cmd)
lines = self._expect_command_output()
service_list = []
while lines:
service = {}
@@ -1049,8 +1052,8 @@ class NodeImpl:
service_list.append(service)
continue
# 'subtypes', port', 'priority', 'weight', 'ttl'
for i in range(0, 5):
# 'subtypes', port', 'priority', 'weight', 'ttl', 'lease', and 'key-lease'
for i in range(0, 7):
key_value = lines.pop(0).strip().split(':')
service[key_value[0].strip()] = key_value[1].strip()
@@ -1163,11 +1166,22 @@ class NodeImpl:
self.send_command(f'srp client host address')
self._expect_done()
def srp_client_add_service(self, instance_name, service_name, port, priority=0, weight=0, txt_entries=[]):
def srp_client_add_service(self,
instance_name,
service_name,
port,
priority=0,
weight=0,
txt_entries=[],
lease=0,
key_lease=0):
txt_record = "".join(self._encode_txt_entry(entry) for entry in txt_entries)
if txt_record == '':
txt_record = '-'
instance_name = self._escape_escapable(instance_name)
self.send_command(
f'srp client service add {instance_name} {service_name} {port} {priority} {weight} {txt_record}')
f'srp client service add {instance_name} {service_name} {port} {priority} {weight} {txt_record} {lease} {key_lease}'
)
self._expect_done()
def srp_client_remove_service(self, instance_name, service_name):
@@ -1194,6 +1208,16 @@ class NodeImpl:
self.send_command(cmd)
return int(self._expect_result('\d+'))
def srp_client_set_key_lease_interval(self, leaseinterval: int):
cmd = f'srp client keyleaseinterval {leaseinterval}'
self.send_command(cmd)
self._expect_done()
def srp_client_get_key_lease_interval(self) -> int:
cmd = 'srp client keyleaseinterval'
self.send_command(cmd)
return int(self._expect_result('\d+'))
def srp_client_set_ttl(self, ttl: int):
cmd = f'srp client ttl {ttl}'
self.send_command(cmd)
@@ -0,0 +1,481 @@
#!/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 ipaddress
import unittest
import command
import config
import thread_cert
# Test description:
#
# This test verifies the SRP client and server behavior when services
# with different lease (and/or key lease) intervals are registered.
#
# Topology:
#
# LEADER (SRP server)
# |
# |
# ROUTER (SRP client)
#
SERVER = 1
CLIENT = 2
class SrpRegisterServicesDiffLease(thread_cert.TestCase):
USE_MESSAGE_FACTORY = False
SUPPORT_NCP = False
TOPOLOGY = {
SERVER: {
'name': 'SRP_SERVER',
'mode': 'rdn',
},
CLIENT: {
'name': 'SRP_CLIENT',
'mode': 'rdn',
},
}
def test(self):
server = self.nodes[SERVER]
client = self.nodes[CLIENT]
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Start the server and client.
server.start()
self.simulator.go(config.LEADER_STARTUP_DELAY)
self.assertEqual(server.get_state(), 'leader')
client.start()
self.simulator.go(config.ROUTER_STARTUP_DELAY)
self.assertEqual(client.get_state(), 'router')
server.srp_server_set_enabled(True)
client.srp_client_enable_auto_start_mode()
self.simulator.go(5)
client.srp_client_set_host_name('host')
client.srp_client_enable_auto_host_address()
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Add a service with specific lease and key lease and verify that
# it is successfully registered and seen with same lease/key-lease
# on server.
client.srp_client_add_service('ins1', '_test._udp', 1111, lease=60, key_lease=800)
self.simulator.go(5)
self.check_services_on_client(client, 1)
services = server.srp_server_get_services()
self.assertEqual(len(services), 1)
service = services[0]
self.assertEqual(service['fullname'], 'ins1._test._udp.default.service.arpa.')
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 60)
self.assertEqual(int(service['lease']), 60)
self.assertEqual(int(service['key-lease']), 800)
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Register two more services with different lease intervals.
client.srp_client_add_service('ins2', '_test._udp', 2222, lease=30, key_lease=200)
client.srp_client_add_service('ins3', '_test._udp', 3333, lease=100, key_lease=1000)
self.simulator.go(10)
self.check_services_on_client(client, 3)
server_services = server.srp_server_get_services()
self.assertEqual(len(server_services), 3)
for service in server_services:
if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 60)
self.assertEqual(int(service['lease']), 60)
self.assertEqual(int(service['key-lease']), 800)
elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 30)
self.assertEqual(int(service['lease']), 30)
self.assertEqual(int(service['key-lease']), 200)
elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 100)
self.assertEqual(int(service['lease']), 100)
self.assertEqual(int(service['key-lease']), 1000)
else:
self.assertTrue(False)
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Wait for longest lease time to validate that all services renew their
# lease successfully.
self.simulator.go(105)
self.check_services_on_client(client, 3)
server_services = server.srp_server_get_services()
self.assertEqual(len(server_services), 3)
for service in server_services:
self.assertEqual(service['deleted'], 'false')
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Remove two services.
client.srp_client_remove_service('ins2', '_test._udp')
client.srp_client_remove_service('ins3', '_test._udp')
self.simulator.go(10)
self.check_services_on_client(client, 1)
server_services = server.srp_server_get_services()
self.assertEqual(len(server_services), 3)
for service in server_services:
if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 60)
self.assertEqual(int(service['lease']), 60)
self.assertEqual(int(service['key-lease']), 800)
elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'true')
elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'true')
else:
self.assertTrue(False)
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Wait for longer than key-lease of `ins2` service and check that it is
# removed on server.
self.simulator.go(201)
self.check_services_on_client(client, 1)
server_services = server.srp_server_get_services()
self.assertEqual(len(server_services), 2)
for service in server_services:
if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 60)
self.assertEqual(int(service['lease']), 60)
self.assertEqual(int(service['key-lease']), 800)
elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'true')
else:
self.assertTrue(False)
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Add both services again now with same lease intervals.
client.srp_client_add_service('ins2', '_test._udp', 2222, lease=30, key_lease=100)
client.srp_client_add_service('ins3', '_test._udp', 3333, lease=30, key_lease=100)
self.simulator.go(10)
self.check_services_on_client(client, 3)
server_services = server.srp_server_get_services()
self.assertEqual(len(server_services), 3)
for service in server_services:
if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 60)
self.assertEqual(int(service['lease']), 60)
self.assertEqual(int(service['key-lease']), 800)
elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 30)
self.assertEqual(int(service['lease']), 30)
self.assertEqual(int(service['key-lease']), 100)
elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 30)
self.assertEqual(int(service['lease']), 30)
self.assertEqual(int(service['key-lease']), 100)
else:
self.assertTrue(False)
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Remove `ins1` while adding a new service with same key-lease as
# `ins1` but different lease interval.
client.srp_client_remove_service('ins1', '_test._udp')
client.srp_client_add_service('ins4', '_test._udp', 4444, lease=90, key_lease=800)
self.simulator.go(5)
self.check_services_on_client(client, 3)
server_services = server.srp_server_get_services()
self.assertEqual(len(server_services), 4)
for service in server_services:
if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'true')
elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 30)
self.assertEqual(int(service['lease']), 30)
self.assertEqual(int(service['key-lease']), 100)
elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 30)
self.assertEqual(int(service['lease']), 30)
self.assertEqual(int(service['key-lease']), 100)
elif service['fullname'] == 'ins4._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 90)
self.assertEqual(int(service['lease']), 90)
self.assertEqual(int(service['key-lease']), 800)
else:
self.assertTrue(False)
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Remove two services `ins2` and `ins3` (they now have same key lease).
client.srp_client_remove_service('ins2', '_test._udp')
client.srp_client_remove_service('ins3', '_test._udp')
self.simulator.go(10)
self.check_services_on_client(client, 1)
server_services = server.srp_server_get_services()
self.assertEqual(len(server_services), 4)
for service in server_services:
if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'true')
elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'true')
elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'true')
elif service['fullname'] == 'ins4._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 90)
self.assertEqual(int(service['lease']), 90)
self.assertEqual(int(service['key-lease']), 800)
else:
self.assertTrue(False)
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Add `ins1` with key-lease smaller than lease and check that
# client handles this properly (uses the lease value for
# key-lease).
client.srp_client_add_service('ins1', '_test._udp', 1111, lease=100, key_lease=90)
self.simulator.go(10)
self.check_services_on_client(client, 2)
server_services = server.srp_server_get_services()
self.assertEqual(len(server_services), 4)
for service in server_services:
if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 100)
self.assertEqual(int(service['lease']), 100)
self.assertEqual(int(service['key-lease']), 100)
elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'true')
elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'true')
elif service['fullname'] == 'ins4._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 90)
self.assertEqual(int(service['lease']), 90)
self.assertEqual(int(service['key-lease']), 800)
else:
self.assertTrue(False)
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Change default lease and key-lease intervals on client.
client.srp_client_set_lease_interval(40)
self.assertEqual(client.srp_client_get_lease_interval(), 40)
client.srp_client_set_key_lease_interval(330)
self.assertEqual(client.srp_client_get_key_lease_interval(), 330)
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Add `ins2` and `ins3`. `ins2` specifies the key-lease explicitly but
# leaves lease as default. `ins3` does the opposite.
client.srp_client_add_service('ins2', '_test._udp', 2222, key_lease=330)
client.srp_client_add_service('ins3', '_test._udp', 3333, lease=40)
self.simulator.go(10)
self.check_services_on_client(client, 4)
server_services = server.srp_server_get_services()
self.assertEqual(len(server_services), 4)
for service in server_services:
if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 100)
self.assertEqual(int(service['lease']), 100)
self.assertEqual(int(service['key-lease']), 100)
elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 40)
self.assertEqual(int(service['lease']), 40)
self.assertEqual(int(service['key-lease']), 330)
elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 40)
self.assertEqual(int(service['lease']), 40)
self.assertEqual(int(service['key-lease']), 330)
elif service['fullname'] == 'ins4._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 90)
self.assertEqual(int(service['lease']), 90)
self.assertEqual(int(service['key-lease']), 800)
else:
self.assertTrue(False)
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Change the default lease to 50 and wait for long enough for `ins2`
# and `ins3` to do lease refresh. Validate that `ins2` now requests
# new default lease of 50 while `ins3` should stay as before.
client.srp_client_set_lease_interval(50)
self.assertEqual(client.srp_client_get_lease_interval(), 50)
self.simulator.go(45)
self.check_services_on_client(client, 4)
server_services = server.srp_server_get_services()
self.assertEqual(len(server_services), 4)
for service in server_services:
if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 100)
self.assertEqual(int(service['lease']), 100)
self.assertEqual(int(service['key-lease']), 100)
elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 50)
self.assertEqual(int(service['lease']), 50)
self.assertEqual(int(service['key-lease']), 330)
elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 40)
self.assertEqual(int(service['lease']), 40)
self.assertEqual(int(service['key-lease']), 330)
elif service['fullname'] == 'ins4._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 90)
self.assertEqual(int(service['lease']), 90)
self.assertEqual(int(service['key-lease']), 800)
else:
self.assertTrue(False)
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Change the default key lease to 30. `ins3` should adopt this but
# since it is shorter than its explicitly specified lease the
# client should use same value for both lease and key-lease.
client.srp_client_set_key_lease_interval(35)
self.assertEqual(client.srp_client_get_key_lease_interval(), 35)
self.simulator.go(45)
self.check_services_on_client(client, 4)
server_services = server.srp_server_get_services()
self.assertEqual(len(server_services), 4)
for service in server_services:
if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 100)
self.assertEqual(int(service['lease']), 100)
self.assertEqual(int(service['key-lease']), 100)
elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 50)
self.assertEqual(int(service['lease']), 50)
self.assertEqual(int(service['key-lease']), 330)
elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 40)
self.assertEqual(int(service['lease']), 40)
self.assertEqual(int(service['key-lease']), 40)
elif service['fullname'] == 'ins4._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 90)
self.assertEqual(int(service['lease']), 90)
self.assertEqual(int(service['key-lease']), 800)
else:
self.assertTrue(False)
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Change the requested TTL. Wait for long enough for all
# services to refresh and check that the new TTL is correctly
# requested by the client (when it is not larger than
# service lease).
client.srp_client_set_ttl(65)
self.assertEqual(client.srp_client_get_ttl(), 65)
self.simulator.go(110)
self.check_services_on_client(client, 4)
server_services = server.srp_server_get_services()
self.assertEqual(len(server_services), 4)
for service in server_services:
if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 65)
self.assertEqual(int(service['lease']), 100)
self.assertEqual(int(service['key-lease']), 100)
elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 50)
self.assertEqual(int(service['lease']), 50)
self.assertEqual(int(service['key-lease']), 330)
elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 40)
self.assertEqual(int(service['lease']), 40)
self.assertEqual(int(service['key-lease']), 40)
elif service['fullname'] == 'ins4._test._udp.default.service.arpa.':
self.assertEqual(service['deleted'], 'false')
self.assertEqual(int(service['ttl']), 65)
self.assertEqual(int(service['lease']), 90)
self.assertEqual(int(service['key-lease']), 800)
else:
self.assertTrue(False)
def check_services_on_client(self, client, expected_num_services):
services = client.srp_client_get_services()
self.assertEqual(len(services), expected_num_services)
for service in client.srp_client_get_services():
self.assertIn(service['state'], ['Registered', 'ToRefresh', 'Refreshing'])
if __name__ == '__main__':
unittest.main()