From da0d9f66b66b4629409e41adc9ffa3d46241b1fb Mon Sep 17 00:00:00 2001 From: kangping Date: Fri, 26 Feb 2021 05:19:31 +0800 Subject: [PATCH] [posix] handle dynamic infra if status (#6186) This commit includes below changes: - handles dynamic infrastructure interface status changes. The Routing Manager will be started if the infra interface status turns to be RUNNING and a valid link-local address is present. Otherwise, the Routing Manager will be stopped. - changes the default status of the Routing Manager from enabled to disabled and it is initially enabled for the posix platform. - the posix implementation now doesn't require the presence of the link-local address at initialization stage. In this case, the Routing Manager will not be started before a link-local address is added to the infra interface. - add CI tests for the new behavior. --- include/openthread/border_router.h | 24 +- include/openthread/instance.h | 2 +- include/openthread/platform/infra_if.h | 33 ++- src/core/api/border_router_api.cpp | 8 +- src/core/border_router/infra_if_platform.cpp | 12 + src/core/border_router/routing_manager.cpp | 84 +++++-- src/core/border_router/routing_manager.hpp | 50 ++++- src/posix/platform/infra_if.cpp | 208 ++++++++++++++---- src/posix/platform/platform-posix.h | 29 ++- src/posix/platform/system.cpp | 22 +- .../test_single_border_router.py | 54 +++++ tests/scripts/thread-cert/node.py | 12 + 12 files changed, 432 insertions(+), 106 deletions(-) diff --git a/include/openthread/border_router.h b/include/openthread/border_router.h index dcc0889bc..c1e489123 100644 --- a/include/openthread/border_router.h +++ b/include/openthread/border_router.h @@ -57,20 +57,30 @@ extern "C" { * * @note This method MUST be called before any other otBorderRouting* APIs. * - * @param[in] aInstance A pointer to an OpenThread instance. - * @param[in] aInfraIfIndex The infrastructure interface index. + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aInfraIfIndex The infrastructure interface index. + * @param[in] aInfraIfIsRunning A boolean that indicates whether the infrastructure + * interface is running. + * @param[in] aInfraIfLinkLocalAddress A pointer to the IPv6 link-local address of the infrastructure + * interface. NULL if the IPv6 link-local address is missing. * - * @retval OT_ERROR_NONE Successfully started the Border Routing manager on given infrastructure. - * @retval OT_ERROR_INVALID_ARGS The index of the infra interface is not valid. - * @retval OT_ERROR_FAILED Internal failure. This is usually failed to generate random prefixes. + * @retval OT_ERROR_NONE Successfully started the Border Routing Manager on given infrastructure. + * @retval OT_ERROR_INVALID_STATE The Border Routing Manager has already been initialized. + * @retval OT_ERROR_INVALID_ARGS The index or the IPv6 link-local address of the infra interface is not valid. + * @retval OT_ERROR_FAILED Internal failure. Usually due to failure in generating random prefixes. + * + * @sa otPlatInfraIfStateChanged. * */ -otError otBorderRoutingInit(otInstance *aInstance, uint32_t aInfraIfIndex); +otError otBorderRoutingInit(otInstance * aInstance, + uint32_t aInfraIfIndex, + bool aInfraIfIsRunning, + const otIp6Address *aInfraIfLinkLocalAddress); /** * This method enables/disables the Border Routing Manager. * - * @note The Border Routing Manager is enabled by default. + * @note The Border Routing Manager is disabled by default. * * @param[in] aInstance A pointer to an OpenThread instance. * @param[in] aEnabled A boolean to enable/disable the routing manager. diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 186edd09e..300c75e40 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 (79) +#define OPENTHREAD_API_VERSION (80) /** * @addtogroup api-instance diff --git a/include/openthread/platform/infra_if.h b/include/openthread/platform/infra_if.h index 959dfdd01..d052eb2ac 100644 --- a/include/openthread/platform/infra_if.h +++ b/include/openthread/platform/infra_if.h @@ -46,16 +46,6 @@ extern "C" { #endif -/** - * This method returns the IPv6 link-local address of given infrastructure interface. - * - * @param[in] aInfraIfIndex The index of the infrastructure interface. - * - * @returns A pointer to the IPv6 link-local address. NULL if no valid IPv6 link-local address found. - * - */ -const otIp6Address *otPlatInfraIfGetLinkLocalAddress(uint32_t aInfraIfIndex); - /** * This method sends an ICMPv6 Neighbor Discovery message on given infrastructure interface. * @@ -100,6 +90,29 @@ extern void otPlatInfraIfRecvIcmp6Nd(otInstance * aInstance, const uint8_t * aBuffer, uint16_t aBufferLength); +/** + * The infra interface driver calls this method to notify OpenThread + * of the interface state changes. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aInfraIfIndex The index of the infrastructure interface. + * @param[in] aIsRunning A boolean that indicates whether the infrastructure + * interface is running. + * @param[in] aLinkLocalAddress A pointer to the IPv6 link-local address of the infrastructure + * interface. NULL if the IPv6 link-local address is lost. + * + * @retval OT_ERROR_NONE Successfully updated the infra interface status. + * @retval OT_ERROR_INVALID_STATE The Routing Manager is not initialized. + * @retval OT_ERROR_INVALID_ARGS The @p aInfraIfIndex doesn't match the infra interface the + * Routing Manager are initialized with, or the @p aLinkLocalAddress + * is not a valid IPv6 link-local address. + * + */ +extern otError otPlatInfraIfStateChanged(otInstance * aInstance, + uint32_t aInfraIfIndex, + bool aIsRunning, + const otIp6Address *aLinkLocalAddress); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/core/api/border_router_api.cpp b/src/core/api/border_router_api.cpp index 720498713..8fca3a3c0 100644 --- a/src/core/api/border_router_api.cpp +++ b/src/core/api/border_router_api.cpp @@ -44,11 +44,15 @@ using namespace ot; #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE -otError otBorderRoutingInit(otInstance *aInstance, uint32_t aInfraIfIndex) +otError otBorderRoutingInit(otInstance * aInstance, + uint32_t aInfraIfIndex, + bool aInfraIfIsRunning, + const otIp6Address *aInfraIfLinkLocalAddress) { Instance &instance = *static_cast(aInstance); - return instance.Get().Init(aInfraIfIndex); + return instance.Get().Init( + aInfraIfIndex, aInfraIfIsRunning, static_cast(aInfraIfLinkLocalAddress)); } otError otBorderRoutingSetEnabled(otInstance *aInstance, bool aEnabled) diff --git a/src/core/border_router/infra_if_platform.cpp b/src/core/border_router/infra_if_platform.cpp index 720acca0c..8a0d783be 100644 --- a/src/core/border_router/infra_if_platform.cpp +++ b/src/core/border_router/infra_if_platform.cpp @@ -52,4 +52,16 @@ extern "C" void otPlatInfraIfRecvIcmp6Nd(otInstance * aInstance, instance.Get().RecvIcmp6Message( aInfraIfIndex, static_cast(*aSrcAddress), aBuffer, aBufferLength); } + +extern "C" otError otPlatInfraIfStateChanged(otInstance * aInstance, + uint32_t aInfraIfIndex, + bool aIsRunning, + const otIp6Address *aLinkLocalAddress) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().HandleInfraIfStateChanged( + aInfraIfIndex, aIsRunning, static_cast(aLinkLocalAddress)); +} + #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index 64bf4dc98..d55a5acac 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -60,8 +60,9 @@ namespace BorderRouter { RoutingManager::RoutingManager(Instance &aInstance) : InstanceLocator(aInstance) , mIsRunning(false) + , mIsEnabled(false) + , mInfraIfIsRunning(false) , mInfraIfIndex(0) - , mEnabled(true) // The routing manager is by default enabled. , mAdvertisedOmrPrefixNum(0) , mAdvertisedOnLinkPrefix(nullptr) , mDiscoveredPrefixNum(0) @@ -71,6 +72,8 @@ RoutingManager::RoutingManager(Instance &aInstance) , mRouterSolicitTimer(aInstance, HandleRouterSolicitTimer) , mRouterSolicitCount(0) { + mInfraIfLinkLocalAddress.Clear(); + mLocalOmrPrefix.Clear(); memset(mAdvertisedOmrPrefixes, 0, sizeof(mAdvertisedOmrPrefixes)); @@ -79,11 +82,13 @@ RoutingManager::RoutingManager(Instance &aInstance) memset(mDiscoveredPrefixes, 0, sizeof(mDiscoveredPrefixes)); } -otError RoutingManager::Init(uint32_t aInfraIfIndex) +otError RoutingManager::Init(uint32_t aInfraIfIndex, + bool aInfraIfIsRunning, + const Ip6::Address *aInfraIfLinkLocalAddress) { otError error; - OT_ASSERT(!IsInitialized()); + VerifyOrExit(!IsInitialized(), error = OT_ERROR_INVALID_STATE); VerifyOrExit(aInfraIfIndex > 0, error = OT_ERROR_INVALID_ARGS); SuccessOrExit(error = LoadOrGenerateRandomOmrPrefix()); @@ -91,7 +96,14 @@ otError RoutingManager::Init(uint32_t aInfraIfIndex) mInfraIfIndex = aInfraIfIndex; + // Initialize the infra interface status. + SuccessOrExit(error = HandleInfraIfStateChanged(mInfraIfIndex, aInfraIfIsRunning, aInfraIfLinkLocalAddress)); + exit: + if (error != OT_ERROR_NONE) + { + mInfraIfIndex = 0; + } return error; } @@ -101,18 +113,10 @@ otError RoutingManager::SetEnabled(bool aEnabled) VerifyOrExit(IsInitialized(), error = OT_ERROR_INVALID_STATE); - VerifyOrExit(aEnabled != mEnabled); + VerifyOrExit(aEnabled != mIsEnabled); - mEnabled = aEnabled; - - if (!mEnabled) - { - Stop(); - } - else if (Get().IsAttached()) - { - Start(); - } + mIsEnabled = aEnabled; + EvaluateState(); exit: return error; @@ -172,6 +176,18 @@ exit: return error; } +void RoutingManager::EvaluateState(void) +{ + if (mIsEnabled && Get().IsAttached() && mInfraIfIsRunning && mInfraIfLinkLocalAddress.IsLinkLocal()) + { + Start(); + } + else + { + Stop(); + } +} + void RoutingManager::Start(void) { if (!mIsRunning) @@ -232,7 +248,7 @@ void RoutingManager::RecvIcmp6Message(uint32_t aInfraIfIndex, VerifyOrExit(IsInitialized() && mIsRunning, error = OT_ERROR_DROP); VerifyOrExit(aInfraIfIndex == mInfraIfIndex, error = OT_ERROR_DROP); - infraLinkLocalAddr = static_cast(otPlatInfraIfGetLinkLocalAddress(mInfraIfIndex)); + infraLinkLocalAddr = static_cast(&mInfraIfLinkLocalAddress); // Drop any ICMPv6 messages sent from myself. VerifyOrExit(infraLinkLocalAddr != nullptr && aSrcAddress != *infraLinkLocalAddr, error = OT_ERROR_DROP); @@ -260,20 +276,42 @@ exit: } } +otError RoutingManager::HandleInfraIfStateChanged(uint32_t aInfraIfIndex, + bool aIsRunning, + const Ip6::Address *aLinkLocalAddress) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(IsInitialized(), error = OT_ERROR_INVALID_STATE); + VerifyOrExit(aInfraIfIndex == mInfraIfIndex, error = OT_ERROR_INVALID_ARGS); + VerifyOrExit(aLinkLocalAddress == nullptr || aLinkLocalAddress->IsLinkLocal(), error = OT_ERROR_INVALID_ARGS); + + otLogInfoBr("infra interface state changed: %s, link-local-addr=%s", aIsRunning ? "RUNNING" : "NOT RUNNING", + (aLinkLocalAddress != nullptr) ? aLinkLocalAddress->ToString().AsCString() : "(null)"); + + mInfraIfIsRunning = aIsRunning; + if (aLinkLocalAddress == nullptr) + { + mInfraIfLinkLocalAddress.Clear(); + } + else + { + mInfraIfLinkLocalAddress = *aLinkLocalAddress; + } + + EvaluateState(); + +exit: + return error; +} + void RoutingManager::HandleNotifierEvents(Events aEvents) { VerifyOrExit(IsInitialized() && IsEnabled()); if (aEvents.Contains(kEventThreadRoleChanged)) { - if (Get().IsAttached()) - { - Start(); - } - else - { - Stop(); - } + EvaluateState(); } if (aEvents.Contains(kEventThreadNetdataChanged)) diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index 55bfb242b..3fa6b1192 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -79,12 +79,16 @@ public: * This method initializes the routing manager on given infrastructure interface. * * @param[in] aInfraIfIndex An infrastructure network interface index. + * @param[in] aInfraIfIsRunning A boolean that indicates whether the infrastructure + * interface is running. + * @param[in] aInfraIfLinkLocalAddress A pointer to the IPv6 link-local address of the infrastructure + * interface. NULL if the IPv6 link-local address is missing. * * @retval OT_ERROR_NONE Successfully started the routing manager. * @retval OT_ERROR_INVALID_ARGS The index of the infra interface is not valid. * */ - otError Init(uint32_t aInfraIfIndex); + otError Init(uint32_t aInfraIfIndex, bool aInfraIfIsRunning, const Ip6::Address *aInfraIfLinkLocalAddress); /** * This method enables/disables the Border Routing Manager. @@ -115,6 +119,24 @@ public: const uint8_t * aBuffer, uint16_t aBufferLength); + /** + * This method handles infrastructure interface state changes. + * + * @param[in] aInfraIfIndex The index of the infrastructure interface. + * @param[in] aIsRunning A boolean that indicates whether the infrastructure + * interface is running. + * @param[in] aLinkLocalAddress A pointer to the IPv6 link local address of the infrastructure + * interface. NULL if the IPv6 link local address is lost. + * + * @retval OT_ERROR_NONE Successfully updated the infra interface status. + * @retval OT_ERROR_INVALID_STATE The Routing Manager is not initialized. + * @retval OT_ERROR_INVALID_ARGS The @p aInfraIfIndex doesn't match the infra interface the + * Routing Manager are initialized with, or the @p aLinkLocalAddress + * is not a valid IPv6 link-local address. + * + */ + otError HandleInfraIfStateChanged(uint32_t aInfraIfIndex, bool aIsRunning, const Ip6::Address *aLinkLocalAddress); + private: enum : uint16_t { @@ -161,11 +183,12 @@ private: bool mIsOnLinkPrefix; }; + void EvaluateState(void); void Start(void); void Stop(void); void HandleNotifierEvents(Events aEvents); bool IsInitialized(void) const { return mInfraIfIndex != 0; } - bool IsEnabled(void) const { return mEnabled; } + bool IsEnabled(void) const { return mIsEnabled; } otError LoadOrGenerateRandomOmrPrefix(void); otError LoadOrGenerateRandomOnLinkPrefix(void); @@ -210,9 +233,28 @@ private: static bool ContainsPrefix(const Ip6::Prefix &aPrefix, const Ip6::Prefix *aPrefixList, uint8_t aPrefixNum); static uint32_t GetPrefixExpireDelay(uint32_t aValidLifetime); - bool mIsRunning; + // Indicates whether the Routing Manager is running (started). + bool mIsRunning; + + // Indicates whether the Routing manager is enabled. + // The Routing Manager will be stopped if we are + // disabled. + bool mIsEnabled; + + // Indicates whether the infra interface is running. + // The Routing Manager will be stopped when the + // Infra interface is not running. + bool mInfraIfIsRunning; + + // The index of the infra interface on which Router + // Advertisement messages will be sent. uint32_t mInfraIfIndex; - bool mEnabled; + + // The IPv6 link-local address of the infra interface. + // It's UNSPECIFIED if no valid IPv6 link-local address + // is associated with the infra interface and the Routing + // Manager will be stopped. + Ip6::Address mInfraIfLinkLocalAddress; // The OMR prefix loaded from local persistent storage or randomly generated // if non is found in persistent storage. diff --git a/src/posix/platform/infra_if.cpp b/src/posix/platform/infra_if.cpp index d6bd0e5c1..613f2f3c4 100644 --- a/src/posix/platform/infra_if.cpp +++ b/src/posix/platform/infra_if.cpp @@ -45,24 +45,30 @@ #include #include // clang-format on +#include #include #include +#ifdef __linux__ +#include +#endif +#include #include #include "common/code_utils.hpp" +#include "common/debug.hpp" #include "lib/platform/exit_code.h" static char sInfraIfName[IFNAMSIZ]; static uint32_t sInfraIfIndex = 0; static int sInfraIfIcmp6Socket = -1; +static int sNetLinkSocket = -1; static otIp6Address sInfraIfLinkLocalAddr; -const otIp6Address *otPlatInfraIfGetLinkLocalAddress(uint32_t aInfraIfIndex) -{ - VerifyOrDie(aInfraIfIndex == sInfraIfIndex, OT_EXIT_FAILURE); - return &sInfraIfLinkLocalAddr; -} +static int CreateIcmp6Socket(void); +static int CreateNetLinkSocket(void); +static void ReceiveNetLinkMessage(otInstance *aInstance); +static void ReceiveIcmp6Message(otInstance *aInstance); otError otPlatInfraIfSendIcmp6Nd(uint32_t aInfraIfIndex, const otIp6Address *aDestAddress, @@ -138,10 +144,10 @@ exit: return error; } -static otError InitLinkLocalAddress(void) +const otIp6Address *platformInfraIfGetLinkLocalAddress(void) { - otError error; - struct ifaddrs *ifAddrs = nullptr; + const otIp6Address *ret = nullptr; + struct ifaddrs * ifAddrs = nullptr; VerifyOrDie(getifaddrs(&ifAddrs) != -1, OT_EXIT_ERROR_ERRNO); @@ -158,51 +164,50 @@ static otError InitLinkLocalAddress(void) if (IN6_IS_ADDR_LINKLOCAL(&ip6Addr->sin6_addr)) { memcpy(&sInfraIfLinkLocalAddr, &ip6Addr->sin6_addr, sizeof(sInfraIfLinkLocalAddr)); - ExitNow(error = OT_ERROR_NONE); + ExitNow(ret = &sInfraIfLinkLocalAddr); } } - otLogCritPlat("cannot find IPv6 link-local address for interface %s", sInfraIfName); - error = OT_ERROR_NOT_FOUND; + otLogWarnPlat("cannot find IPv6 link-local address for interface %s", sInfraIfName); exit: freeifaddrs(ifAddrs); - return error; + return ret; } -void platformInfraIfInit(otInstance *aInstance, const char *aIfName) +bool platformInfraIfIsRunning(void) { - OT_UNUSED_VARIABLE(aInstance); + int sock; + struct ifreq ifReq; + OT_ASSERT(sInfraIfIndex != 0); + + sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP); + VerifyOrDie(sock != -1, OT_EXIT_ERROR_ERRNO); + + memset(&ifReq, 0, sizeof(ifReq)); + strncpy(ifReq.ifr_name, sInfraIfName, sizeof(ifReq.ifr_name)); + VerifyOrDie(ioctl(sock, SIOCGIFFLAGS, &ifReq) != -1, OT_EXIT_ERROR_ERRNO); + + close(sock); + + return (ifReq.ifr_flags & IFF_RUNNING); +} + +static int CreateIcmp6Socket(void) +{ int sock; + int rval; struct icmp6_filter filter; - ssize_t rval; const int kEnable = 1; const int kIpv6ChecksumOffset = 2; const int kHopLimit = 255; - uint32_t ifIndex = 0; - - if (strlen(aIfName) >= sizeof(sInfraIfName)) - { - otLogCritPlat("infra interface name '%s' is too long", aIfName); - DieNow(OT_EXIT_INVALID_ARGUMENTS); - } - strcpy(sInfraIfName, aIfName); - - // Initializes the infra interface. - ifIndex = if_nametoindex(aIfName); - if (ifIndex == 0) - { - otLogCritPlat("failed to get the index for infra interface %s: %s", aIfName, strerror(errno)); - DieNow(OT_EXIT_ERROR_ERRNO); - } - sInfraIfIndex = ifIndex; // Initializes the ICMPv6 socket. sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); VerifyOrDie(sock != -1, OT_EXIT_ERROR_ERRNO); - // Only accept router advertisements and router solicits. + // Only accept router advertisements and solicitations. ICMP6_FILTER_SETBLOCKALL(&filter); ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); @@ -231,15 +236,43 @@ void platformInfraIfInit(otInstance *aInstance, const char *aIfName) rval = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &kHopLimit, sizeof(kHopLimit)); VerifyOrDie(rval == 0, OT_EXIT_ERROR_ERRNO); + return sock; +} + +uint32_t platformInfraIfInit(otInstance *aInstance, const char *aIfName) +{ + OT_UNUSED_VARIABLE(aInstance); + + ssize_t rval; + uint32_t ifIndex = 0; + + if (strlen(aIfName) >= sizeof(sInfraIfName)) + { + otLogCritPlat("infra interface name '%s' is too long", aIfName); + DieNow(OT_EXIT_INVALID_ARGUMENTS); + } + strcpy(sInfraIfName, aIfName); + + // Initializes the infra interface. + ifIndex = if_nametoindex(aIfName); + if (ifIndex == 0) + { + otLogCritPlat("failed to get the index for infra interface %s", aIfName); + DieNow(OT_EXIT_INVALID_ARGUMENTS); + } + sInfraIfIndex = ifIndex; + + sInfraIfIcmp6Socket = CreateIcmp6Socket(); #ifdef __linux__ - rval = setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, sInfraIfName, strlen(sInfraIfName)); + rval = setsockopt(sInfraIfIcmp6Socket, SOL_SOCKET, SO_BINDTODEVICE, sInfraIfName, strlen(sInfraIfName)); #else // __NetBSD__ || __FreeBSD__ || __APPLE__ - rval = setsockopt(sock, IPPROTO_IP, IP_BOUND_IF, &sInfraIfIndex, sizeof(sInfraIfIndex)); + rval = setsockopt(sInfraIfIcmp6Socket, IPPROTO_IP, IP_BOUND_IF, &sInfraIfIndex, sizeof(sInfraIfIndex)); #endif // __linux__ VerifyOrDie(rval == 0, OT_EXIT_ERROR_ERRNO); - sInfraIfIcmp6Socket = sock; - SuccessOrDie(InitLinkLocalAddress()); + sNetLinkSocket = CreateNetLinkSocket(); + + return sInfraIfIndex; } void platformInfraIfDeinit(void) @@ -250,21 +283,97 @@ void platformInfraIfDeinit(void) sInfraIfIcmp6Socket = -1; } + if (sNetLinkSocket != -1) + { + close(sNetLinkSocket); + sNetLinkSocket = -1; + } + sInfraIfIndex = 0; } void platformInfraIfUpdateFdSet(fd_set &aReadFdSet, int &aMaxFd) { VerifyOrExit(sInfraIfIcmp6Socket != -1); + VerifyOrExit(sNetLinkSocket != -1); FD_SET(sInfraIfIcmp6Socket, &aReadFdSet); aMaxFd = OT_MAX(aMaxFd, sInfraIfIcmp6Socket); + FD_SET(sNetLinkSocket, &aReadFdSet); + aMaxFd = OT_MAX(aMaxFd, sNetLinkSocket); + exit: return; } -void platformInfraIfProcess(otInstance *aInstance, const fd_set &aReadFdSet) +// Create a net-link socket that subscribes to link & addresses events. +static int CreateNetLinkSocket(void) +{ + int sock; + int rval; + struct sockaddr_nl addr; + + sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); + VerifyOrDie(sock != -1, OT_EXIT_ERROR_ERRNO); + + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV6_IFADDR; + addr.nl_pid = getpid(); + + rval = bind(sock, reinterpret_cast(&addr), sizeof(addr)); + VerifyOrDie(rval == 0, OT_EXIT_ERROR_ERRNO); + + return sock; +} + +static void ReceiveNetLinkMessage(otInstance *aInstance) +{ + const size_t kMaxNetLinkBufSize = 8192; + ssize_t len; + union + { + nlmsghdr mHeader; + uint8_t mBuffer[kMaxNetLinkBufSize]; + } msgBuffer; + + len = recv(sNetLinkSocket, msgBuffer.mBuffer, sizeof(msgBuffer.mBuffer), 0); + if (len < 0) + { + otLogCritPlat("failed to receive netlink message: %s", strerror(errno)); + ExitNow(); + } + + for (struct nlmsghdr *header = &msgBuffer.mHeader; NLMSG_OK(header, static_cast(len)); + header = NLMSG_NEXT(header, len)) + { + switch (header->nlmsg_type) + { + case RTM_NEWLINK: + case RTM_DELLINK: + case RTM_NEWADDR: + case RTM_DELADDR: + SuccessOrDie(otPlatInfraIfStateChanged(aInstance, sInfraIfIndex, platformInfraIfIsRunning(), + platformInfraIfGetLinkLocalAddress())); + break; + case NLMSG_ERROR: + { + struct nlmsgerr *errMsg = reinterpret_cast(NLMSG_DATA(header)); + + OT_UNUSED_VARIABLE(errMsg); + otLogWarnPlat("netlink NLMSG_ERROR response: seq=%u, error=%d", header->nlmsg_seq, errMsg->error); + } + default: + break; + } + } + +exit: + return; +} + +static void ReceiveIcmp6Message(otInstance *aInstance) { otError error = OT_ERROR_NONE; uint8_t buffer[1500]; @@ -281,10 +390,6 @@ void platformInfraIfProcess(otInstance *aInstance, const fd_set &aReadFdSet) struct sockaddr_in6 srcAddr; struct in6_addr dstAddr; - // It is not an error when there is no input data on the socket. - VerifyOrExit(sInfraIfIcmp6Socket != -1); - VerifyOrExit(FD_ISSET(sInfraIfIcmp6Socket, &aReadFdSet)); - memset(&srcAddr, 0, sizeof(srcAddr)); memset(&dstAddr, 0, sizeof(dstAddr)); @@ -340,8 +445,23 @@ exit: } } -uint32_t platformInfraIfGetIndex(void) +void platformInfraIfProcess(otInstance *aInstance, const fd_set &aReadFdSet) { - return sInfraIfIndex; + VerifyOrExit(sInfraIfIcmp6Socket != -1); + VerifyOrExit(sNetLinkSocket != -1); + + if (FD_ISSET(sInfraIfIcmp6Socket, &aReadFdSet)) + { + ReceiveIcmp6Message(aInstance); + } + + if (FD_ISSET(sNetLinkSocket, &aReadFdSet)) + { + ReceiveNetLinkMessage(aInstance); + } + +exit: + return; } + #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE diff --git a/src/posix/platform/platform-posix.h b/src/posix/platform/platform-posix.h index 5e4e3dd2e..5ad29a420 100644 --- a/src/posix/platform/platform-posix.h +++ b/src/posix/platform/platform-posix.h @@ -47,6 +47,7 @@ #include #include +#include #include #include @@ -505,8 +506,10 @@ extern unsigned int gBackboneNetifIndex; * @param[in] aInstance The OpenThread instance. * @param[in] aIfName The name of the infrastructure interface. * + * @returns The index of the infrastructure interface. + * */ -void platformInfraIfInit(otInstance *aInstance, const char *aIfName); +uint32_t platformInfraIfInit(otInstance *aInstance, const char *aIfName); /** * This function deinitializes the infrastructure interface. @@ -514,6 +517,22 @@ void platformInfraIfInit(otInstance *aInstance, const char *aIfName); */ void platformInfraIfDeinit(void); +/** + * This function tells if the infrastructure interface is running. + * + * @returns TRUE if the infrastructure interface is running, FALSE if not. + * + */ +bool platformInfraIfIsRunning(void); + +/** + * this function returns the IPv6 link-local address of the infrastructure interface. + * + * @returns A pointer to the link-local address; NULL if no link-local address is present. + * + */ +const otIp6Address *platformInfraIfGetLinkLocalAddress(void); + /** * This function updates the read fd set. * @@ -532,14 +551,6 @@ void platformInfraIfUpdateFdSet(fd_set &aReadFdSet, int &aMaxFd); */ void platformInfraIfProcess(otInstance *aInstance, const fd_set &aReadFdSet); -/** - * This function returns the index of the infrastructure interface. - * - * @returns The index of the infrastructure interface. 0 indicates invalid index. - * - */ -uint32_t platformInfraIfGetIndex(void); - #ifdef __cplusplus } #endif diff --git a/src/posix/platform/system.cpp b/src/posix/platform/system.cpp index 9ffd6b77c..74e3ea1da 100644 --- a/src/posix/platform/system.cpp +++ b/src/posix/platform/system.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -100,8 +101,21 @@ otInstance *otSysInit(otPlatformConfig *aPlatformConfig) #endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE - // Reuse the backbone interface name. - platformInfraIfInit(instance, aPlatformConfig->mBackboneInterfaceName); + { + uint32_t infraIfIndex; + + // Reuse the backbone interface name. + if (aPlatformConfig->mBackboneInterfaceName == nullptr || strlen(aPlatformConfig->mBackboneInterfaceName) == 0) + { + DieNowWithMessage("no infra interface is specified", OT_EXIT_INVALID_ARGUMENTS); + } + + infraIfIndex = platformInfraIfInit(instance, aPlatformConfig->mBackboneInterfaceName); + + SuccessOrDie(otBorderRoutingInit(instance, infraIfIndex, platformInfraIfIsRunning(), + platformInfraIfGetLinkLocalAddress())); + SuccessOrDie(otBorderRoutingSetEnabled(instance, /* aEnabled */ true)); + } #endif #if OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE @@ -114,10 +128,6 @@ otInstance *otSysInit(otPlatformConfig *aPlatformConfig) SuccessOrDie(otSetStateChangedCallback(instance, processStateChange, instance)); #endif -#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE - SuccessOrDie(otBorderRoutingInit(instance, platformInfraIfGetIndex())); -#endif - return instance; } diff --git a/tests/scripts/thread-cert/border_router/test_single_border_router.py b/tests/scripts/thread-cert/border_router/test_single_border_router.py index ea7dda767..3fabc206a 100755 --- a/tests/scripts/thread-cert/border_router/test_single_border_router.py +++ b/tests/scripts/thread-cert/border_router/test_single_border_router.py @@ -255,6 +255,60 @@ class SingleBorderRouter(thread_cert.TestCase): self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0], backbone=True)) + # + # Case 4. The Routing Manager should be stopped if the infra interface went down. + # + + self.nodes[BR1].disable_ether() + + self.simulator.go(10) + self.collect_ipaddrs() + + logging.info("BR1 addrs: %r", self.nodes[BR1].get_addrs()) + logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs()) + logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs()) + + self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 0) + self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 0) + self.assertTrue(len(self.nodes[BR1].get_routes()) == 0) + self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 0) + self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 0) + self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 0) + + self.nodes[BR1].enable_ether() + + # It takes around 10 seconds to start sending RA messages. + self.simulator.go(15) + self.collect_ipaddrs() + + logging.info("BR1 addrs: %r", self.nodes[BR1].get_addrs()) + logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs()) + logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs()) + + self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[BR1].get_routes()) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1) + + # The same local OMR and on-link prefix should be re-registered. + self.assertEqual(omr_prefix, self.nodes[BR1].get_prefixes()[0]) + self.assertEqual(omr_prefix, self.nodes[ROUTER1].get_prefixes()[0]) + self.assertEqual(external_route, self.nodes[BR1].get_routes()[0]) + self.assertEqual(external_route, self.nodes[ROUTER1].get_routes()[0]) + + self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1) + + self.assertEqual(br1_omr_address, self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]) + self.assertEqual(router1_omr_address, self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]) + self.assertEqual(host_ula_address, self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]) + + # Router1 can ping to/from the Host on infra link. + self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) + self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0], + backbone=True)) + if __name__ == '__main__': unittest.main() diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 32449b1e7..91447b13f 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -2592,6 +2592,18 @@ class LinuxHost(): PING_RESPONSE_PATTERN = re.compile(r'\d+ bytes from .*:.*') ETH_DEV = config.BACKBONE_IFNAME + def enable_ether(self): + """Enable the ethernet interface. + """ + + self.bash(f'ifconfig {self.ETH_DEV} up') + + def disable_ether(self): + """Disable the ethernet interface. + """ + + self.bash(f'ifconfig {self.ETH_DEV} down') + def get_ether_addrs(self): output = self.bash(f'ip -6 addr list dev {self.ETH_DEV}')