From 95d297523a5052ed9b66597797e5c2c136c0ff79 Mon Sep 17 00:00:00 2001 From: Li Cao Date: Thu, 22 Aug 2024 05:49:48 +0800 Subject: [PATCH] [ncp] integrate netif unicast address update (#2437) This commit integrates the Netif module with the `NcpHost` so that the unicast address table received from NCP will be updated to the network interface. The commit adds a expect test to verify the addresses are added/removed correctly. --- src/common/types.hpp | 33 +++++++++ src/ncp/CMakeLists.txt | 1 + src/ncp/ncp_host.cpp | 6 ++ src/ncp/ncp_host.hpp | 2 + src/ncp/ncp_spinel.cpp | 46 ++++++++++++ src/ncp/ncp_spinel.hpp | 21 +++++- src/ncp/posix/netif.cpp | 14 +--- src/ncp/posix/netif.hpp | 33 +-------- src/ncp/posix/netif_unix.cpp | 5 ++ tests/gtest/test_netif.cpp | 26 ++++--- .../expect/ncp_netif_address_update.exp | 74 +++++++++++++++++++ 11 files changed, 205 insertions(+), 56 deletions(-) create mode 100755 tests/scripts/expect/ncp_netif_address_update.exp diff --git a/src/common/types.hpp b/src/common/types.hpp index e5ba3ea32e9..7677b14c5df 100644 --- a/src/common/types.hpp +++ b/src/common/types.hpp @@ -430,6 +430,39 @@ class Ip6Prefix uint8_t mLength; ///< The IPv6 prefix length (in bits). }; +/** + * This class represents a Ipv6 address and its info. + * + */ +class Ip6AddressInfo +{ +public: + Ip6AddressInfo(void) { Clear(); } + + Ip6AddressInfo(const otIp6Address &aAddress, + uint8_t aPrefixLength, + uint8_t aScope, + bool aPreferred, + bool aMeshLocal) + : mAddress(aAddress) + , mPrefixLength(aPrefixLength) + , mScope(aScope) + , mPreferred(aPreferred) + , mMeshLocal(aMeshLocal) + { + } + + void Clear(void) { memset(reinterpret_cast(this), 0, sizeof(*this)); } + + otIp6Address mAddress; + uint8_t mPrefixLength; + uint8_t mScope : 4; + bool mPreferred : 1; + bool mMeshLocal : 1; + + bool operator==(const Ip6AddressInfo &aOther) const { return memcmp(this, &aOther, sizeof(Ip6AddressInfo)) == 0; } +}; + /** * This class represents an ethernet MAC address. */ diff --git a/src/ncp/CMakeLists.txt b/src/ncp/CMakeLists.txt index c22ae16293b..9e706ee0c5f 100644 --- a/src/ncp/CMakeLists.txt +++ b/src/ncp/CMakeLists.txt @@ -43,6 +43,7 @@ add_library(otbr-ncp target_link_libraries(otbr-ncp PRIVATE otbr-common + otbr-posix $<$:otbr-proto> $<$:otbr-proto> ) diff --git a/src/ncp/ncp_host.cpp b/src/ncp/ncp_host.cpp index 5dd7790949c..26bd7f85e5b 100644 --- a/src/ncp/ncp_host.cpp +++ b/src/ncp/ncp_host.cpp @@ -65,6 +65,7 @@ void NcpNetworkProperties::SetDeviceRole(otDeviceRole aRole) NcpHost::NcpHost(const char *aInterfaceName, bool aDryRun) : mSpinelDriver(*static_cast(otSysGetSpinelDriver())) + , mNetif() { memset(&mConfig, 0, sizeof(mConfig)); mConfig.mInterfaceName = aInterfaceName; @@ -81,11 +82,16 @@ void NcpHost::Init(void) { otSysInit(&mConfig); mNcpSpinel.Init(mSpinelDriver, *this); + mNetif.Init(mConfig.mInterfaceName); + + mNcpSpinel.Ip6SetAddressCallback( + [this](const std::vector &aAddrInfos) { mNetif.UpdateIp6UnicastAddresses(aAddrInfos); }); } void NcpHost::Deinit(void) { mNcpSpinel.Deinit(); + mNetif.Deinit(); otSysDeinit(); } diff --git a/src/ncp/ncp_host.hpp b/src/ncp/ncp_host.hpp index fbcb5214b4d..9d74177bbed 100644 --- a/src/ncp/ncp_host.hpp +++ b/src/ncp/ncp_host.hpp @@ -40,6 +40,7 @@ #include "common/mainloop.hpp" #include "ncp/ncp_spinel.hpp" #include "ncp/thread_host.hpp" +#include "posix/netif.hpp" namespace otbr { namespace Ncp { @@ -105,6 +106,7 @@ class NcpHost : public MainloopProcessor, public ThreadHost, public NcpNetworkPr otPlatformConfig mConfig; NcpSpinel mNcpSpinel; TaskRunner mTaskRunner; + Netif mNetif; }; } // namespace Ncp diff --git a/src/ncp/ncp_spinel.cpp b/src/ncp/ncp_spinel.cpp index a5d8891270f..f80f3627114 100644 --- a/src/ncp/ncp_spinel.cpp +++ b/src/ncp/ncp_spinel.cpp @@ -356,6 +356,16 @@ void NcpSpinel::HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, ui break; } + case SPINEL_PROP_IPV6_ADDRESS_TABLE: + { + std::vector addressInfoTable; + + VerifyOrExit(ParseIp6AddressTable(aBuffer, aLength, addressInfoTable) == OT_ERROR_NONE, + error = OTBR_ERROR_PARSE); + SafeInvoke(mIp6AddressTableCallback, addressInfoTable); + break; + } + default: otbrLogWarning("Received uncognized key: %u", aKey); break; @@ -486,6 +496,42 @@ otError NcpSpinel::SendEncodedFrame(void) return error; } +otError NcpSpinel::ParseIp6AddressTable(const uint8_t *aBuf, + uint16_t aLength, + std::vector &aAddressTable) +{ + otError error = OT_ERROR_NONE; + ot::Spinel::Decoder decoder; + + VerifyOrExit(aBuf != nullptr, error = OT_ERROR_INVALID_ARGS); + decoder.Init(aBuf, aLength); + + while (!decoder.IsAllReadInStruct()) + { + Ip6AddressInfo cur; + const otIp6Address *addr; + uint8_t prefixLength; + uint32_t preferredLifetime; + uint32_t validLifetime; + + SuccessOrExit(error = decoder.OpenStruct()); + SuccessOrExit(error = decoder.ReadIp6Address(addr)); + memcpy(&cur.mAddress, addr, sizeof(otIp6Address)); + SuccessOrExit(error = decoder.ReadUint8(prefixLength)); + cur.mPrefixLength = prefixLength; + SuccessOrExit(error = decoder.ReadUint32(preferredLifetime)); + cur.mPreferred = preferredLifetime ? true : false; + SuccessOrExit(error = decoder.ReadUint32(validLifetime)); + OTBR_UNUSED_VARIABLE(validLifetime); + SuccessOrExit((error = decoder.CloseStruct())); + + aAddressTable.push_back(cur); + } + +exit: + return error; +} + otDeviceRole NcpSpinel::SpinelRoleToDeviceRole(spinel_net_role_t aRole) { otDeviceRole role = OT_DEVICE_ROLE_DISABLED; diff --git a/src/ncp/ncp_spinel.hpp b/src/ncp/ncp_spinel.hpp index 418446b8e8f..2221d757fdd 100644 --- a/src/ncp/ncp_spinel.hpp +++ b/src/ncp/ncp_spinel.hpp @@ -83,6 +83,8 @@ class PropsObserver class NcpSpinel { public: + using Ip6AddressTableCallback = std::function &)>; + /** * Constructor. * @@ -147,6 +149,18 @@ class NcpSpinel */ void Ip6SetEnabled(bool aEnable, AsyncTaskPtr aAsyncTask); + /** + * This method sets the callback to receive the IPv6 address table from the NCP. + * + * The callback will be invoked when receiving an IPv6 address table from the NCP. When the + * callback is invoked, the callback MUST copy the otIp6AddressInfo objects and maintain it + * if it's not used immediately (within the callback). + * + * @param[in] aCallback The callback to handle the IP6 address table. + * + */ + void Ip6SetAddressCallback(const Ip6AddressTableCallback &aCallback) { mIp6AddressTableCallback = aCallback; } + /** * This method enableds/disables the Thread network on the NCP. * @@ -186,12 +200,11 @@ class NcpSpinel static constexpr uint8_t kMaxTids = 16; - template static void CallAndClear(Function &aFunc, Args &&...aArgs) + template static void SafeInvoke(Function &aFunc, Args &&...aArgs) { if (aFunc) { aFunc(std::forward(aArgs)...); - aFunc = nullptr; } } @@ -231,6 +244,8 @@ class NcpSpinel otError SetProperty(spinel_prop_key_t aKey, const EncodingFunc &aEncodingFunc); otError SendEncodedFrame(void); + otError ParseIp6AddressTable(const uint8_t *aBuf, uint16_t aLength, std::vector &aAddressTable); + ot::Spinel::SpinelDriver *mSpinelDriver; uint16_t mCmdTidsInUse; ///< Used transaction ids. spinel_tid_t mCmdNextTid; ///< Next available transaction id. @@ -255,6 +270,8 @@ class NcpSpinel AsyncTaskPtr mThreadSetEnabledTask; AsyncTaskPtr mThreadDetachGracefullyTask; AsyncTaskPtr mThreadErasePersistentInfoTask; + + Ip6AddressTableCallback mIp6AddressTableCallback; }; } // namespace Ncp diff --git a/src/ncp/posix/netif.cpp b/src/ncp/posix/netif.cpp index abb69e743f4..90ff4f20cec 100644 --- a/src/ncp/posix/netif.cpp +++ b/src/ncp/posix/netif.cpp @@ -87,13 +87,12 @@ void Netif::Deinit(void) Clear(); } -void Netif::UpdateIp6UnicastAddresses(const std::vector &aOtAddrInfos) +void Netif::UpdateIp6UnicastAddresses(const std::vector &aAddrInfos) { // Remove stale addresses for (const Ip6AddressInfo &addrInfo : mIp6UnicastAddresses) { - auto comparator = [&addrInfo](const otIp6AddressInfo &aOtAddrInfo) { return addrInfo == aOtAddrInfo; }; - if (std::find_if(aOtAddrInfos.begin(), aOtAddrInfos.end(), comparator) == aOtAddrInfos.end()) + if (std::find(aAddrInfos.begin(), aAddrInfos.end(), addrInfo) == aAddrInfos.end()) { otbrLogInfo("Remove address: %s", Ip6Address(addrInfo.mAddress).ToString().c_str()); // TODO: Verify success of the addition or deletion in Netlink response. @@ -102,9 +101,8 @@ void Netif::UpdateIp6UnicastAddresses(const std::vector &aOtAd } // Add new addresses - for (const otIp6AddressInfo &otAddrInfo : aOtAddrInfos) + for (const Ip6AddressInfo &addrInfo : aAddrInfos) { - Ip6AddressInfo addrInfo(otAddrInfo); if (std::find(mIp6UnicastAddresses.begin(), mIp6UnicastAddresses.end(), addrInfo) == mIp6UnicastAddresses.end()) { otbrLogInfo("Add address: %s", Ip6Address(addrInfo.mAddress).ToString().c_str()); @@ -113,11 +111,7 @@ void Netif::UpdateIp6UnicastAddresses(const std::vector &aOtAd } } - mIp6UnicastAddresses.clear(); - for (const otIp6AddressInfo &otAddrInfo : aOtAddrInfos) - { - mIp6UnicastAddresses.emplace_back(Ip6AddressInfo(otAddrInfo)); - } + mIp6UnicastAddresses.assign(aAddrInfos.begin(), aAddrInfos.end()); } void Netif::Clear(void) diff --git a/src/ncp/posix/netif.hpp b/src/ncp/posix/netif.hpp index 2cdce564a00..24a82439411 100644 --- a/src/ncp/posix/netif.hpp +++ b/src/ncp/posix/netif.hpp @@ -52,43 +52,12 @@ class Netif otbrError Init(const std::string &aInterfaceName); void Deinit(void); - void UpdateIp6UnicastAddresses(const std::vector &aOtAddrInfos); + void UpdateIp6UnicastAddresses(const std::vector &aAddrInfos); private: // TODO: Retrieve the Maximum Ip6 size from the coprocessor. static constexpr size_t kIp6Mtu = 1280; - class Ip6AddressInfo - { - public: - explicit Ip6AddressInfo(const otIp6AddressInfo &aOtAddrInfo) - : mPrefixLength(aOtAddrInfo.mPrefixLength) - , mScope(aOtAddrInfo.mScope) - , mPreferred(aOtAddrInfo.mPreferred) - , mMeshLocal(aOtAddrInfo.mMeshLocal) - { - memcpy(&mAddress, aOtAddrInfo.mAddress, sizeof(otIp6Address)); - } - - otIp6Address mAddress; ///< A pointer to the IPv6 address. - uint8_t mPrefixLength; ///< The prefix length of mAddress if it is a unicast address. - uint8_t mScope : 4; ///< The scope of this address. - bool mPreferred : 1; ///< Whether this is a preferred address. - bool mMeshLocal : 1; ///< Whether this is a mesh-local unicast/anycast address. - - bool operator==(const Ip6AddressInfo &aOther) const - { - return memcmp(this, &aOther, sizeof(Ip6AddressInfo)) == 0; - } - - bool operator==(const otIp6AddressInfo &aOtAddrInfo) const - { - return memcmp(&mAddress, aOtAddrInfo.mAddress, sizeof(mAddress)) == 0 && - mPrefixLength == aOtAddrInfo.mPrefixLength && mScope == aOtAddrInfo.mScope && - mPreferred == aOtAddrInfo.mPreferred && mMeshLocal == aOtAddrInfo.mMeshLocal; - } - }; - void Clear(void); otbrError CreateTunDevice(const std::string &aInterfaceName); diff --git a/src/ncp/posix/netif_unix.cpp b/src/ncp/posix/netif_unix.cpp index 57c1deb5863..7a7a4987328 100644 --- a/src/ncp/posix/netif_unix.cpp +++ b/src/ncp/posix/netif_unix.cpp @@ -48,6 +48,11 @@ otbrError Netif::CreateTunDevice(const std::string &aInterfaceName) return OTBR_ERROR_NONE; } +otbrError Netif::InitNetlink(void) +{ + return OTBR_ERROR_NONE; +} + void Netif::PlatformSpecificInit(void) { /* Empty */ diff --git a/tests/gtest/test_netif.cpp b/tests/gtest/test_netif.cpp index 36f58801439..6925a0941da 100644 --- a/tests/gtest/test_netif.cpp +++ b/tests/gtest/test_netif.cpp @@ -260,12 +260,13 @@ TEST(Netif, WpanIfHasCorrectUnicastAddresses_AfterUpdatingUnicastAddresses) otbr::Netif netif; EXPECT_EQ(netif.Init(wpan), OT_ERROR_NONE); - otIp6AddressInfo testArray1[] = { - {&kLl, 64, 0, 1, 0}, - {&kMlEid, 64, 0, 1, 1}, - {&kMlRloc, 64, 0, 1, 1}, + otbr::Ip6AddressInfo testArray1[] = { + {kLl, 64, 0, 1, 0}, + {kMlEid, 64, 0, 1, 1}, + {kMlRloc, 64, 0, 1, 1}, }; - std::vector testVec1(testArray1, testArray1 + sizeof(testArray1) / sizeof(otIp6AddressInfo)); + std::vector testVec1(testArray1, + testArray1 + sizeof(testArray1) / sizeof(otbr::Ip6AddressInfo)); netif.UpdateIp6UnicastAddresses(testVec1); std::vector wpan_addrs = GetAllIp6Addrs(wpan); EXPECT_EQ(wpan_addrs.size(), 3); @@ -273,13 +274,14 @@ TEST(Netif, WpanIfHasCorrectUnicastAddresses_AfterUpdatingUnicastAddresses) EXPECT_THAT(wpan_addrs, ::testing::Contains(kMlEidStr)); EXPECT_THAT(wpan_addrs, ::testing::Contains(kMlRlocStr)); - otIp6AddressInfo testArray2[] = { - {&kLl, 64, 0, 1, 0}, - {&kMlEid, 64, 0, 1, 1}, - {&kMlRloc, 64, 0, 1, 1}, - {&kMlAloc, 64, 0, 1, 1}, + otbr::Ip6AddressInfo testArray2[] = { + {kLl, 64, 0, 1, 0}, + {kMlEid, 64, 0, 1, 1}, + {kMlRloc, 64, 0, 1, 1}, + {kMlAloc, 64, 0, 1, 1}, }; - std::vector testVec2(testArray2, testArray2 + sizeof(testArray2) / sizeof(otIp6AddressInfo)); + std::vector testVec2(testArray2, + testArray2 + sizeof(testArray2) / sizeof(otbr::Ip6AddressInfo)); netif.UpdateIp6UnicastAddresses(testVec2); wpan_addrs = GetAllIp6Addrs(wpan); EXPECT_EQ(wpan_addrs.size(), 4); @@ -288,7 +290,7 @@ TEST(Netif, WpanIfHasCorrectUnicastAddresses_AfterUpdatingUnicastAddresses) EXPECT_THAT(wpan_addrs, ::testing::Contains(kMlRlocStr)); EXPECT_THAT(wpan_addrs, ::testing::Contains(kMlAlocStr)); - std::vector testVec3; + std::vector testVec3; netif.UpdateIp6UnicastAddresses(testVec3); wpan_addrs = GetAllIp6Addrs(wpan); EXPECT_EQ(wpan_addrs.size(), 0); diff --git a/tests/scripts/expect/ncp_netif_address_update.exp b/tests/scripts/expect/ncp_netif_address_update.exp new file mode 100755 index 00000000000..95329e0bef3 --- /dev/null +++ b/tests/scripts/expect/ncp_netif_address_update.exp @@ -0,0 +1,74 @@ +#!/usr/bin/expect -f +# +# Copyright (c) 2024, 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. +# +source "tests/scripts/expect/_common.exp" + +# Dataset +# Mesh Local Prefix: fd0d:7fc:a1b9:f050::/64 +set dataset_dbus "0x0e,0x08,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x14,0x35,0x06,0x00,0x04,0x00,0x1f,0xff,0xe0,0x02,0x08,0x7d,0x61,0xeb,0x42,0xcd,0xc4,0x8d,0x6a,0x07,0x08,0xfd,0x0d,0x07,0xfc,0xa1,0xb9,0xf0,0x50,0x05,0x10,0xba,0x08,0x8f,0xc2,0xbd,0x6c,0x3b,0x38,0x97,0xf7,0xa1,0x0f,0x58,0x26,0x3f,0xf3,0x03,0x0f,0x4f,0x70,0x65,0x6e,0x54,0x68,0x72,0x65,0x61,0x64,0x2d,0x35,0x32,0x34,0x66,0x01,0x02,0x52,0x4f,0x04,0x10,0x9d,0xc0,0x23,0xcc,0xd4,0x47,0xb1,0x2b,0x50,0x99,0x7e,0xf6,0x80,0x20,0xf1,0x9e,0x0c,0x04,0x02,0xa0,0xf7,0xf8" + +# Step 1. Start otbr-agent with a NCP and join the network by dbus join method. +spawn_node 2 otbr $::env(EXP_OT_NCP_PATH) +sleep 1 + +spawn dbus-send --system --dest=io.openthread.BorderRouter.wpan0 --type=method_call --print-reply /io/openthread/BorderRouter/wpan0 io.openthread.BorderRouter.Join "array:byte:${dataset_dbus}" +expect eof + +# Step 2. Wait 10 seconds for it becomes a leader. +sleep 10 +spawn dbus-send --system --dest=io.openthread.BorderRouter.wpan0 --print-reply --reply-timeout=1000 /io/openthread/BorderRouter/wpan0 org.freedesktop.DBus.Properties.Get string:io.openthread.BorderRouter string:DeviceRole +expect -re {leader} { +} timeout { + puts "timeout!" + exit 1 +} +expect eof + +# Step 3. Verify the addresses on wpan. +# There should be: +# 1. ml eid +# 2. ml anycast +# 3. ml rloc +# 4. link local +spawn ip addr show wpan0 +expect -re {fd0d:7fc:a1b9:f050(:[0-9a-f]{1,4}){4,4}} +expect -re {fd0d:7fc:a1b9:f050(:[0-9a-f]{1,4}){4,4}} +expect -re {fd0d:7fc:a1b9:f050(:[0-9a-f]{1,4}){4,4}} +expect -re {fe80:(:[0-9a-f]{1,4}){4,4}} +expect eof + +# Step 4. Use dbus leave method to let the node leave the network +spawn dbus-send --system --dest=io.openthread.BorderRouter.wpan0 --type=method_call --print-reply /io/openthread/BorderRouter/wpan0 io.openthread.BorderRouter.LeaveNetwork +expect eof + +# Step 5. Verify the addresses on wpan. +# There should be: +# 1. link local +spawn ip addr show wpan0 +expect -re {fe80:(:[0-9a-f]{1,4}){4,4}} +expect eof