diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 910eefd47..682494369 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 (2) +#define OPENTHREAD_API_VERSION (3) /** * @addtogroup api-instance diff --git a/include/openthread/ip6.h b/include/openthread/ip6.h index 7e0db0d98..17454dbe8 100644 --- a/include/openthread/ip6.h +++ b/include/openthread/ip6.h @@ -57,6 +57,24 @@ extern "C" { #define OT_IP6_IID_SIZE 8 ///< Size of an IPv6 Interface Identifier (bytes) #define OT_IP6_ADDRESS_SIZE 16 ///< Size of an IPv6 address (bytes) +/** + * @struct otIp6InterfaceIdentifier + * + * This structure represents the Interface Identifier of an IPv6 address. + * + */ +OT_TOOL_PACKED_BEGIN +struct otIp6InterfaceIdentifier +{ + uint8_t m8[OT_IP6_IID_SIZE]; ///< The Interface Identifier of an IPv6 address. +} OT_TOOL_PACKED_END; + +/** + * This structure represents the Interface Identifier of an IPv6 address. + * + */ +typedef struct otIp6InterfaceIdentifier otIp6InterfaceIdentifier; + /** * @struct otIp6Address * diff --git a/include/openthread/thread.h b/include/openthread/thread.h index 034fd0a48..bcd4570ac 100644 --- a/include/openthread/thread.h +++ b/include/openthread/thread.h @@ -477,7 +477,7 @@ otError otThreadSetNetworkName(otInstance *aInstance, const char *aNetworkName); /** * Get the Thread Domain Name. * - * This function is only availble since Thread 1.2. + * This function is only available since Thread 1.2. * * @param[in] aInstance A pointer to an OpenThread instance. * @@ -491,7 +491,7 @@ const char *otThreadGetDomainName(otInstance *aInstance); /** * Set the Thread Domain Name. * - * This function is only availble since Thread 1.2. + * This function is only available since Thread 1.2. * This function succeeds only when Thread protocols are disabled. * * @param[in] aInstance A pointer to an OpenThread instance. @@ -505,6 +505,35 @@ const char *otThreadGetDomainName(otInstance *aInstance); */ otError otThreadSetDomainName(otInstance *aInstance, const char *aDomainName); +/** + * Set/Clear the Interface Identifier manually specified for the Thread Domain Unicast Address. + * + * This function is only available since Thread 1.2 when `OPENTHREAD_CONFIG_DUA_ENABLE` is enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aIid A pointer to the Interface Identifier to set or NULL to clear. + * + * @retval OT_ERROR_NONE Successfully set/cleared the Interface Identifier. + * @retval OT_ERROR_INVALID_ARGS The specified Interface Identifier is reserved. + * + * @sa otThreadGetFixedDuaInterfaceIdentifier + */ +otError otThreadSetFixedDuaInterfaceIdentifier(otInstance *aInstance, const otIp6InterfaceIdentifier *aIid); + +/** + * Get the Interface Identifier manually specified for the Thread Domain Unicast Address. + * + * This function is only available since Thread 1.2 when `OPENTHREAD_CONFIG_DUA_ENABLE` is enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns A pointer to the Interface Identifier which was set manually, or NULL if none was set. + * + * @sa otThreadSetFixedDuaInterfaceIdentifier + * + */ +const otIp6InterfaceIdentifier *otThreadGetFixedDuaInterfaceIdentifier(otInstance *aInstance); + /** * Get the thrKeySequenceCounter. * diff --git a/src/cli/README.md b/src/cli/README.md index 7ef1ac4d7..7a8be33f2 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -39,6 +39,7 @@ Done - [discover](#discover-channel) - [dns](#dns-resolve-hostname-dns-server-ip-dns-server-port) - [domainname](#domainname) +- [dua](#dua-iid) - [eidcache](#eidcache) - [eui64](#eui64) - [extaddr](#extaddr) @@ -562,6 +563,34 @@ Set the Thread Domain Name for Thread 1.2 device. Done ``` +### dua iid + +Get the Interface Identifier mannually specified for Thread Domain Unicast Address on Thread 1.2 device. + +```bash +> dua iid +0004000300020001 +Done +``` + +### dua iid \ + +Set the Interface Identifier mannually specified for Thread Domain Unicast Address on Thread 1.2 device. + +```bash +> dua iid 0004000300020001 +Done +``` + +### dua iid clear + +Clear the Interface Identifier mannually specified for Thread Domain Unicast Address on Thread 1.2 device. + +```bash +> dua iid clear +Done +``` + ### eidcache Print the EID-to-RLOC cache entries. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 4c5803115..d53b03119 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -147,6 +147,9 @@ const struct Command Interpreter::sCommands[] = { #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) {"domainname", &Interpreter::ProcessDomainName}, #endif +#if OPENTHREAD_CONFIG_DUA_ENABLE + {"dua", &Interpreter::ProcessDua}, +#endif #if OPENTHREAD_FTD {"eidcache", &Interpreter::ProcessEidCache}, #endif @@ -609,6 +612,51 @@ exit: AppendResult(error); } +#if OPENTHREAD_CONFIG_DUA_ENABLE +void Interpreter::ProcessDua(uint8_t aArgsLength, char *aArgs[]) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(aArgsLength >= 1 && strcmp(aArgs[0], "iid") == 0, error = OT_ERROR_INVALID_COMMAND); + + switch (aArgsLength) + { + case 1: + { + const otIp6InterfaceIdentifier *iid = otThreadGetFixedDuaInterfaceIdentifier(mInstance); + + if (iid != NULL) + { + OutputBytes(iid->m8, sizeof(otIp6InterfaceIdentifier)); + mServer->OutputFormat("\r\n"); + } + break; + } + case 2: + if (strcmp(aArgs[1], "clear") == 0) + { + SuccessOrExit(error = otThreadSetFixedDuaInterfaceIdentifier(mInstance, NULL)); + } + else + { + otIp6InterfaceIdentifier iid; + + VerifyOrExit(Hex2Bin(aArgs[1], iid.m8, sizeof(otIp6InterfaceIdentifier)) == + sizeof(otIp6InterfaceIdentifier), + error = OT_ERROR_INVALID_ARGS); + SuccessOrExit(error = otThreadSetFixedDuaInterfaceIdentifier(mInstance, &iid)); + } + break; + default: + error = OT_ERROR_INVALID_ARGS; + break; + } + +exit: + AppendResult(error); +} +#endif // OPENTHREAD_CONFIG_DUA_ENABLE + #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) void Interpreter::ProcessBufferInfo(uint8_t aArgsLength, char *aArgs[]) diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 02728565a..0ef44e019 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -211,6 +211,11 @@ private: #endif void ProcessDomainName(uint8_t aArgsLength, char *aArgs[]); + +#if OPENTHREAD_CONFIG_DUA_ENABLE + void ProcessDua(uint8_t aArgsLength, char *aArgs[]); +#endif + #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) #if OPENTHREAD_FTD diff --git a/src/core/api/thread_api.cpp b/src/core/api/thread_api.cpp index 684d606d7..0e1b0fb01 100644 --- a/src/core/api/thread_api.cpp +++ b/src/core/api/thread_api.cpp @@ -220,6 +220,40 @@ otError otThreadSetDomainName(otInstance *aInstance, const char *aDomainName) exit: return error; } + +#if OPENTHREAD_CONFIG_DUA_ENABLE +otError otThreadSetFixedDuaInterfaceIdentifier(otInstance *aInstance, const otIp6InterfaceIdentifier *aIid) +{ + Instance &instance = *static_cast(aInstance); + otError error = OT_ERROR_NONE; + + if (aIid) + { + error = instance.Get().SetFixedDuaInterfaceIdentifier( + *static_cast(aIid)); + } + else + { + instance.Get().ClearFixedDuaInterfaceIdentifier(); + } + + return error; +} + +const otIp6InterfaceIdentifier *otThreadGetFixedDuaInterfaceIdentifier(otInstance *aInstance) +{ + Instance & instance = *static_cast(aInstance); + const otIp6InterfaceIdentifier *iid = NULL; + + if (instance.Get().IsFixedDuaInterfaceIdentifierSet()) + { + iid = &instance.Get().GetFixedDuaInterfaceIdentifier(); + } + + return iid; +} +#endif // OPENTHREAD_CONFIG_DUA_ENABLE + #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) uint32_t otThreadGetKeySequenceCounter(otInstance *aInstance) diff --git a/src/core/net/ip6_address.cpp b/src/core/net/ip6_address.cpp index 13d306810..1f4d87bc5 100644 --- a/src/core/net/ip6_address.cpp +++ b/src/core/net/ip6_address.cpp @@ -45,6 +45,25 @@ using ot::Encoding::BigEndian::HostSwap32; namespace ot { namespace Ip6 { +bool InterfaceIdentifier::IsUnspecified(void) const +{ + return (m8[0] == 0 && m8[1] == 0 && m8[2] == 0 && m8[3] == 0 && m8[4] == 0 && m8[5] == 0 && m8[6] == 0 && + m8[7] == 0); +} + +bool InterfaceIdentifier::IsReserved(void) const +{ + Address addr; + + addr.SetIid(this->m8); + return addr.IsIidReserved(); +} + +InterfaceIdentifier::InfoString InterfaceIdentifier::ToString(void) const +{ + return InfoString("%02x%02x%02x%02x%02x%02x%02x%02x", m8[0], m8[1], m8[2], m8[3], m8[4], m8[5], m8[6], m8[7]); +} + bool Address::IsUnspecified(void) const { return (mFields.m32[0] == 0 && mFields.m32[1] == 0 && mFields.m32[2] == 0 && mFields.m32[3] == 0); @@ -213,6 +232,11 @@ void Address::SetMulticastNetworkPrefix(const uint8_t *aPrefix, uint8_t aPrefixL mFields.m8[kMulticastNetworkPrefixLengthOffset] = aPrefixLength; } +bool Address::HasIid(const uint8_t *aIid) const +{ + return (memcmp(mFields.m8 + kInterfaceIdentifierOffset, aIid, kInterfaceIdentifierSize) == 0); +} + void Address::SetIid(const uint8_t *aIid) { memcpy(mFields.m8 + kInterfaceIdentifierOffset, aIid, kInterfaceIdentifierSize); diff --git a/src/core/net/ip6_address.hpp b/src/core/net/ip6_address.hpp index ca64746f3..a1f37f780 100644 --- a/src/core/net/ip6_address.hpp +++ b/src/core/net/ip6_address.hpp @@ -57,6 +57,54 @@ namespace Ip6 { * */ +/** + * This class represents the Interface Identifier of an IPv6 address. + * + */ +OT_TOOL_PACKED_BEGIN +class InterfaceIdentifier : public otIp6InterfaceIdentifier, + public Equatable, + public Clearable +{ +public: + enum + { + kInfoStringSize = 17, // Max chars for the info string (`ToString()`). + }; + + /** + * This type defines the fixed-length `String` object returned from `ToString()`. + * + */ + typedef String InfoString; + + /** + * This method indicates whether or not the Interface Identifier is unspecified. + * + * @retval true If the Interface Identifier is unspecified. + * @retval false If the Interface Identifier is not unspecified. + * + */ + bool IsUnspecified(void) const; + + /** + * This method indicates whether or not the Interface Identifier is reserved (RFC 5453). + * + * @retval true If the Interface Identifier is reserved. + * @retval false If the Interface Identifier is not reserved. + * + */ + bool IsReserved(void) const; + + /** + * This method converts an Interface Identifier to a string. + * + * @returns An `InfoString` containing the string representation of the Interface Identifier. + * + */ + InfoString ToString(void) const; +} OT_TOOL_PACKED_END; + /** * This class implements an IPv6 address object. * @@ -81,7 +129,7 @@ public: */ enum { - kInterfaceIdentifierSize = 8, ///< Interface Identifier size in bytes. + kInterfaceIdentifierSize = OT_IP6_IID_SIZE, ///< Interface Identifier size in bytes. kIp6AddressStringSize = 40, ///< Max buffer size in bytes to store an IPv6 address in string format. }; @@ -451,13 +499,43 @@ public: */ uint8_t *GetIid(void) { return mFields.m8 + kInterfaceIdentifierOffset; } + /** + * This method indicates whether or not the IPv6 address has the specified Interface Identifier. + * + * @param[in] aIid A pointer to the Interface Identifier. + * + * @retval true If the IPv6 address has the specified Interface Identifier. + * @retval false If the IPv6 address doesn't have the specified Interface Identifier. + * + */ + bool HasIid(const uint8_t *aIid) const; + + /** + * This method indicates whether or not the IPv6 address has the specified Interface Identifier. + * + * @param[in] aIid A reference to the Interface Identifier. + * + * @retval true If the IPv6 address has the specified Interface Identifier. + * @retval false If the IPv6 address doesn't have the specified Interface Identifier. + * + */ + bool HasIid(const InterfaceIdentifier &aIid) const { return HasIid(aIid.m8); } + + /** + * This method sets the Interface Identifier. + * + * @param[in] aIid A pointer to the Interface Identifier. + * + */ + void SetIid(const uint8_t *aIid); + /** * This method sets the Interface Identifier. * * @param[in] aIid A reference to the Interface Identifier. * */ - void SetIid(const uint8_t *aIid); + void SetIid(const InterfaceIdentifier &aIid) { SetIid(aIid.m8); } /** * This method sets the Interface Identifier. diff --git a/src/core/thread/dua_manager.cpp b/src/core/thread/dua_manager.cpp index f7f3f0f32..d2b304227 100644 --- a/src/core/thread/dua_manager.cpp +++ b/src/core/thread/dua_manager.cpp @@ -54,6 +54,8 @@ DuaManager::DuaManager(Instance &aInstance) mDomainUnicastAddress.mValid = true; mDomainUnicastAddress.mScopeOverride = Ip6::Address::kGlobalScope; mDomainUnicastAddress.mScopeOverrideValid = true; + + mFixedDuaInterfaceIdentifier.Clear(); } void DuaManager::UpdateDomainUnicastAddress(BackboneRouter::Leader::DomainPrefixState aState) @@ -64,7 +66,6 @@ void DuaManager::UpdateDomainUnicastAddress(BackboneRouter::Leader::DomainPrefix (aState == BackboneRouter::Leader::kDomainPrefixRefreshed)) { Get().RemoveUnicastAddress(mDomainUnicastAddress); - mDomainUnicastAddress.GetAddress().Clear(); } VerifyOrExit((aState == BackboneRouter::Leader::kDomainPrefixAdded) || @@ -75,19 +76,82 @@ void DuaManager::UpdateDomainUnicastAddress(BackboneRouter::Leader::DomainPrefix OT_ASSERT(prefix != NULL); - mDomainUnicastAddress.GetAddress().SetPrefix(prefix->mPrefix.mFields.m8, prefix->mLength); mDomainUnicastAddress.mPrefixLength = prefix->mLength; + mDomainUnicastAddress.GetAddress().Clear(); + mDomainUnicastAddress.GetAddress().SetPrefix(prefix->mPrefix.mFields.m8, prefix->mLength); - if (Get().GenerateIid(mDomainUnicastAddress, NULL, 0, &mDadCounter) == OT_ERROR_NONE) + // Apply cached DUA Interface Identifier manually specified. + if (IsFixedDuaInterfaceIdentifierSet()) { - Get().AddUnicastAddress(mDomainUnicastAddress); + mDomainUnicastAddress.GetAddress().SetIid(mFixedDuaInterfaceIdentifier); } else { - mDomainUnicastAddress.GetAddress().Clear(); - otLogWarnCore("Failed to generate valid DUA"); + SuccessOrExit(GenerateDomainUnicastAddressIid()); } + Get().AddUnicastAddress(mDomainUnicastAddress); + +exit: + return; +} + +otError DuaManager::GenerateDomainUnicastAddressIid(void) +{ + otError error; + + if ((error = Get().GenerateIid(mDomainUnicastAddress, NULL, 0, &mDadCounter)) == OT_ERROR_NONE) + { + otLogInfoIp6("Generated DUA: %s", mDomainUnicastAddress.GetAddress().ToString().AsCString()); + } + else + { + otLogWarnIp6("Generate DUA: %s", otThreadErrorToString(error)); + } + + return error; +} + +otError DuaManager::SetFixedDuaInterfaceIdentifier(const Ip6::InterfaceIdentifier &aIid) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(!aIid.IsReserved(), error = OT_ERROR_INVALID_ARGS); + VerifyOrExit(mFixedDuaInterfaceIdentifier.IsUnspecified() || mFixedDuaInterfaceIdentifier != aIid, OT_NOOP); + + mFixedDuaInterfaceIdentifier = aIid; + otLogInfoIp6("Set DUA IID: %s", mFixedDuaInterfaceIdentifier.ToString().AsCString()); + + if (Get().IsUnicastAddress(GetDomainUnicastAddress())) + { + Get().RemoveUnicastAddress(mDomainUnicastAddress); + mDomainUnicastAddress.GetAddress().SetIid(mFixedDuaInterfaceIdentifier); + Get().AddUnicastAddress(mDomainUnicastAddress); + } + +exit: + return error; +} + +void DuaManager::ClearFixedDuaInterfaceIdentifier(void) +{ + // Nothing to clear. + VerifyOrExit(IsFixedDuaInterfaceIdentifierSet(), OT_NOOP); + + if (GetDomainUnicastAddress().HasIid(mFixedDuaInterfaceIdentifier) && + Get().IsUnicastAddress(GetDomainUnicastAddress())) + { + Get().RemoveUnicastAddress(mDomainUnicastAddress); + + if (GenerateDomainUnicastAddressIid() == OT_ERROR_NONE) + { + Get().AddUnicastAddress(mDomainUnicastAddress); + } + } + + otLogInfoIp6("Cleared DUA IID: %s", mFixedDuaInterfaceIdentifier.ToString().AsCString()); + mFixedDuaInterfaceIdentifier.Clear(); + exit: return; } diff --git a/src/core/thread/dua_manager.hpp b/src/core/thread/dua_manager.hpp index 16046140b..6855e35c1 100644 --- a/src/core/thread/dua_manager.hpp +++ b/src/core/thread/dua_manager.hpp @@ -89,7 +89,45 @@ public: */ const Ip6::Address &GetDomainUnicastAddress(void) const { return mDomainUnicastAddress.GetAddress(); } + /** + * This method sets the Interface Identifier manually specified for the Thread Domain Unicast Address. + * + * @param[in] aIid A reference to the Interface Identifier to set. + * + * @retval OT_ERROR_NONE Successfully set the Interface Identifier. + * @retval OT_ERROR_INVALID_ARGS The specified Interface Identifier is reserved. + * + */ + otError SetFixedDuaInterfaceIdentifier(const Ip6::InterfaceIdentifier &aIid); + + /** + * This method clears the Interface Identifier manually specified for the Thread Domain Unicast Address. + * + */ + void ClearFixedDuaInterfaceIdentifier(void); + + /** + * This method indicates whether or not there is Interface Identifier manually specified for the Thread + * Domain Unicast Address. + * + * @retval true If there is Interface Identifier manually specified. + * @retval false If there is no Interface Identifier manually specified. + * + */ + bool IsFixedDuaInterfaceIdentifierSet(void) { return !mFixedDuaInterfaceIdentifier.IsUnspecified(); } + + /** + * This method gets the Interface Identifier for the Thread Domain Unicast Address if manually specified. + * + * @returns A reference to the Interface Identifier. + * + */ + const Ip6::InterfaceIdentifier &GetFixedDuaInterfaceIdentifier(void) const { return mFixedDuaInterfaceIdentifier; } + private: + otError GenerateDomainUnicastAddressIid(void); + + Ip6::InterfaceIdentifier mFixedDuaInterfaceIdentifier; Ip6::NetifUnicastAddress mDomainUnicastAddress; uint8_t mDadCounter; }; diff --git a/tests/scripts/thread-cert/command.py b/tests/scripts/thread-cert/command.py index 3141084ba..3d7fa9e32 100644 --- a/tests/scripts/thread-cert/command.py +++ b/tests/scripts/thread-cert/command.py @@ -607,6 +607,41 @@ def check_address_registration_tlv( return found +def check_compressed_address_registration_tlv(command_msg, + cid, + iid, + cid_present_once=False): + '''Check whether or not a compressed IPv6 address in AddressRegistrationTlv. + note: only compare the iid part of the address. + + Args: + command_msg (MleMessage) : The Mle message to check. + cid (int): The context id of the domain prefix. + iid (string): The Interface Identifier. + cid_present_once(boolean): True if cid entry should apprear only once in AR Tlv. + False otherwise. + ''' + found = False + cid_cnt = 0 + + addresses = command_msg.assertMleMessageContainsTlv( + mle.AddressRegistration).addresses + + for item in addresses: + if isinstance(item, mle.AddressCompressed): + if cid == item.cid: + cid_cnt = cid_cnt + 1 + if iid == item.iid.hex(): + found = True + break + assert found, 'Error: Expected (cid, iid):({},{}) Not Found'.format( + cid, iid) + + assert cid_present_once == ( + cid_cnt == 1), 'Error: Expected cid present {} but present {}'.format( + 'once' if cid_present_once else '', cid_cnt) + + def assert_contains_tlv(tlvs, check_type, tlv_type): """Assert a tlv list contains specific tlv and return the first qualified. """ diff --git a/tests/scripts/thread-cert/config.py b/tests/scripts/thread-cert/config.py index dcac3bd67..767948ae2 100644 --- a/tests/scripts/thread-cert/config.py +++ b/tests/scripts/thread-cert/config.py @@ -60,9 +60,13 @@ LINK_LOCAL_ALL_NODES_ADDRESS = 'ff02::1' LINK_LOCAL_ALL_ROUTERS_ADDRESS = 'ff02::2' DOMAIN_PREFIX = 'fd00:7d03:7d03:7d03::/64' -ALL_DOMAIN_BBRS_ADDRESS = 'ff32:40:fd00:7d03:7d03:7d03:0:3' +DOMAIN_PREFIX_ALTER = 'fd00:7d04:7d04:7d04::/64' + ALL_NETWORK_BBRS_ADDRESS = 'ff32:40:fd00:db8:0:0:0:3' +ALL_DOMAIN_BBRS_ADDRESS = 'ff32:40:fd00:7d03:7d03:7d03:0:3' +ALL_DOMAIN_BBRS_ADDRESS_ALTER = 'ff32:40:fd00:7d04:7d04:7d04:0:3' + DEFAULT_MASTER_KEY = bytearray([ 0x00, 0x11, diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 5cceea7ef..32e710ddc 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -514,8 +514,7 @@ class Node: self.send_command(cmd) self._expect('Done') - def set_domain_prefix(self, prefix): - flags = 'prosD' + def set_domain_prefix(self, prefix, flags='prosD'): self.add_prefix(prefix, flags) self.register_netdata() @@ -523,6 +522,16 @@ class Node: self.remove_prefix(prefix) self.register_netdata() + def set_dua_iid(self, iid): + cmd = 'dua iid {}'.format(iid) + self.send_command(cmd) + self._expect('Done') + + def clear_dua_iid(self): + cmd = 'dua iid clear' + self.send_command(cmd) + self._expect('Done') + def set_link_quality(self, addr, lqi): cmd = 'macfilter rss add-lqi %s %s' % (addr, lqi) self.send_command(cmd) @@ -748,6 +757,16 @@ class Node: return None + def has_ipaddr(self, address): + ipaddr = ipaddress.ip_address(address) + ipaddrs = self.get_addrs() + for addr in ipaddrs: + if isinstance(addr, bytearray): + addr = bytes(addr) + if ipaddress.ip_address(addr) == ipaddr: + return True + return False + def get_ipmaddrs(self): self.send_command('ipmaddr') return self._expect_results(r'\S+(:\S*)+') @@ -880,7 +899,7 @@ class Node: self.send_command(cmd) self._expect('Done') - def add_prefix(self, prefix, flags, prf='med'): + def add_prefix(self, prefix, flags='paosr', prf='med'): cmd = 'prefix add %s %s %s' % (prefix, flags, prf) self.send_command(cmd) self._expect('Done') diff --git a/tests/scripts/thread-cert/v1_2_test_domain_unicast_address.py b/tests/scripts/thread-cert/v1_2_test_domain_unicast_address.py new file mode 100755 index 000000000..8ddbe6883 --- /dev/null +++ b/tests/scripts/thread-cert/v1_2_test_domain_unicast_address.py @@ -0,0 +1,339 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, 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 command +import config +import ipaddress +import mle +import thread_cert + +BBR_1 = 1 # Collapsed with Leader Role +ROUTER_1_1 = 2 +ROUTER_1_2 = 3 +MED_1_2 = 4 +SED_1_2 = 5 + +WAIT_ATTACH = 5 +WAIT_REDUNDANCE = 3 +ROUTER_SELECTION_JITTER = 1 +BBR_REGISTRATION_JITTER = 5 +SED_POLL_PERIOD = 2000 # 2s +MED_TIMEOUT = 20 # 20s + +DUA_IID_MANUAL1 = '4444333322221111' +DUA_IID_MANUAL2 = '5555444433332222' + +TEST_PREFIX1 = '2001:0:0:1::/64' +TEST_PREFIX2 = '2001:0:0:2::/64' +TEST_PREFIX3 = '2001:0:0:3::/64' +""" + Topology + + + SED_1_2 + | + | + ROUTER_1_1 MED_1_2 + | | + | | + BBR_1 (LEADER) --- ROUTER_1_2 + + + 1) Bring up BBR_1, BBR_1 becomes Leader and Primary Backbone Router, with Domain + Prefix without `P_slaac`. + + 2) Bring up ROUTER_1_1, no DUA was added due to that `P_slaac` flag is not set. + + 3) Bring up ROUTER_1_2, verify that it has DUA generated. + + 4) Bring up MED_1_2 with DUA_IID_MANUAL1 set in advance, verify + a) DUA_IID_MANUAL1 is registered in Address Registration TLV via Child Update Request. + b) Remove DUA_IID_MANUAL1, a new DUA generated via SLAAC would be registered in Address + Registration TLV via Child Update Request. + c) Set DUA_IID_MANUAL2 which should override the generated one and be registered in Address + Registration TLV via Child Update Request. + d) Remove DUA_IID_MANUAL2, a new DUA generated via SLAAC, the same as in above b) would + be registered in Address Registration TLV via Child Update Request. + + 5) Change BBR_1 from config.DOMAIN_PREFIX to config.DOMAIN_PRFIX_ALTER. Verify that MED_1_2 + generate a new Interface Identifier different from the one generated in 4d) due to the + Domain Prefix change. + + 6) Recover config.Domain_Prefix on BBR_1. Verify that MED_1_2 generates and registers the same + DUA as in step 4b). + + 7) Configure ROUTER_1_1 as Border Router with 3 SLAAC prefixes, verify MED_1_2 would register + its DUA in Address Registration TLV. + + 8) Bring up SED_1_2, verify it generates one DUA, and registers it to its parent, though the parent + is a Thread 1.1 device. + +""" + + +class TestDomainUnicastAddress(thread_cert.TestCase): + topology = { + BBR_1: { + 'version': '1.2', + 'whitelist': [ROUTER_1_1, ROUTER_1_2], + 'is_bbr': True + }, + ROUTER_1_1: { + 'version': '1.1', + 'whitelist': [BBR_1, SED_1_2] + }, + ROUTER_1_2: { + 'version': '1.2', + 'whitelist': [BBR_1, MED_1_2] + }, + MED_1_2: { + 'mode': 'rsn', + 'version': '1.2', + 'whitelist': [ROUTER_1_2], + }, + SED_1_2: { + 'mode': 'sn', + 'version': '1.2', + 'whitelist': [ROUTER_1_1], + }, + } + """All nodes are created with default configurations""" + + def __get_iid(self, address): + ''' Get the interface identifier of an IPv6 address. + + Args: + address (string): An IPv6 address; + ''' + return ''.join(ipaddress.ip_address(address).exploded.split(':')[4:]) + + def __check_dua_registration(self, node, iid, dp_cid): + ''' Check whether or not the specified domain unicast address is registered in Address + Registraion TLV. + + Args: + node (int) : The device id + iid (string): The Interface Identifier + dp_cid (int): The context id of the domain prefix. + ''' + + messages = self.simulator.get_messages_sent_by(node) + msg = messages.next_mle_message(mle.CommandType.CHILD_UPDATE_REQUEST) + command.check_compressed_address_registration_tlv(msg, + dp_cid, + iid, + cid_present_once=True) + + def test(self): + # starting context id + context_id = 1 + + # 1) Bring up BBR_1, BBR_1 becomes Leader and Primary Backbone Router, with Domain + # Prefix without `P_slaac`. + self.nodes[BBR_1].set_router_selection_jitter(ROUTER_SELECTION_JITTER) + self.nodes[BBR_1].set_bbr_registration_jitter(BBR_REGISTRATION_JITTER) + self.nodes[BBR_1].set_backbone_router(seqno=1) + self.nodes[BBR_1].start() + WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER + self.simulator.go(WAIT_TIME) + self.assertEqual(self.nodes[BBR_1].get_state(), 'leader') + self.nodes[BBR_1].enable_backbone_router() + WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(), + 'Primary') + assert self.nodes[BBR_1].has_ipmaddr(config.ALL_NETWORK_BBRS_ADDRESS) + assert not self.nodes[BBR_1].has_ipmaddr(config.ALL_DOMAIN_BBRS_ADDRESS) + + self.nodes[BBR_1].set_domain_prefix(config.DOMAIN_PREFIX, 'prosD') + WAIT_TIME = WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + assert self.nodes[BBR_1].has_ipmaddr(config.ALL_DOMAIN_BBRS_ADDRESS) + + self.simulator.set_lowpan_context(context_id, config.DOMAIN_PREFIX) + domain_prefix_cid = context_id + + # 2) Bring up ROUTER_1_1, no DUA was added due to that `P_slaac` flag is not set. + self.nodes[ROUTER_1_1].set_router_selection_jitter( + ROUTER_SELECTION_JITTER) + WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER + self.nodes[ROUTER_1_1].start() + self.simulator.go(WAIT_TIME) + self.assertEqual(self.nodes[ROUTER_1_1].get_state(), 'router') + dua = self.nodes[ROUTER_1_1].get_addr(config.DOMAIN_PREFIX) + assert not dua, 'Error: Unexpected DUA ({})'.format(dua) + + # 3) Bring up ROUTER_1_2, verify that it has DUA generated. + self.nodes[ROUTER_1_2].set_router_selection_jitter( + ROUTER_SELECTION_JITTER) + self.nodes[ROUTER_1_2].start() + WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER + self.simulator.go(WAIT_TIME) + self.assertEqual(self.nodes[ROUTER_1_2].get_state(), 'router') + dua = self.nodes[ROUTER_1_2].get_addr(config.DOMAIN_PREFIX) + assert dua, 'Error: Expected DUA not found' + self.assertTrue(self.nodes[BBR_1].ping(dua)) + + # 4) Bring up MED_1_2 with DUA_IID_MANUAL1 set in advance + self.nodes[MED_1_2].set_dua_iid(DUA_IID_MANUAL1) + self.nodes[MED_1_2].set_timeout(MED_TIMEOUT) + self.nodes[MED_1_2].start() + WAIT_TIME = WAIT_ATTACH + self.simulator.go(WAIT_TIME) + self.assertEqual(self.nodes[MED_1_2].get_state(), 'child') + + # 4a) DUA_IID_MANUAL1 is registered in Address Registration TLV via Child Update Request. + self.__check_dua_registration(MED_1_2, DUA_IID_MANUAL1, + domain_prefix_cid) + + # 4b) Remove DUA_IID_MANUAL1, a new DUA generated via SLAAC would be registered in Address + # Registration TLV via Child Update Request. + + # Flush relative message queues. + messages = self.simulator.get_messages_sent_by(MED_1_2) + + self.nodes[MED_1_2].clear_dua_iid() + WAIT_TIME = MED_TIMEOUT + WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + + med_1_2_dua = self.nodes[MED_1_2].get_addr(config.DOMAIN_PREFIX) + assert med_1_2_dua, 'Error: Expected DUA not found' + + med_1_2_dua_iid = self.__get_iid(med_1_2_dua) + self.__check_dua_registration(MED_1_2, med_1_2_dua_iid, + domain_prefix_cid) + + # 4c) Set DUA_IID_MANUAL2 which should override the generated one and be registered in Address + # Registration TLV via Child Update Request. + + # Flush relative message queues. + messages = self.simulator.get_messages_sent_by(MED_1_2) + self.nodes[MED_1_2].set_dua_iid(DUA_IID_MANUAL2) + WAIT_TIME = WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + + dua = self.nodes[MED_1_2].get_addr(config.DOMAIN_PREFIX) + + self.__check_dua_registration(MED_1_2, DUA_IID_MANUAL2, + domain_prefix_cid) + + # 4d) Remove DUA_IID_MANUAL2, a new DUA generated via SLAAC, the same as in above b) would + # be registered in Address Registration TLV via Child Update Request. + + # Flush relative message queues. + messages = self.simulator.get_messages_sent_by(MED_1_2) + self.nodes[MED_1_2].clear_dua_iid() + WAIT_TIME = WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + dua = self.nodes[MED_1_2].get_addr(config.DOMAIN_PREFIX) + assert ipaddress.ip_address(dua) == ipaddress.ip_address( + med_1_2_dua), 'Error: Expected SLAAC DUA not generated' + assert ipaddress.ip_address(med_1_2_dua) == ipaddress.ip_address( + dua), 'Error: Expected same SLAAC DUA not generated' + + self.__check_dua_registration(MED_1_2, med_1_2_dua_iid, + domain_prefix_cid) + + # 5) Change BBR_1 from config.DOMAIN_PREFIX to config.DOMAIN_PRFIX_ALTER. Verify that MED_1_2 + # generates a new Interface Identifier different from the one generated in 4d) due to the + # Domain Prefix change. + context_id += 1 + self.simulator.set_lowpan_context(context_id, + config.DOMAIN_PREFIX_ALTER) + self.nodes[BBR_1].set_domain_prefix(config.DOMAIN_PREFIX_ALTER) + WAIT_TIME = WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + + med_1_2_dua2 = self.nodes[MED_1_2].get_addr(config.DOMAIN_PREFIX_ALTER) + med_1_2_dua2_iid = self.__get_iid(med_1_2_dua2) + self.__check_dua_registration(MED_1_2, med_1_2_dua2_iid, context_id) + + #6) Recover config.Domain_Prefix on BBR_1. Verify that MED_1_2 generates and registers the same + # DUA as in step 4b). + self.nodes[BBR_1].set_domain_prefix(config.DOMAIN_PREFIX) + WAIT_TIME = WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + dua = self.nodes[MED_1_2].get_addr(config.DOMAIN_PREFIX) + assert dua, 'Error: Expected DUA not found' + assert ipaddress.ip_address(med_1_2_dua) == ipaddress.ip_address( + dua), 'Error: Expected same SLAAC DUA not generated' + + self.__check_dua_registration(MED_1_2, med_1_2_dua_iid, + domain_prefix_cid) + + #7) Configure ROUTER_1_1 as Border Router with 3 SLAAC prefixes, verify MED_1_2 would register + # its DUA in Address Registration TLV. + + # Flush relative message queues. + messages = self.simulator.get_messages_sent_by(MED_1_2) + + context_id += 1 + self.simulator.set_lowpan_context(context_id, TEST_PREFIX1) + self.nodes[ROUTER_1_1].add_prefix(TEST_PREFIX1) + + context_id += 1 + self.simulator.set_lowpan_context(context_id, TEST_PREFIX2) + self.nodes[ROUTER_1_1].add_prefix(TEST_PREFIX2) + context_id += 1 + self.simulator.set_lowpan_context(context_id, TEST_PREFIX3) + self.nodes[ROUTER_1_1].add_prefix(TEST_PREFIX3) + self.nodes[ROUTER_1_1].register_netdata() + + WAIT_TIME = WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + + WAIT_TIME = MED_TIMEOUT + dua = self.nodes[MED_1_2].get_addr(config.DOMAIN_PREFIX) + assert dua, 'Error: Expected DUA not found' + assert ipaddress.ip_address(med_1_2_dua) == ipaddress.ip_address( + dua), 'Error: Expected same SLAAC DUA not generated' + + self.__check_dua_registration(MED_1_2, med_1_2_dua_iid, + domain_prefix_cid) + + #8) Bring up SED_1_2, verify that it generates one DUA, and registers it to its parent, though the parent + # is a Thread 1.1 device. + self.nodes[SED_1_2].set_pollperiod(SED_POLL_PERIOD) + self.nodes[SED_1_2].start() + WAIT_TIME = WAIT_ATTACH + self.simulator.go(WAIT_TIME) + + dua = self.nodes[SED_1_2].get_addr(config.DOMAIN_PREFIX) + assert dua, 'Error: Expected DUA not found' + + dua_iid = self.__get_iid(dua) + self.__check_dua_registration(SED_1_2, dua_iid, domain_prefix_cid) + + self.assertTrue(self.nodes[BBR_1].ping(dua)) + + +if __name__ == '__main__': + unittest.main()