From 61cf7c9a485f4c43ca61e3a97bb49dcda8a3915a Mon Sep 17 00:00:00 2001 From: Simon Lin Date: Tue, 3 Nov 2020 00:06:45 +0800 Subject: [PATCH] [dua] implement ND Proxy callbacks (#5595) This commit: - Add callback for Domain Prefix events - Add callback for ND Proxy events - Add corresponding test to verify ND Proxy works - Some misc enhancements to the Backbone tests - Run sudo modprobe ip6table_filter in Docker host so that ip6tables works in ubuntu docker instances. - Use -DOTBR_REST=OFF -DOTBR_WEB=OFF for building OTBR Docker image since these features are not used in tests. - Setup radvd on Host to send Router Advertisements for the Domain Prefix using configuration: AdvOnLink on; AdvAutonomous off; AdvRouterAddr off; - Set sysctl net.ipv6.conf.eth0.accept_ra=2 for BBR and Host --- .github/workflows/simulation-1.2.yml | 1 - include/openthread/backbone_router_ftd.h | 97 ++++++++ include/openthread/instance.h | 2 +- script/test | 8 +- src/core/api/backbone_router_ftd_api.cpp | 28 +++ src/core/backbone_router/bbr_leader.cpp | 2 +- src/core/backbone_router/bbr_local.cpp | 31 ++- src/core/backbone_router/bbr_local.hpp | 19 +- src/core/backbone_router/ndproxy_table.cpp | 62 ++++- src/core/backbone_router/ndproxy_table.hpp | 32 ++- .../thread-cert/backbone/bbr_5_11_01.py | 10 +- .../thread-cert/backbone/test_ndproxy.py | 225 ++++++++++++++++++ tests/scripts/thread-cert/node.py | 46 +++- tests/scripts/thread-cert/pktverify/addrs.py | 2 +- tests/scripts/thread-cert/run_bbr_tests.py | 1 + 15 files changed, 537 insertions(+), 29 deletions(-) create mode 100644 tests/scripts/thread-cert/backbone/test_ndproxy.py diff --git a/.github/workflows/simulation-1.2.yml b/.github/workflows/simulation-1.2.yml index 4003db7d5..31f3ff70c 100644 --- a/.github/workflows/simulation-1.2.yml +++ b/.github/workflows/simulation-1.2.yml @@ -251,7 +251,6 @@ jobs: MULTIPLY: 1 PYTHONUNBUFFERED: 1 VERBOSE: 1 - OTBR_COMMIT: "3cf6ff1126dca96803252514691f40d0554046bf" # Date: Mon Oct 26 15:27:04 2020 +0800 steps: - uses: actions/checkout@v2 - name: Build OTBR Docker diff --git a/include/openthread/backbone_router_ftd.h b/include/openthread/backbone_router_ftd.h index 3b4668b9e..eefcf2b22 100644 --- a/include/openthread/backbone_router_ftd.h +++ b/include/openthread/backbone_router_ftd.h @@ -313,6 +313,103 @@ otError otBackboneRouterMulticastListenerGetNext(otInstance * otBackboneRouterMulticastListenerIterator *aIterator, otBackboneRouterMulticastListenerInfo * aListenerInfo); +/** + * Represents the ND Proxy events. + * + */ +typedef enum +{ + OT_BACKBONE_ROUTER_NDPROXY_ADDED = 0, ///< ND Proxy was added. + OT_BACKBONE_ROUTER_NDPROXY_REMOVED = 1, ///< ND Proxy was removed. + OT_BACKBONE_ROUTER_NDPROXY_RENEWED = 2, ///< ND Proxy was renewed. + OT_BACKBONE_ROUTER_NDPROXY_CLEARED = 3, ///< All ND Proxies were cleared. +} otBackboneRouterNdProxyEvent; + +/** + * This function pointer is called whenever the Nd Proxy changed. + * + * @param[in] aContext The user context pointer. + * @param[in] aEvent The ND Proxy event. + * @param[in] aDua The Domain Unicast Address of the ND Proxy, or `nullptr` if @p aEvent is + * `OT_BACKBONE_ROUTER_NDPROXY_CLEARED`. + * + */ +typedef void (*otBackboneRouterNdProxyCallback)(void * aContext, + otBackboneRouterNdProxyEvent aEvent, + const otIp6Address * aDua); + +/** + * This method sets the Backbone Router ND Proxy callback. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aCallback A pointer to the ND Proxy callback. + * @param[in] aContext A user context pointer. + * + */ +void otBackboneRouterSetNdProxyCallback(otInstance * aInstance, + otBackboneRouterNdProxyCallback aCallback, + void * aContext); + +/** + * Represents the Backbone Router ND Proxy info. + * + */ +struct otBackboneRouterNdProxyInfo +{ + otIp6InterfaceIdentifier *mMeshLocalIid; ///< Mesh-local IID + uint32_t mTimeSinceLastTransaction; ///< Time since last transaction (Seconds) + uint16_t mRloc16; ///< RLOC16 +}; + +/** + * This method gets the Backbone Router ND Proxy info. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aDua The Domain Unicast Address. + * @param[out] aNdProxyInfo A pointer to the ND Proxy info. + * + * @retval OT_ERROR_NONE Successfully got the ND Proxy info. + * @retval OT_ERROR_NOT_FOUND Failed to find the Domain Unicast Address in the ND Proxy table. + * + */ +otError otBackboneRouterGetNdProxyInfo(otInstance * aInstance, + const otIp6Address * aDua, + otBackboneRouterNdProxyInfo *aNdProxyInfo); + +/** + * Represents the Domain Prefix events. + * + */ +typedef enum +{ + OT_BACKBONE_ROUTER_DOMAIN_PREFIX_ADDED = 0, ///< Domain Prefix was added. + OT_BACKBONE_ROUTER_DOMAIN_PREFIX_REMOVED = 1, ///< Domain Prefix was removed. + OT_BACKBONE_ROUTER_DOMAIN_PREFIX_CHANGED = 2, ///< Domain Prefix was changed. +} otBackboneRouterDomainPrefixEvent; + +/** + * This function pointer is called whenever the Domain Prefix changed. + * + * @param[in] aContext The user context pointer. + * @param[in] aEvent The Domain Prefix event. + * @param[in] aDomainPrefix The new Domain Prefix if added or changed, nullptr otherwise. + * + */ +typedef void (*otBackboneRouterDomainPrefixCallback)(void * aContext, + otBackboneRouterDomainPrefixEvent aEvent, + const otIp6Prefix * aDomainPrefix); +/** + * This method sets the Backbone Router Domain Prefix callback. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aCallback A pointer to the Domain Prefix callback. + * @param[in] aContext A user context pointer. + * + */ +void otBackboneRouterSetDomainPrefixCallback(otInstance * aInstance, + otBackboneRouterDomainPrefixCallback aCallback, + void * aContext); + /** * @} * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 2da7e3aaf..90a07e5e7 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 (44) +#define OPENTHREAD_API_VERSION (45) /** * @addtogroup api-instance diff --git a/script/test b/script/test index 2294343d8..d967bde2b 100755 --- a/script/test +++ b/script/test @@ -279,8 +279,8 @@ do_build_otbr_docker() echo "Building OTBR Docker ..." local otdir local otbrdir - local otbr_options="-DOT_DUA=ON -DOT_MLR=ON -DOT_COVERAGE=ON" - local otbr_commit=${OTBR_COMMIT:-master} + local otbr_options="-DOT_DUA=ON -DOT_MLR=ON -DOT_COVERAGE=ON -DOTBR_REST=OFF -DOTBR_WEB=OFF" + local otbr_commit=${OTBR_COMMIT:-8d0b6ccd857441208f56ed29050a9367d8d74a62} # [backbone-router] implement Backbone ND proxy #581 local otbr_docker_image=${OTBR_DOCKER_IMAGE:-otbr-ot12-backbone-ci} otbrdir=$(mktemp -d -t otbr_XXXXXX) @@ -289,10 +289,8 @@ do_build_otbr_docker() ( cd "${otbrdir}" git init - git remote add origin https://github.com/openthread/ot-br-posix.git - git fetch origin "${otbr_commit}" --depth 1 + git fetch https://github.com/openthread/ot-br-posix.git "${otbr_commit}" --depth 1 git reset --hard FETCH_HEAD - git submodule update --init --recursive --depth 1 rm -rf third_party/openthread/repo cp -r "${otdir}" third_party/openthread/repo rm -rf .git diff --git a/src/core/api/backbone_router_ftd_api.cpp b/src/core/api/backbone_router_ftd_api.cpp index 23785951d..26720fccd 100644 --- a/src/core/api/backbone_router_ftd_api.cpp +++ b/src/core/api/backbone_router_ftd_api.cpp @@ -182,4 +182,32 @@ otError otBackboneRouterMulticastListenerGetNext(otInstance * } #endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE +void otBackboneRouterSetNdProxyCallback(otInstance * aInstance, + otBackboneRouterNdProxyCallback aCallback, + void * aContext) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().SetCallback(aCallback, aContext); +} + +otError otBackboneRouterGetNdProxyInfo(otInstance * aInstance, + const otIp6Address * aDua, + otBackboneRouterNdProxyInfo *aNdProxyInfo) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().GetInfo(reinterpret_cast(*aDua), + *aNdProxyInfo); +} + +void otBackboneRouterSetDomainPrefixCallback(otInstance * aInstance, + otBackboneRouterDomainPrefixCallback aCallback, + void * aContext) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().SetDomainPrefixCallback(aCallback, aContext); +} + #endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE diff --git a/src/core/backbone_router/bbr_leader.cpp b/src/core/backbone_router/bbr_leader.cpp index 19028c0a3..0ad01c025 100644 --- a/src/core/backbone_router/bbr_leader.cpp +++ b/src/core/backbone_router/bbr_leader.cpp @@ -301,7 +301,7 @@ void Leader::UpdateDomainPrefixConfig(void) LogDomainPrefix(state, mDomainPrefix); #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE - Get().UpdateAllDomainBackboneRouters(state); + Get().HandleDomainPrefixUpdate(state); Get().HandleDomainPrefixUpdate(state); #endif diff --git a/src/core/backbone_router/bbr_local.cpp b/src/core/backbone_router/bbr_local.cpp index f808e9552..19a3a2f98 100644 --- a/src/core/backbone_router/bbr_local.cpp +++ b/src/core/backbone_router/bbr_local.cpp @@ -55,6 +55,8 @@ Local::Local(Instance &aInstance) , mSequenceNumber(Random::NonCrypto::GetUint8()) , mRegistrationJitter(Mle::kBackboneRouterRegistrationJitter) , mIsServiceAdded(false) + , mDomainPrefixCallback(nullptr) + , mDomainPrefixCallbackContext(nullptr) { mDomainPrefixConfig.GetPrefix().SetLength(0); @@ -359,7 +361,7 @@ exit: return; } -void Local::UpdateAllDomainBackboneRouters(Leader::DomainPrefixState aState) +void Local::HandleDomainPrefixUpdate(Leader::DomainPrefixState aState) { if (!IsEnabled()) { @@ -377,6 +379,27 @@ void Local::UpdateAllDomainBackboneRouters(Leader::DomainPrefixState aState) Get().SubscribeMulticast(mAllDomainBackboneRouters); } + if (mDomainPrefixCallback != nullptr) + { + switch (aState) + { + case Leader::kDomainPrefixAdded: + mDomainPrefixCallback(mDomainPrefixCallbackContext, OT_BACKBONE_ROUTER_DOMAIN_PREFIX_ADDED, + Get().GetDomainPrefix()); + break; + case Leader::kDomainPrefixRemoved: + mDomainPrefixCallback(mDomainPrefixCallbackContext, OT_BACKBONE_ROUTER_DOMAIN_PREFIX_REMOVED, + Get().GetDomainPrefix()); + break; + case Leader::kDomainPrefixRefreshed: + mDomainPrefixCallback(mDomainPrefixCallbackContext, OT_BACKBONE_ROUTER_DOMAIN_PREFIX_CHANGED, + Get().GetDomainPrefix()); + break; + default: + break; + } + } + exit: return; } @@ -419,6 +442,12 @@ void Local::LogBackboneRouterService(const char *aAction, otError aError) } #endif +void Local::SetDomainPrefixCallback(otBackboneRouterDomainPrefixCallback aCallback, void *aContext) +{ + mDomainPrefixCallback = aCallback; + mDomainPrefixCallbackContext = aContext; +} + } // namespace BackboneRouter } // namespace ot diff --git a/src/core/backbone_router/bbr_local.hpp b/src/core/backbone_router/bbr_local.hpp index 7d7c2090e..7d1b82853 100644 --- a/src/core/backbone_router/bbr_local.hpp +++ b/src/core/backbone_router/bbr_local.hpp @@ -227,7 +227,16 @@ public: * @param[in] aState The Domain Prefix state or state change. * */ - void UpdateAllDomainBackboneRouters(Leader::DomainPrefixState aState); + void HandleDomainPrefixUpdate(Leader::DomainPrefixState aState); + + /** + * This method sets the Domain Prefix callback. + * + * @param[in] aCallback The callback function. + * @param[in] aContext A user context pointer. + * + */ + void SetDomainPrefixCallback(otBackboneRouterDomainPrefixCallback aCallback, void *aContext); private: void SetState(BackboneRouterState aState); @@ -255,9 +264,11 @@ private: NetworkData::OnMeshPrefixConfig mDomainPrefixConfig; - Ip6::NetifUnicastAddress mBackboneRouterPrimaryAloc; - Ip6::Address mAllNetworkBackboneRouters; - Ip6::Address mAllDomainBackboneRouters; + Ip6::NetifUnicastAddress mBackboneRouterPrimaryAloc; + Ip6::Address mAllNetworkBackboneRouters; + Ip6::Address mAllDomainBackboneRouters; + otBackboneRouterDomainPrefixCallback mDomainPrefixCallback; + void * mDomainPrefixCallbackContext; }; } // namespace BackboneRouter diff --git a/src/core/backbone_router/ndproxy_table.cpp b/src/core/backbone_router/ndproxy_table.cpp index 46bab94e1..f755f0f43 100644 --- a/src/core/backbone_router/ndproxy_table.cpp +++ b/src/core/backbone_router/ndproxy_table.cpp @@ -141,6 +141,11 @@ void NdProxyTable::Clear(void) proxy.Clear(); } + if (mCallback != nullptr) + { + mCallback(mCallbackContext, OT_BACKBONE_ROUTER_NDPROXY_CLEARED, nullptr); + } + otLogNoteBbr("NdProxyTable::Clear!"); } @@ -158,13 +163,14 @@ otError NdProxyTable::Register(const Ip6::InterfaceIdentifier &aAddressIid, VerifyOrExit(proxy->mMeshLocalIid == aMeshLocalIid, error = OT_ERROR_DUPLICATED); proxy->Update(aRloc16, timeSinceLastTransaction); - NotifyDuaRegistrationOnBackboneLink(*proxy); + NotifyDuaRegistrationOnBackboneLink(*proxy, /* aIsRenew */ true); ExitNow(); } proxy = FindByMeshLocalIid(aMeshLocalIid); if (proxy != nullptr) { + TriggerCallback(OT_BACKBONE_ROUTER_NDPROXY_REMOVED, proxy->mAddressIid); Erase(*proxy); } else @@ -245,7 +251,7 @@ void NdProxyTable::HandleTimer(void) if (proxy.IsDadAttamptsComplete()) { proxy.mDadFlag = false; - NotifyDuaRegistrationOnBackboneLink(proxy); + NotifyDuaRegistrationOnBackboneLink(proxy, /* aIsRenew */ false); } else { @@ -262,6 +268,31 @@ exit: return; } +void NdProxyTable::SetCallback(otBackboneRouterNdProxyCallback aCallback, void *aContext) +{ + mCallback = aCallback; + mCallbackContext = aContext; +} + +void NdProxyTable::TriggerCallback(otBackboneRouterNdProxyEvent aEvent, + const Ip6::InterfaceIdentifier &aAddressIid) const +{ + Ip6::Address dua; + const Ip6::Prefix *prefix = Get().GetDomainPrefix(); + + VerifyOrExit(mCallback != nullptr); + + OT_ASSERT(prefix != nullptr); + + dua.SetPrefix(*prefix); + dua.SetIid(aAddressIid); + + mCallback(mCallbackContext, aEvent, &dua); + +exit: + return; +} + void NdProxyTable::NotifyDadComplete(NdProxyTable::NdProxy &aNdProxy, bool aDuplicated) { if (aDuplicated) @@ -292,15 +323,40 @@ NdProxyTable::NdProxy *NdProxyTable::ResolveDua(const Ip6::Address &aDua) return Get().IsDomainUnicast(aDua) ? FindByAddressIid(aDua.GetIid()) : nullptr; } -void NdProxyTable::NotifyDuaRegistrationOnBackboneLink(NdProxyTable::NdProxy &aNdProxy) +void NdProxyTable::NotifyDuaRegistrationOnBackboneLink(NdProxyTable::NdProxy &aNdProxy, bool aIsRenew) { if (!aNdProxy.mDadFlag) { + TriggerCallback(aIsRenew ? OT_BACKBONE_ROUTER_NDPROXY_RENEWED : OT_BACKBONE_ROUTER_NDPROXY_ADDED, + aNdProxy.mAddressIid); + IgnoreError(Get().SendProactiveBackboneNotification( GetDua(aNdProxy), aNdProxy.GetMeshLocalIid(), aNdProxy.GetTimeSinceLastTransaction())); } } +otError NdProxyTable::GetInfo(const Ip6::Address &aDua, otBackboneRouterNdProxyInfo &aNdProxyInfo) +{ + otError error = OT_ERROR_NOT_FOUND; + + VerifyOrExit(Get().IsDomainUnicast(aDua), error = OT_ERROR_INVALID_ARGS); + + for (NdProxy &proxy : Iterate(kFilterValid)) + { + if (proxy.mAddressIid == aDua.GetIid()) + { + aNdProxyInfo.mMeshLocalIid = &proxy.mMeshLocalIid; + aNdProxyInfo.mTimeSinceLastTransaction = proxy.GetTimeSinceLastTransaction(); + aNdProxyInfo.mRloc16 = proxy.mRloc16; + + ExitNow(error = OT_ERROR_NONE); + } + } + +exit: + return error; +} + } // namespace BackboneRouter } // namespace ot diff --git a/src/core/backbone_router/ndproxy_table.hpp b/src/core/backbone_router/ndproxy_table.hpp index cbb95e8ba..631e4cfbe 100644 --- a/src/core/backbone_router/ndproxy_table.hpp +++ b/src/core/backbone_router/ndproxy_table.hpp @@ -132,6 +132,8 @@ public: */ explicit NdProxyTable(Instance &aInstance) : InstanceLocator(aInstance) + , mCallback(nullptr) + , mCallbackContext(nullptr) , mIsAnyDadInProcess(false) { } @@ -206,6 +208,27 @@ public: */ static void Erase(NdProxy &aNdProxy); + /* + * This method sets the ND Proxy callback. + * + * @param[in] aCallback The callback function. + * @param[in] aContext A user context pointer. + * + */ + void SetCallback(otBackboneRouterNdProxyCallback aCallback, void *aContext); + + /** + * This method retrieves the ND Proxy info of the Domain Unicast Address. + * + * @param[in] aDua The Domain Unicast Address to get info. + * @param[in] aNdProxyInfo A pointer to the ND Proxy info. + * + * @retval OT_ERROR_NONE Successfully retrieve the ND Proxy info. + * @retval OT_ERROR_NOT_FOUND Failed to find the Domain Unicast Address in the ND Proxy table. + * + */ + otError GetInfo(const Ip6::Address &aDua, otBackboneRouterNdProxyInfo &aNdProxyInfo); + private: enum { @@ -275,10 +298,13 @@ private: NdProxy * FindByMeshLocalIid(const Ip6::InterfaceIdentifier &aMeshLocalIid); NdProxy * FindInvalid(void); Ip6::Address GetDua(NdProxy &aNdProxy); - void NotifyDuaRegistrationOnBackboneLink(NdProxy &aNdProxy); + void NotifyDuaRegistrationOnBackboneLink(NdProxy &aNdProxy, bool aIsRenew); + void TriggerCallback(otBackboneRouterNdProxyEvent aEvent, const Ip6::InterfaceIdentifier &aAddressIid) const; - NdProxy mProxies[kMaxNdProxyNum]; - bool mIsAnyDadInProcess : 1; + NdProxy mProxies[kMaxNdProxyNum]; + otBackboneRouterNdProxyCallback mCallback; + void * mCallbackContext; + bool mIsAnyDadInProcess : 1; }; } // namespace BackboneRouter diff --git a/tests/scripts/thread-cert/backbone/bbr_5_11_01.py b/tests/scripts/thread-cert/backbone/bbr_5_11_01.py index efd5ac0f8..57f0c475b 100755 --- a/tests/scripts/thread-cert/backbone/bbr_5_11_01.py +++ b/tests/scripts/thread-cert/backbone/bbr_5_11_01.py @@ -122,8 +122,8 @@ class BBR_5_11_01(thread_cert.TestCase): backbone=True)) # Step 23: Host sends ping packet to destination Dg - self.assertFalse(self.nodes[HOST].ping(Dg, - backbone=True)) # Must fail since ND Proxying is not implemented yet + # TODO: (DUA) implement DUA routing on OTBR + self.assertFalse(self.nodes[HOST].ping(Dg, backbone=True)) def verify(self, pv: PacketVerifier): pkts = pv.pkts @@ -161,12 +161,10 @@ class BBR_5_11_01(thread_cert.TestCase): pkts.filter_eth_src(Host_ETH).filter_ipv6_src_dst(Host_BGUA, BR_1_BGUA).filter_ping_reply().must_next() # Step 16: Host: Queries DUA, Dg, with ND-NS - # TODO: setup radvd on Host - pkts.filter_eth_src(Host_ETH).filter_icmpv6_nd_ns(Dg).must_not_next() + pkts.filter_eth_src(Host_ETH).filter_icmpv6_nd_ns(Dg).must_next() # Step 17: BR_1: Responds with a neighbor advertisement. - # TODO: (DUA) implement ND proxy on PBBR - pkts.filter_eth_src(BR_1_ETH).filter_icmpv6_nd_na(Dg).must_not_next() + pkts.filter_eth_src(BR_1_ETH).filter_icmpv6_nd_na(Dg).must_next() if __name__ == '__main__': diff --git a/tests/scripts/thread-cert/backbone/test_ndproxy.py b/tests/scripts/thread-cert/backbone/test_ndproxy.py new file mode 100644 index 000000000..1e5b7c62e --- /dev/null +++ b/tests/scripts/thread-cert/backbone/test_ndproxy.py @@ -0,0 +1,225 @@ +#!/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. +# +# This test verifies PBBR's ND Proxy feature: +# 1. Sends ICMPv6 Neighbor Advertisement (NA) for ND Proxy +# 2. Receive and handle ICMPv6 Neighbor Solicitation (NS) and sends NA accordingly. +# +import ipaddress +import unittest + +import config +import thread_cert +from pktverify.packet_verifier import PacketVerifier +from pktverify.consts import DUA_RECENT_TIME + +PBBR = 1 +SBBR = 2 +ROUTER1 = 3 +HOST = 4 + +REREG_DELAY = 5 # Seconds +MLR_TIMEOUT = 300 # Seconds +WAIT_REDUNDANCE = 3 + + +class TestNdProxy(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + + # Topology: + # ---- -(eth)------------ + # | | | + # PBBR----SBBR HOST + # \ / + # Router1 + # + TOPOLOGY = { + PBBR: { + 'name': 'PBBR', + 'allowlist': [SBBR, ROUTER1], + 'is_otbr': True, + 'version': '1.2', + 'router_selection_jitter': 1, + }, + SBBR: { + 'name': 'SBBR', + 'allowlist': [PBBR, ROUTER1], + 'is_otbr': True, + 'version': '1.2', + 'router_selection_jitter': 1, + }, + ROUTER1: { + 'name': 'ROUTER1', + 'allowlist': [PBBR, SBBR], + 'version': '1.2', + 'router_selection_jitter': 1, + }, + HOST: { + 'name': 'HOST', + 'is_host': True + }, + } + + def test(self): + # Bring up HOST + self.nodes[HOST].start() + + # Bring up PBBR + self.nodes[PBBR].start() + self.simulator.go(5) + self.assertEqual('leader', self.nodes[PBBR].get_state()) + self.wait_node_state(PBBR, 'leader', 5) + + self.nodes[PBBR].set_backbone_router(reg_delay=REREG_DELAY, mlr_timeout=MLR_TIMEOUT) + self.nodes[PBBR].enable_backbone_router() + self.nodes[PBBR].set_domain_prefix(config.DOMAIN_PREFIX, 'prosD') + self.simulator.go(5) + self.assertTrue(self.nodes[PBBR].is_primary_backbone_router) + self.assertIsNotNone(self.nodes[PBBR].get_ip6_address(config.ADDRESS_TYPE.DUA)) + + # Bring up SBBR + self.nodes[SBBR].start() + self.simulator.go(5) + self.assertEqual('router', self.nodes[SBBR].get_state()) + + self.nodes[SBBR].set_backbone_router(reg_delay=REREG_DELAY, mlr_timeout=MLR_TIMEOUT) + self.nodes[SBBR].enable_backbone_router() + self.simulator.go(5) + self.assertFalse(self.nodes[SBBR].is_primary_backbone_router) + self.assertIsNotNone(self.nodes[SBBR].get_ip6_address(config.ADDRESS_TYPE.DUA)) + + # Bring up ROUTER1 + self.nodes[ROUTER1].start() + self.simulator.go(5) + self.assertEqual('router', self.nodes[ROUTER1].get_state()) + self.assertIsNotNone(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.DUA)) + + self.collect_ipaddrs() + self.collect_rloc16s() + + # Wait for DUA_RECENT_TIME + self.simulator.go(DUA_RECENT_TIME + WAIT_REDUNDANCE) + + self._test_neighbor_solicitation(PBBR) + self._test_neighbor_solicitation(SBBR) + self._test_neighbor_solicitation(ROUTER1) + + def _test_neighbor_solicitation(self, nodeid): + dua = self.nodes[nodeid].get_ip6_address(config.ADDRESS_TYPE.DUA) + print('Node DUA: ', dua) + self.nodes[HOST].ip_neighbors_flush() + self.nodes[HOST].ping(dua, backbone=True) + self.simulator.go(WAIT_REDUNDANCE) + + def verify(self, pv: PacketVerifier): + pkts = pv.pkts + pv.add_common_vars() + pv.summary.show() + + PBBR_DUA = pv.vars['PBBR_DUA'] + SBBR_DUA = pv.vars['SBBR_DUA'] + ROUTER1_DUA = pv.vars['ROUTER1_DUA'] + PBBR_ETH = pv.vars['PBBR_ETH'] + SBBR_ETH = pv.vars['SBBR_ETH'] + HOST_ETH = pv.vars['HOST_ETH'] + HOST_BGUA = pv.vars['HOST_BGUA'] + + # SBBR must not send NA for any DUA + pkts.filter_eth_src(SBBR_ETH).filter_LLANMA().filter_icmpv6_nd_na(PBBR_DUA).must_not_next() + pkts.filter_eth_src(SBBR_ETH).filter_LLANMA().filter_icmpv6_nd_na(SBBR_DUA).must_not_next() + pkts.filter_eth_src(SBBR_ETH).filter_LLANMA().filter_icmpv6_nd_na(ROUTER1_DUA).must_not_next() + + # PBBR must send unsolicited NA for PBBR's DUA + pkts.filter_eth_src(PBBR_ETH).filter_LLANMA().filter_icmpv6_nd_na(PBBR_DUA).filter( + 'icmpv6.nd.na.flag.s == 0').must_next().must_verify(""" + icmpv6.opt.linkaddr == {PBBR_ETH} + and icmpv6.nd.na.flag.r == 1 + and icmpv6.nd.na.flag.o == 1 + and icmpv6.nd.na.flag.rsv == 0 + """, + PBBR_ETH=PBBR_ETH) + + # PBBR must send unsolicited NA for SBBR's DUA + pkts.filter_eth_src(PBBR_ETH).filter_LLANMA().filter_icmpv6_nd_na(SBBR_DUA).filter( + 'icmpv6.nd.na.flag.s == 0').must_next().must_verify(""" + icmpv6.opt.linkaddr == {PBBR_ETH} + and icmpv6.nd.na.flag.r == 1 + and icmpv6.nd.na.flag.o == 1 + and icmpv6.nd.na.flag.rsv == 0 + """, + PBBR_ETH=PBBR_ETH) + + # PBBR must send unsolicited NA for ROUTER1's DUA + pkts.filter_eth_src(PBBR_ETH).filter_LLANMA().filter_icmpv6_nd_na(ROUTER1_DUA).filter( + 'icmpv6.nd.na.flag.s == 0').must_next().must_verify(""" + icmpv6.opt.linkaddr == {PBBR_ETH} + and icmpv6.nd.na.flag.r == 1 + and icmpv6.nd.na.flag.o == 1 + and icmpv6.nd.na.flag.rsv == 0 + """, + PBBR_ETH=PBBR_ETH) + + # HOST should send NS for PBBR's DUA + pkts.filter_eth_src(HOST_ETH).filter_icmpv6_nd_ns(PBBR_DUA).must_next() + # PBBR must send solicited NA for PBBR's DUA + pkts.filter_eth_src(PBBR_ETH).filter_ipv6_dst(HOST_BGUA).filter_icmpv6_nd_na(PBBR_DUA).filter( + 'icmpv6.nd.na.flag.s == 1').must_next().must_verify(""" + icmpv6.opt.linkaddr == {PBBR_ETH} + and icmpv6.nd.na.flag.r == 1 + and icmpv6.nd.na.flag.o == 0 + and icmpv6.nd.na.flag.rsv == 0 + """, + PBBR_ETH=PBBR_ETH) + + # Host should send NS for SBBR's DUA + pkts.filter_eth_src(HOST_ETH).filter_icmpv6_nd_ns(SBBR_DUA).must_next() + # PBBR must send solicited NA for SBBR's DUA + pkts.filter_eth_src(PBBR_ETH).filter_ipv6_dst(HOST_BGUA).filter_icmpv6_nd_na(SBBR_DUA).filter( + 'icmpv6.nd.na.flag.s == 1').must_next().must_verify(""" + icmpv6.opt.linkaddr == {PBBR_ETH} + and icmpv6.nd.na.flag.r == 1 + and icmpv6.nd.na.flag.o == 0 + and icmpv6.nd.na.flag.rsv == 0 + """, + PBBR_ETH=PBBR_ETH) + + # Host should send NS for ROUTER1's DUA + pkts.filter_eth_src(HOST_ETH).filter_icmpv6_nd_ns(ROUTER1_DUA).must_next() + # PBBR must send solicited NA for ROUTER1's DUA + pkts.filter_eth_src(PBBR_ETH).filter_ipv6_dst(HOST_BGUA).filter_icmpv6_nd_na(ROUTER1_DUA).filter( + 'icmpv6.nd.na.flag.s == 1').must_next().must_verify(""" + icmpv6.opt.linkaddr == {PBBR_ETH} + and icmpv6.nd.na.flag.r == 1 + and icmpv6.nd.na.flag.o == 0 + and icmpv6.nd.na.flag.rsv == 0 + """, + PBBR_ETH=PBBR_ETH) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index f817d2682..eb1c4657a 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -217,6 +217,9 @@ class OtbrDocker: else: return lines + def _setup_sysctl(self): + self.bash(f'sysctl net.ipv6.conf.{self.ETH_DEV}.accept_ra=2') + class OtCli: @@ -2018,6 +2021,14 @@ class LinuxHost(): else: return super().ping(*args, **kwargs) + def ip_neighbors_flush(self): + # clear neigh cache on linux + self.bash(f'ip -6 neigh list dev {self.ETH_DEV}') + self.bash(f'ip -6 neigh flush nud all nud failed nud noarp dev {self.ETH_DEV}') + self.bash('ip -6 neigh list nud all dev %s | cut -d " " -f1 | sudo xargs -I{} ip -6 neigh delete {} dev %s' % + (self.ETH_DEV, self.ETH_DEV)) + self.bash(f'ip -6 neigh list dev {self.ETH_DEV}') + class OtbrNode(LinuxHost, NodeImpl, OtbrDocker): is_otbr = True @@ -2030,6 +2041,10 @@ class OtbrNode(LinuxHost, NodeImpl, OtbrDocker): def get_addrs(self) -> List[str]: return super().get_addrs() + self.get_ether_addrs() + def start(self): + self._setup_sysctl() + super().start() + class HostNode(LinuxHost, OtbrDocker): is_host = True @@ -2040,11 +2055,11 @@ class HostNode(LinuxHost, OtbrDocker): super().__init__(nodeid, **kwargs) def start(self): - # TODO: Use radvd to advertise the Domain Prefix on the Backbone link. - pass + self._setup_sysctl() + self._service_radvd_start() def stop(self): - pass + self._service_radvd_stop() def get_addrs(self) -> List[str]: return self.get_ether_addrs() @@ -2068,6 +2083,31 @@ class HostNode(LinuxHost, OtbrDocker): else: return None + def _service_radvd_start(self): + self.bash("""cat >/etc/radvd.conf </dev/null')