diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 02e5b89b7..e0ff78f8b 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (217) +#define OPENTHREAD_API_VERSION (218) /** * @addtogroup api-instance diff --git a/include/openthread/srp_client.h b/include/openthread/srp_client.h index 1dc0c9e3e..bb291d414 100644 --- a/include/openthread/srp_client.h +++ b/include/openthread/srp_client.h @@ -75,8 +75,9 @@ typedef enum typedef struct otSrpClientHostInfo { const char * mName; ///< Host name (label) string (NULL if not yet set). - const otIp6Address * mAddresses; ///< Pointer to an array of host IPv6 addresses (NULL if not yet set). + const otIp6Address * mAddresses; ///< Array of host IPv6 addresses (NULL if not set or auto address is enabled). uint8_t mNumAddresses; ///< Number of IPv6 addresses in `mAddresses` array. + bool mAutoAddress; ///< Indicates whether auto address mode is enabled or not. otSrpClientItemState mState; ///< Host info state. } otSrpClientHostInfo; @@ -428,6 +429,27 @@ const otSrpClientHostInfo *otSrpClientGetHostInfo(otInstance *aInstance); */ otError otSrpClientSetHostName(otInstance *aInstance, const char *aName); +/** + * This function enables auto host address mode. + * + * When enabled host IPv6 addresses are automatically set by SRP client using all the unicast addresses on Thread netif + * excluding all link-local and mesh-local addresses. If there is no valid address, then Mesh Local EID address is + * added. The SRP client will automatically re-register when/if addresses on Thread netif are updated (new addresses + * are added or existing addresses are removed). + * + * The auto host address mode can be enabled before start or during operation of SRP client except when the host info + * is being removed (client is busy handling a remove request from an call to `otSrpClientRemoveHostAndServices()` and + * host info still being in either `STATE_TO_REMOVE` or `STATE_REMOVING` states). + * + * After auto host address mode is enabled, it can be disabled by a call to `otSrpClientSetHostAddresses()` which + * then explicitly sets the host addresses. + * + * @retval OT_ERROR_NONE Successfully enabled auto host address mode. + * @retval OT_ERROR_INVALID_STATE Host is being removed and therefore cannot enable auto host address mode. + * + */ +otError otSrpClientEnableAutoHostAddress(otInstance *aInstance); + /** * This function sets/updates the list of host IPv6 address. * @@ -442,6 +464,9 @@ otError otSrpClientSetHostName(otInstance *aInstance, const char *aName); * After a successful call to this function, `otSrpClientCallback` will be called to report the status of the address * registration with SRP server. * + * Calling this function disables auto host address mode if it was previously enabled from a successful call to + * `otSrpClientEnableAutoHostAddress()`. + * * @param[in] aInstance A pointer to the OpenThread instance. * @param[in] aIp6Addresses A pointer to the an array containing the host IPv6 addresses. * @param[in] aNumAddresses The number of addresses in the @p aIp6Addresses array. diff --git a/src/cli/README_SRP_CLIENT.md b/src/cli/README_SRP_CLIENT.md index ab5dcdb77..ae48fbf1e 100644 --- a/src/cli/README_SRP_CLIENT.md +++ b/src/cli/README_SRP_CLIENT.md @@ -139,6 +139,14 @@ name:"dev4312", state:Registered, addrs:[fd00:0:0:0:0:0:0:1234, fd00:0:0:0:0:0:0 Done ``` +When auto host address mode is enabled. + +```bash +srp client host +name:"dev1234", state:Registered, addrs:auto +Done +``` + ### host name Usage: `srp client host name [name]` @@ -160,9 +168,17 @@ Done ### host address -Usage : `srp client host address [
...]` +Usage : `srp client host address [auto |
...]` -Get the list of host addresses. +Indicate auto address mode is enabled. + +```bash +> srp client host address +auto +Done +``` + +Get the list of host addresses (when auto host address is not enabled). ```bash > srp client host address @@ -171,7 +187,14 @@ fd00:0:0:0:0:0:0:beef Done ``` -Set the list of host addresses (can be set while client is running to update the host addresses) +Enable auto host address mode. When enabled client will automatically use all Thread netif unicast addresses excluding all link-local and mesh-local addresses. If there is no valid address, then Mesh Local EID address is added. SRP client will automatically re-register if/when addresses on Thread netif get changed (e.g., new address is added or existing address is removed). + +```bash +> srp client host address auto +Done +``` + +Explicitly set the list of host addresses (can be set while client is running to update the host addresses), also disabled auto host address mode. ```bash > srp client host address fd00::cafe diff --git a/src/cli/cli_srp_client.cpp b/src/cli/cli_srp_client.cpp index 4d81cd901..0732ac2e4 100644 --- a/src/cli/cli_srp_client.cpp +++ b/src/cli/cli_srp_client.cpp @@ -160,10 +160,21 @@ template <> otError SrpClient::Process(Arg aArgs[]) { const otSrpClientHostInfo *hostInfo = otSrpClientGetHostInfo(GetInstancePtr()); - for (uint8_t index = 0; index < hostInfo->mNumAddresses; index++) + if (hostInfo->mAutoAddress) { - OutputIp6AddressLine(hostInfo->mAddresses[index]); + OutputLine("auto"); } + else + { + for (uint8_t index = 0; index < hostInfo->mNumAddresses; index++) + { + OutputIp6AddressLine(hostInfo->mAddresses[index]); + } + } + } + else if (aArgs[1] == "auto") + { + error = otSrpClientEnableAutoHostAddress(GetInstancePtr()); } else { @@ -447,19 +458,28 @@ void SrpClient::OutputHostInfo(uint8_t aIndentSize, const otSrpClientHostInfo &a OutputFormat("(null)"); } - OutputFormat(", state:%s, addrs:[", otSrpClientItemStateToString(aHostInfo.mState)); + OutputFormat(", state:%s, addrs:", otSrpClientItemStateToString(aHostInfo.mState)); - for (uint8_t index = 0; index < aHostInfo.mNumAddresses; index++) + if (aHostInfo.mAutoAddress) { - if (index > 0) + OutputLine("auto"); + } + else + { + OutputFormat("["); + + for (uint8_t index = 0; index < aHostInfo.mNumAddresses; index++) { - OutputFormat(", "); + if (index > 0) + { + OutputFormat(", "); + } + + OutputIp6Address(aHostInfo.mAddresses[index]); } - OutputIp6Address(aHostInfo.mAddresses[index]); + OutputLine("]"); } - - OutputLine("]"); } void SrpClient::OutputServiceList(uint8_t aIndentSize, const otSrpClientService *aServices) diff --git a/src/core/api/srp_client_api.cpp b/src/core/api/srp_client_api.cpp index a0c16e3d9..3ec356f34 100644 --- a/src/core/api/srp_client_api.cpp +++ b/src/core/api/srp_client_api.cpp @@ -124,6 +124,11 @@ otError otSrpClientSetHostName(otInstance *aInstance, const char *aName) return AsCoreType(aInstance).Get().SetHostName(aName); } +otError otSrpClientEnableAutoHostAddress(otInstance *aInstance) +{ + return AsCoreType(aInstance).Get().EnableAutoHostAddress(); +} + otError otSrpClientSetHostAddresses(otInstance *aInstance, const otIp6Address *aIp6Addresses, uint8_t aNumAddresses) { return AsCoreType(aInstance).Get().SetHostAddresses(AsCoreTypePtr(aIp6Addresses), aNumAddresses); diff --git a/src/core/net/srp_client.cpp b/src/core/net/srp_client.cpp index e148d659a..a65fe9251 100644 --- a/src/core/net/srp_client.cpp +++ b/src/core/net/srp_client.cpp @@ -76,10 +76,20 @@ void Client::HostInfo::SetState(ItemState aState) } } +void Client::HostInfo::EnableAutoAddress(void) +{ + mAddresses = nullptr; + mNumAddresses = 0; + mAutoAddress = true; + + LogInfo("HostInfo enabled auto address", GetNumAddresses()); +} + void Client::HostInfo::SetAddresses(const Ip6::Address *aAddresses, uint8_t aNumAddresses) { mAddresses = aAddresses; mNumAddresses = aNumAddresses; + mAutoAddress = false; LogInfo("HostInfo set %d addrs", GetNumAddresses()); @@ -239,6 +249,7 @@ Client::Client(Instance &aInstance) , mState(kStateStopped) , mTxFailureRetryCount(0) , mShouldRemoveKeyLease(false) + , mAutoHostAddressAddedMeshLocal(false) #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE , mServiceKeyRecordEnabled(false) #endif @@ -415,6 +426,22 @@ void Client::HandleNotifierEvents(Events aEvents) ProcessAutoStart(); } #endif + + if (mHostInfo.IsAutoAddressEnabled()) + { + Events::Flags eventFlags = (kEventIp6AddressAdded | kEventIp6AddressRemoved); + + if (mAutoHostAddressAddedMeshLocal) + { + eventFlags |= kEventThreadMeshLocalAddrChanged; + } + + if (aEvents.ContainsAny(eventFlags)) + { + IgnoreError(UpdateHostInfoStateOnAddressChange()); + UpdateState(); + } + } } void Client::HandleRoleChanged(void) @@ -466,11 +493,37 @@ exit: return error; } +Error Client::EnableAutoHostAddress(void) +{ + Error error = kErrorNone; + + VerifyOrExit(!mHostInfo.IsAutoAddressEnabled()); + SuccessOrExit(error = UpdateHostInfoStateOnAddressChange()); + + mHostInfo.EnableAutoAddress(); + UpdateState(); + +exit: + return error; +} + Error Client::SetHostAddresses(const Ip6::Address *aAddresses, uint8_t aNumAddresses) { Error error = kErrorNone; VerifyOrExit((aAddresses != nullptr) && (aNumAddresses > 0), error = kErrorInvalidArgs); + SuccessOrExit(error = UpdateHostInfoStateOnAddressChange()); + + mHostInfo.SetAddresses(aAddresses, aNumAddresses); + UpdateState(); + +exit: + return error; +} + +Error Client::UpdateHostInfoStateOnAddressChange(void) +{ + Error error = kErrorNone; VerifyOrExit((mHostInfo.GetState() != kToRemove) && (mHostInfo.GetState() != kRemoving), error = kErrorInvalidState); @@ -484,9 +537,6 @@ Error Client::SetHostAddresses(const Ip6::Address *aAddresses, uint8_t aNumAddre mHostInfo.SetState(kToRefresh); } - mHostInfo.SetAddresses(aAddresses, aNumAddresses); - UpdateState(); - exit: return error; } @@ -1008,10 +1058,9 @@ exit: return error; } -Error Client::AppendHostDescriptionInstruction(Message &aMessage, Info &aInfo) const +Error Client::AppendHostDescriptionInstruction(Message &aMessage, Info &aInfo) { - Error error = kErrorNone; - Dns::ResourceRecord rr; + Error error = kErrorNone; //---------------------------------- // Host Description Instruction @@ -1024,16 +1073,37 @@ Error Client::AppendHostDescriptionInstruction(Message &aMessage, Info &aInfo) c // AAAA RRs - rr.Init(Dns::ResourceRecord::kTypeAaaa); - rr.SetTtl(GetTtl()); - rr.SetLength(sizeof(Ip6::Address)); - - for (uint8_t index = 0; index < mHostInfo.GetNumAddresses(); index++) + if (mHostInfo.IsAutoAddressEnabled()) { - SuccessOrExit(error = AppendHostName(aMessage, aInfo)); - SuccessOrExit(error = aMessage.Append(rr)); - SuccessOrExit(error = aMessage.Append(mHostInfo.GetAddress(index))); - aInfo.mRecordCount++; + // Append all addresses on Thread netif excluding link-local and + // mesh-local addresses. If no address is appended, we include + // the mesh local address. + + mAutoHostAddressAddedMeshLocal = true; + + for (const Ip6::Netif::UnicastAddress &unicastAddress : Get().GetUnicastAddresses()) + { + if (unicastAddress.GetAddress().IsLinkLocal() || + Get().IsMeshLocalAddress(unicastAddress.GetAddress())) + { + continue; + } + + SuccessOrExit(error = AppendAaaaRecord(unicastAddress.GetAddress(), aMessage, aInfo)); + mAutoHostAddressAddedMeshLocal = false; + } + + if (mAutoHostAddressAddedMeshLocal) + { + SuccessOrExit(error = AppendAaaaRecord(Get().GetMeshLocal64(), aMessage, aInfo)); + } + } + else + { + for (uint8_t index = 0; index < mHostInfo.GetNumAddresses(); index++) + { + SuccessOrExit(error = AppendAaaaRecord(mHostInfo.GetAddress(index), aMessage, aInfo)); + } } // KEY RR @@ -1045,6 +1115,24 @@ exit: return error; } +Error Client::AppendAaaaRecord(const Ip6::Address &aAddress, Message &aMessage, Info &aInfo) const +{ + Error error; + Dns::ResourceRecord rr; + + rr.Init(Dns::ResourceRecord::kTypeAaaa); + rr.SetTtl(GetTtl()); + rr.SetLength(sizeof(Ip6::Address)); + + SuccessOrExit(error = AppendHostName(aMessage, aInfo)); + SuccessOrExit(error = aMessage.Append(rr)); + SuccessOrExit(error = aMessage.Append(aAddress)); + aInfo.mRecordCount++; + +exit: + return error; +} + Error Client::AppendKeyRecord(Message &aMessage, Info &aInfo) const { Error error; @@ -1519,7 +1607,7 @@ void Client::UpdateState(void) // host address, otherwise no need to send SRP update message. // The exception is when removing host info where we allow // for empty service list. - VerifyOrExit(!mServices.IsEmpty() && (mHostInfo.GetNumAddresses() > 0)); + VerifyOrExit(!mServices.IsEmpty() && (mHostInfo.IsAutoAddressEnabled() || (mHostInfo.GetNumAddresses() > 0))); // Fall through diff --git a/src/core/net/srp_client.hpp b/src/core/net/srp_client.hpp index 8e7cca7dc..4a3502bb6 100644 --- a/src/core/net/srp_client.hpp +++ b/src/core/net/srp_client.hpp @@ -103,7 +103,7 @@ public: * This type represents an SRP client host info. * */ - class HostInfo : public otSrpClientHostInfo, public Clearable + class HostInfo : public otSrpClientHostInfo, private Clearable { friend class Client; @@ -128,6 +128,15 @@ public: */ const char *GetName(void) const { return mName; } + /** + * This method indicates whether or not the host auto address mode is enabled. + * + * @retval TRUE If the auto address mode is enabled. + * @retval FALSE If the auto address mode is disabled. + * + */ + bool IsAutoAddressEnabled(void) const { return mAutoAddress; } + /** * This method gets the number of host IPv6 addresses. * @@ -158,6 +167,7 @@ public: void SetName(const char *aName) { mName = aName; } void SetState(ItemState aState); void SetAddresses(const Ip6::Address *aAddresses, uint8_t aNumAddresses); + void EnableAutoAddress(void); }; /** @@ -489,16 +499,16 @@ public: const HostInfo &GetHostInfo(void) const { return mHostInfo; } /** - * This function sets the host name label. + * This method sets the host name label. * - * After a successful call to this function, `Callback` will be called to report the status of host info + * After a successful call to this method, `Callback` will be called to report the status of host info * registration with SRP server. * * The host name can be set before client is started or after start but before host info is registered with server * (host info should be in either `kToAdd` or `kRemoved`). * * @param[in] aName A pointer to host name label string (MUST NOT be NULL). Pointer the string buffer MUST - * persist and remain valid and constant after return from this function. + * persist and remain valid and constant after return from this method. * * @retval kErrorNone The host name label was set successfully. * @retval kErrorInvalidArgs The @p aName is NULL. @@ -507,6 +517,27 @@ public: */ Error SetHostName(const char *aName); + /** + * This method enables auto host address mode. + * + * When enabled host IPv6 addresses are automatically set by SRP client using all the unicast addresses on Thread + * netif excluding the link-local and mesh-local addresses. If there is no valid address, then Mesh Local EID + * address is added. The SRP client will automatically re-register when/if addresses on Thread netif are updated + * (new addresses are added or existing addresses are removed). + * + * The auto host address mode can be enabled before start or during operation of SRP client except when the host + * info is being removed (client is busy handling a remove request from an call to `RemoveHostAndServices()` and + * host info still being in either `kStateToRemove` or `kStateRemoving` states). + * + * After auto host address mode is enabled, it can be disabled by a call to `SetHostAddresses()` which then + * explicitly sets the host addresses. + * + * @retval kErrorNone Successfully enabled auto host address mode. + * @retval kErrorInvalidState Host is being removed and therefore cannot enable auto host address mode. + * + */ + Error EnableAutoHostAddress(void); + /** * This method sets/updates the list of host IPv6 address. * @@ -518,6 +549,9 @@ public: * After a successful call to this method, `Callback` will be called to report the status of the address * registration with SRP server. * + * Calling this method disables auto host address mode if it was previously enabled from a successful call to + * `EnableAutoHostAddress()`. + * * @param[in] aAddresses A pointer to the an array containing the host IPv6 addresses. * @param[in] aNumAddresses The number of addresses in the @p aAddresses array. * @@ -884,6 +918,7 @@ private: void Pause(void); void HandleNotifierEvents(Events aEvents); void HandleRoleChanged(void); + Error UpdateHostInfoStateOnAddressChange(void); void UpdateServiceStateToRemove(Service &aService); State GetState(void) const { return mState; } void SetState(State aState); @@ -895,10 +930,11 @@ private: Error PrepareUpdateMessage(Message &aMessage); Error ReadOrGenerateKey(Crypto::Ecdsa::P256::KeyPair &aKeyPair); Error AppendServiceInstructions(Service &aService, Message &aMessage, Info &aInfo); - Error AppendHostDescriptionInstruction(Message &aMessage, Info &aInfo) const; + Error AppendHostDescriptionInstruction(Message &aMessage, Info &aInfo); Error AppendKeyRecord(Message &aMessage, Info &aInfo) const; Error AppendDeleteAllRrsets(Message &aMessage) const; Error AppendHostName(Message &aMessage, Info &aInfo, bool aDoNotCompress = false) const; + Error AppendAaaaRecord(const Ip6::Address &aAddress, Message &aMessage, Info &aInfo) const; Error AppendUpdateLeaseOptRecord(Message &aMessage) const; Error AppendSignature(Message &aMessage, Info &aInfo); void UpdateRecordLengthInMessage(Dns::ResourceRecord &aRecord, uint16_t aOffset, Message &aMessage) const; @@ -938,6 +974,7 @@ private: State mState; uint8_t mTxFailureRetryCount : 4; bool mShouldRemoveKeyLease : 1; + bool mAutoHostAddressAddedMeshLocal : 1; #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE bool mServiceKeyRecordEnabled : 1; #endif diff --git a/tests/scripts/thread-cert/Makefile.am b/tests/scripts/thread-cert/Makefile.am index 72906e699..200c46a3c 100644 --- a/tests/scripts/thread-cert/Makefile.am +++ b/tests/scripts/thread-cert/Makefile.am @@ -188,6 +188,7 @@ EXTRA_DIST = \ test_router_upgrade.py \ test_service.py \ test_set_mliid.py \ + test_srp_auto_host_address.py \ test_srp_auto_start_mode.py \ test_srp_client_remove_host.py \ test_srp_client_save_server_info.py \ @@ -264,6 +265,7 @@ check_SCRIPTS = \ test_router_reattach.py \ test_router_upgrade.py \ test_service.py \ + test_srp_auto_host_address.py \ test_srp_auto_start_mode.py \ test_srp_client_remove_host.py \ test_srp_client_save_server_info.py \ diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 9b317bac5..ccc400ed2 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -1146,6 +1146,10 @@ class NodeImpl: self.send_command(f'srp client host clear') self._expect_done() + def srp_client_enable_auto_host_address(self): + self.send_command(f'srp client host address auto') + self._expect_done() + def srp_client_set_host_address(self, *addrs: str): self.send_command(f'srp client host address {" ".join(addrs)}') self._expect_done() diff --git a/tests/scripts/thread-cert/test_srp_auto_host_address.py b/tests/scripts/thread-cert/test_srp_auto_host_address.py new file mode 100755 index 000000000..bb9ac6e1e --- /dev/null +++ b/tests/scripts/thread-cert/test_srp_auto_host_address.py @@ -0,0 +1,258 @@ +#!/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 SRP client auto host address mode. +# +# Topology: +# SRP client (leader) +# | +# | +# SRP server (router) +# + +CLIENT = 1 +SERVER = 2 + + +class SrpAutoHostAddress(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + SUPPORT_NCP = False + + TOPOLOGY = { + CLIENT: { + 'name': 'SRP_CLIENT', + 'mode': 'rdn', + }, + SERVER: { + 'name': 'SRP_SERVER', + 'mode': 'rdn', + }, + } + + def test(self): + client = self.nodes[CLIENT] + server = self.nodes[SERVER] + + #------------------------------------------------------------------- + # Form the network. + + client.srp_server_set_enabled(False) + client.start() + self.simulator.go(15) + self.assertEqual(client.get_state(), 'leader') + + server.start() + self.simulator.go(5) + self.assertEqual(server.get_state(), 'router') + + #------------------------------------------------------------------- + # Enable SRP server + + server.srp_server_set_enabled(True) + self.simulator.go(5) + + #------------------------------------------------------------------- + # Enable auto start mode on SRP client + + self.assertEqual(client.srp_client_get_state(), 'Disabled') + client.srp_client_enable_auto_start_mode() + self.assertEqual(client.srp_client_get_auto_start_mode(), 'Enabled') + self.simulator.go(2) + + self.assertEqual(client.srp_client_get_state(), 'Enabled') + + #------------------------------------------------------------------- + # Set host name and enable auto host address on client + + client.srp_client_set_host_name('host') + client.srp_client_enable_auto_host_address() + + #------------------------------------------------------------------- + # Register a service on client + + client.srp_client_add_service('test_srv', '_test._udo', 12345, 0, 0) + self.simulator.go(2) + self.check_registered_addresses(client, server) + + #------------------------------------------------------------------- + # Add an address and check the SRP client re-registered and updated + # server with new address. + + client.add_ipaddr('fd00:1:2:3:4:5:6:7') + + self.simulator.go(5) + client_addresses = [addr.strip() for addr in client.get_addrs()] + self.assertIn('fd00:1:2:3:4:5:6:7', client_addresses) + self.check_registered_addresses(client, server) + + #------------------------------------------------------------------- + # Remove the address and check the SRP client re-registered and updated + # server. + + client.del_ipaddr('fd00:1:2:3:4:5:6:7') + + self.simulator.go(5) + client_addresses = [addr.strip() for addr in client.get_addrs()] + self.assertNotIn('fd00:1:2:3:4:5:6:7', client_addresses) + self.check_registered_addresses(client, server) + + #------------------------------------------------------------------- + # Add an SLAAC on-mesh prefix (which will trigger an address to be + # added) and check that the SRP client re-registered and updated + # server with the new address. + + client.add_prefix('fd00:abba:cafe:bee::/64', 'paos') + client.register_netdata() + self.simulator.go(5) + + slaac_addr = [addr.strip() for addr in client.get_addrs() if addr.strip().startswith('fd00:abba:cafe:bee:')] + self.assertEqual(len(slaac_addr), 1) + self.check_registered_addresses(client, server) + + #------------------------------------------------------------------- + # Add another SLAAC on-mesh prefix and check that the SRP client + # re-registered and updated server with all address. + + client.add_prefix('fd00:9:8:7::/64', 'paos') + client.register_netdata() + self.simulator.go(5) + + slaac_addr = [addr.strip() for addr in client.get_addrs() if addr.strip().startswith('fd00:9:8:7:')] + self.assertEqual(len(slaac_addr), 1) + self.check_registered_addresses(client, server) + + #------------------------------------------------------------------- + # Remove the on-mesh prefix (which will trigger an address to be + # removed) and check that the SRP client re-registered and updated + # server with the remaining address. + + client.remove_prefix('fd00:abba:cafe:bee::/64') + client.register_netdata() + self.simulator.go(5) + + slaac_addr = [addr.strip() for addr in client.get_addrs() if addr.strip().startswith('fd00:abba:cafe:bee:')] + self.assertEqual(len(slaac_addr), 0) + self.check_registered_addresses(client, server) + + #------------------------------------------------------------------- + # Remove the next on-mesh prefix. Check that SRP client re-registered + # now with only ML-EID. + + client.remove_prefix('fd00:9:8:7::/64') + client.register_netdata() + self.simulator.go(5) + + slaac_addr = [addr.strip() for addr in client.get_addrs() if addr.strip().startswith('fd00:9:8:7:')] + self.assertEqual(len(slaac_addr), 0) + self.check_registered_addresses(client, server) + + #------------------------------------------------------------------- + # Explicitly set the host addresses (which disables the auto host + # address mode) and check that only the new addresses are registered. + + client.srp_client_set_host_address('fd00:f:e:d:c:b:a:9') + self.simulator.go(5) + + self.assertEqual(client.srp_client_get_host_state(), 'Registered') + server_hosts = server.srp_server_get_hosts() + self.assertEqual(len(server_hosts), 1) + server_host = server_hosts[0] + self.assertEqual(server_host['deleted'], 'false') + self.assertEqual(server_host['fullname'], 'host.default.service.arpa.') + host_addresses = [addr.strip() for addr in server_host['addresses']] + self.assertEqual(len(host_addresses), 1) + self.assertEqual(host_addresses[0], 'fd00:f:e:d:c:b:a:9') + + #------------------------------------------------------------------- + # Re-enable auto host address mode and check that addresses are + # updated and registered properly. + + client.srp_client_enable_auto_host_address() + self.simulator.go(5) + self.check_registered_addresses(client, server) + + def check_registered_addresses(self, client, server): + # Ensure client has registered successfully. + self.assertEqual(client.srp_client_get_host_state(), 'Registered') + + # Check the host info on server. + server_hosts = server.srp_server_get_hosts() + self.assertEqual(len(server_hosts), 1) + server_host = server_hosts[0] + self.assertEqual(server_host['deleted'], 'false') + self.assertEqual(server_host['fullname'], 'host.default.service.arpa.') + + # Check the host addresses on server to match client. + + host_addresses = [addr.strip() for addr in server_host['addresses']] + client_addresses = [addr.strip() for addr in client.get_addrs()] + + # All registered addresses must be in client list of addresses. + + for addr in host_addresses: + self.assertIn(addr, client_addresses) + + # All addresses on client excluding link-local and mesh-local + # addresses must be seen on server side. But if there was + # no address, then mesh-local address should be the only + # one registered. + + client_mleid = client.get_mleid() + checked_address = False + + for addr in client_addresses: + if not self.is_address_link_local(addr) and not self.is_address_locator(addr) and addr != client_mleid: + self.assertIn(addr, host_addresses) + checked_address = True + + if not checked_address: + self.assertEqual(len(host_addresses), 1) + self.assertIn(client_mleid, host_addresses) + + def is_address_locator(self, addr): + # Checks if an IPv6 address is a locator (IID should match `0:ff:fe00:xxxx`) + u32s = addr.split(':') + self.assertEqual(len(u32s), 8) + return ':'.join(u32s[4:]).startswith('0:ff:fe00:') + + def is_address_link_local(self, addr): + # Checks if an IPv6 address is link-local + return addr.startswith('fe80:') + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/toranj/cli/cli.py b/tests/toranj/cli/cli.py index 5e24bedec..3bfb7a830 100644 --- a/tests/toranj/cli/cli.py +++ b/tests/toranj/cli/cli.py @@ -329,6 +329,9 @@ class Node(object): def srp_client_clear_host(self): self._cli_no_output('srp client host clear') + def srp_client_enable_auto_host_address(self): + self._cli_no_output('srp client host address auto') + def srp_client_set_host_address(self, *addrs): self._cli_no_output('srp client host address', *addrs)