diff --git a/Android.mk b/Android.mk index 2d2ef9fbd..3c71d731e 100644 --- a/Android.mk +++ b/Android.mk @@ -300,6 +300,7 @@ LOCAL_SRC_FILES := \ src/posix/platform/hdlc_interface.cpp \ src/posix/platform/logging.cpp \ src/posix/platform/misc.cpp \ + src/posix/platform/multicast_routing.cpp \ src/posix/platform/netif.cpp \ src/posix/platform/radio.cpp \ src/posix/platform/radio_url.cpp \ diff --git a/script/test b/script/test index 80b328cd0..fb41d293a 100755 --- a/script/test +++ b/script/test @@ -230,7 +230,7 @@ do_build_otbr_docker() local otdir local otbrdir 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_commit=${OTBR_COMMIT:-5092e71b06b23f78f2288fcc5386bce3a8986bb1} # [backbone-router] remove MLR multicast routing using smcroute #604 local otbr_docker_image=${OTBR_DOCKER_IMAGE:-otbr-ot12-backbone-ci} otbrdir=$(mktemp -d -t otbr_XXXXXX) diff --git a/src/core/common/logging.hpp b/src/core/common/logging.hpp index d13c3bef5..fdf9c613e 100644 --- a/src/core/common/logging.hpp +++ b/src/core/common/logging.hpp @@ -2218,6 +2218,35 @@ const char *otLogLevelToPrefixString(otLogLevel aLogLevel); #endif // OPENTHREAD_CONFIG_LOG_DEFINE_AS_MACRO_ONLY +#define _otLogResult(aRegion, aError, ...) \ + do \ + { \ + otError _err = (aError); \ + \ + if (_err == OT_ERROR_NONE) \ + { \ + otLogInfo##aRegion(OT_FIRST_ARG(__VA_ARGS__) ": %s" OT_REST_ARGS(__VA_ARGS__), \ + otThreadErrorToString(_err)); \ + } \ + else \ + { \ + otLogWarn##aRegion(OT_FIRST_ARG(__VA_ARGS__) ": %s" OT_REST_ARGS(__VA_ARGS__), \ + otThreadErrorToString(_err)); \ + } \ + } while (false) + +/** + * @def otLogResultPlat + * + * This function generates a log for the Plat region according to the error result. If @p aError is `OT_ERROR_NONE`, the + * log level is info. Otherwise the log level is warn. + * + * @param[in] aError The error result. + * @param[in] ... Arguments for the format specification. + * + */ +#define otLogResultPlat(aError, ...) _otLogResult(Plat, aError, OT_FIRST_ARG(__VA_ARGS__) OT_REST_ARGS(__VA_ARGS__)) + #ifdef __cplusplus } #endif diff --git a/src/core/net/ip6_address.hpp b/src/core/net/ip6_address.hpp index 831bb7c58..00c82a096 100644 --- a/src/core/net/ip6_address.hpp +++ b/src/core/net/ip6_address.hpp @@ -533,6 +533,15 @@ public: */ const uint8_t *GetBytes(void) const { return mFields.m8; } + /** + * This method sets the IPv6 address from a given byte array. + * + * @param[in] aBuffer Pointer to an array containing the IPv6 address. `kSize` bytes from the buffer + * are copied to form the IPv6 address. + * + */ + void SetBytes(const uint8_t *aBuffer) { memcpy(mFields.m8, aBuffer, kSize); } + /** * This method indicates whether or not the IPv6 address is the Unspecified Address. * @@ -885,6 +894,20 @@ public: */ InfoString ToString(void) const; + /** + * This method overloads operator `<` to compare two IPv6 addresses. + * + * @param[in] aOther The other IPv6 address to compare with. + * + * @retval true The IPv6 address is smaller than @p aOther. + * @retval false The IPv6 address is larger than or equal to @p aOther. + * + */ + bool operator<(const Ip6::Address &aOther) const + { + return memcmp(mFields.m8, aOther.mFields.m8, sizeof(Ip6::Address)) < 0; + } + private: void SetPrefix(uint8_t aOffset, const uint8_t *aPrefix, uint8_t aPrefixLength); void SetToLocator(const NetworkPrefix &aNetworkPrefix, uint16_t aLocator); diff --git a/src/posix/main.c b/src/posix/main.c index 8f1833396..625d982f8 100644 --- a/src/posix/main.c +++ b/src/posix/main.c @@ -101,15 +101,13 @@ void __gcov_flush(); */ enum { -#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE OT_POSIX_OPT_BACKBONE_INTERFACE_NAME = 'B', -#endif - OT_POSIX_OPT_DEBUG_LEVEL = 'd', - OT_POSIX_OPT_DRY_RUN = 'n', - OT_POSIX_OPT_HELP = 'h', - OT_POSIX_OPT_INTERFACE_NAME = 'I', - OT_POSIX_OPT_TIME_SPEED = 's', - OT_POSIX_OPT_VERBOSE = 'v', + OT_POSIX_OPT_DEBUG_LEVEL = 'd', + OT_POSIX_OPT_DRY_RUN = 'n', + OT_POSIX_OPT_HELP = 'h', + OT_POSIX_OPT_INTERFACE_NAME = 'I', + OT_POSIX_OPT_TIME_SPEED = 's', + OT_POSIX_OPT_VERBOSE = 'v', OT_POSIX_OPT_SHORT_MAX = 128, @@ -118,9 +116,7 @@ enum }; static const struct option kOptions[] = { -#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE {"backbone-interface-name", required_argument, NULL, OT_POSIX_OPT_BACKBONE_INTERFACE_NAME}, -#endif {"debug-level", required_argument, NULL, OT_POSIX_OPT_DEBUG_LEVEL}, {"dry-run", no_argument, NULL, OT_POSIX_OPT_DRY_RUN}, {"help", no_argument, NULL, OT_POSIX_OPT_HELP}, @@ -137,9 +133,7 @@ static void PrintUsage(const char *aProgramName, FILE *aStream, int aExitCode) "Syntax:\n" " %s [Options] RadioURL\n" "Options:\n" -#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE " -B --backbone-interface-name Backbone network interface name.\n" -#endif " -d --debug-level Debug level of logging.\n" " -h --help Display this usage information.\n" " -I --interface-name name Thread network interface name.\n" @@ -173,12 +167,7 @@ static void ParseArg(int aArgCount, char *aArgVector[], PosixConfig *aConfig) while (true) { int index = 0; - int option = getopt_long(aArgCount, aArgVector, -#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE - "B:" -#endif - "d:hI:ns:v", - kOptions, &index); + int option = getopt_long(aArgCount, aArgVector, "B:d:hI:ns:v", kOptions, &index); if (option == -1) { @@ -196,11 +185,9 @@ static void ParseArg(int aArgCount, char *aArgVector[], PosixConfig *aConfig) case OT_POSIX_OPT_INTERFACE_NAME: aConfig->mPlatformConfig.mInterfaceName = optarg; break; -#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE case OT_POSIX_OPT_BACKBONE_INTERFACE_NAME: aConfig->mPlatformConfig.mBackboneInterfaceName = optarg; break; -#endif case OT_POSIX_OPT_DRY_RUN: aConfig->mIsDryRun = true; break; diff --git a/src/posix/platform/CMakeLists.txt b/src/posix/platform/CMakeLists.txt index 8496d6ba7..0e779c7d1 100644 --- a/src/posix/platform/CMakeLists.txt +++ b/src/posix/platform/CMakeLists.txt @@ -66,6 +66,7 @@ add_library(openthread-posix hdlc_interface.cpp logging.cpp misc.cpp + multicast_routing.cpp netif.cpp radio.cpp radio_url.cpp diff --git a/src/posix/platform/Makefile.am b/src/posix/platform/Makefile.am index 8401b46c6..04cfb3ae2 100644 --- a/src/posix/platform/Makefile.am +++ b/src/posix/platform/Makefile.am @@ -50,6 +50,7 @@ libopenthread_posix_a_SOURCES = \ hdlc_interface.cpp \ logging.cpp \ misc.cpp \ + multicast_routing.cpp \ netif.cpp \ radio.cpp \ radio_url.cpp \ @@ -68,6 +69,7 @@ libopenthread_posix_a_LIBADD noinst_HEADERS = \ hdlc_interface.hpp \ + multicast_routing.hpp \ openthread-posix-config.h \ platform-posix.h \ radio_url.hpp \ diff --git a/src/posix/platform/backbone.cpp b/src/posix/platform/backbone.cpp index 7c62078f6..626ea355a 100644 --- a/src/posix/platform/backbone.cpp +++ b/src/posix/platform/backbone.cpp @@ -35,17 +35,19 @@ #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE +#include "multicast_routing.hpp" #include "platform-posix.h" #include "common/code_utils.hpp" -char gBackboneNetifName[IFNAMSIZ] = ""; -unsigned int gBackboneNetifIndex = 0; +char gBackboneNetifName[IFNAMSIZ] = ""; +unsigned int gBackboneNetifIndex = 0; +static ot::Posix::MulticastRoutingManager sMulticastRoutingManager; void platformBackboneInit(otInstance *aInstance, const char *aInterfaceName) { OT_UNUSED_VARIABLE(aInstance); - VerifyOrExit(aInterfaceName != nullptr, otLogInfoPlat("Backbone interface is not configured")); + VerifyOrDie(aInterfaceName != nullptr, OT_EXIT_INVALID_ARGUMENTS); VerifyOrDie(strnlen(aInterfaceName, IFNAMSIZ) <= IFNAMSIZ - 1, OT_EXIT_INVALID_ARGUMENTS); strcpy(gBackboneNetifName, aInterfaceName); @@ -55,8 +57,22 @@ void platformBackboneInit(otInstance *aInstance, const char *aInterfaceName) otLogInfoPlat("Backbone interface is configured to %s (%d)", gBackboneNetifName, gBackboneNetifIndex); -exit: - return; + sMulticastRoutingManager.Init(aInstance); +} + +void platformBackboneUpdateFdSet(fd_set &aReadFdSet, int &aMaxFd) +{ + sMulticastRoutingManager.UpdateFdSet(aReadFdSet, aMaxFd); +} + +void platformBackboneProcess(const fd_set &aReadSet) +{ + sMulticastRoutingManager.Process(aReadSet); +} + +void platformBackboneStateChange(otInstance *aInstance, otChangedFlags aFlags) +{ + sMulticastRoutingManager.HandleStateChange(aInstance, aFlags); } #endif diff --git a/src/posix/platform/multicast_routing.cpp b/src/posix/platform/multicast_routing.cpp new file mode 100644 index 000000000..349c0dfb6 --- /dev/null +++ b/src/posix/platform/multicast_routing.cpp @@ -0,0 +1,569 @@ +/* + * 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. + */ + +#include "posix/platform/multicast_routing.hpp" + +#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + +#include +#include +#include +#include +#include +#include +#include +#include +#if __linux__ +#include +#else +#error "Multicast Routing feature is not ported to non-Linux platforms yet." +#endif + +#include + +#include "core/common/logging.hpp" + +namespace ot { +namespace Posix { + +void MulticastRoutingManager::Init(otInstance *aInstance) +{ + mInstance = aInstance; + + otBackboneRouterSetMulticastListenerCallback(aInstance, + &MulticastRoutingManager::HandleBackboneMulticastListenerEvent, this); +} + +void MulticastRoutingManager::HandleBackboneMulticastListenerEvent(void * aContext, + otBackboneRouterMulticastListenerEvent aEvent, + const otIp6Address * aAddress) +{ + static_cast(aContext)->HandleBackboneMulticastListenerEvent( + aEvent, static_cast(*aAddress)); +} + +void MulticastRoutingManager::HandleBackboneMulticastListenerEvent(otBackboneRouterMulticastListenerEvent aEvent, + const Ip6::Address & aAddress) +{ + switch (aEvent) + { + case OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ADDED: + Add(aAddress); + break; + case OT_BACKBONE_ROUTER_MULTICAST_LISTENER_REMOVED: + Remove(aAddress); + break; + } +} + +void MulticastRoutingManager::Enable(void) +{ + VerifyOrExit(!IsEnabled()); + + InitMulticastRouterSock(); + + otLogResultPlat(OT_ERROR_NONE, "MulticastRoutingManager: %s", __FUNCTION__); +exit: + return; +} + +void MulticastRoutingManager::Disable(void) +{ + FinalizeMulticastRouterSock(); + + otLogResultPlat(OT_ERROR_NONE, "MulticastRoutingManager: %s", __FUNCTION__); +} + +void MulticastRoutingManager::Add(const Ip6::Address &aAddress) +{ + VerifyOrExit(IsEnabled()); + + UnblockInboundMulticastForwardingCache(aAddress); + otLogResultPlat(OT_ERROR_NONE, "MulticastRoutingManager: %s: %s", __FUNCTION__, aAddress.ToString().AsCString()); + +exit: + return; +} + +void MulticastRoutingManager::Remove(const Ip6::Address &aAddress) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(IsEnabled()); + + RemoveInboundMulticastForwardingCache(aAddress); + otLogResultPlat(error, "MulticastRoutingManager: %s: %s", __FUNCTION__, aAddress.ToString().AsCString()); + +exit: + return; +} + +bool MulticastRoutingManager::HasMulticastListener(const Ip6::Address &aAddress) const +{ + bool found = false; + otBackboneRouterMulticastListenerIterator iter = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ITERATOR_INIT; + otBackboneRouterMulticastListenerInfo listenerInfo; + + while (otBackboneRouterMulticastListenerGetNext(mInstance, &iter, &listenerInfo) == OT_ERROR_NONE) + { + VerifyOrExit(static_cast(listenerInfo.mAddress) != aAddress, found = true); + } + +exit: + return found; +} + +void MulticastRoutingManager::UpdateFdSet(fd_set &aReadFdSet, int &aMaxFd) const +{ + VerifyOrExit(IsEnabled()); + + FD_SET(mMulticastRouterSock, &aReadFdSet); + aMaxFd = OT_MAX(aMaxFd, mMulticastRouterSock); + +exit: + return; +} + +void MulticastRoutingManager::Process(const fd_set &aReadFdSet) +{ + VerifyOrExit(IsEnabled()); + + ExpireMulticastForwardingCache(); + + if (FD_ISSET(mMulticastRouterSock, &aReadFdSet)) + { + ProcessMulticastRouterMessages(); + } + +exit: + return; +} + +void MulticastRoutingManager::InitMulticastRouterSock(void) +{ + int one = 1; + struct icmp6_filter filter; + struct mif6ctl mif6ctl; + + // Create a Multicast Routing socket + mMulticastRouterSock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + VerifyOrDie(mMulticastRouterSock != -1, OT_EXIT_ERROR_ERRNO); + + // Enable Multicast Forwarding in Kernel + VerifyOrDie(0 == setsockopt(mMulticastRouterSock, IPPROTO_IPV6, MRT6_INIT, &one, sizeof(one)), OT_EXIT_ERROR_ERRNO); + + // Filter all ICMPv6 messages + ICMP6_FILTER_SETBLOCKALL(&filter); + VerifyOrDie(0 == setsockopt(mMulticastRouterSock, IPPROTO_ICMPV6, ICMP6_FILTER, (void *)&filter, sizeof(filter)), + OT_EXIT_ERROR_ERRNO); + + memset(&mif6ctl, 0, sizeof(mif6ctl)); + mif6ctl.mif6c_flags = 0; + mif6ctl.vifc_threshold = 1; + mif6ctl.vifc_rate_limit = 0; + + // Add Thread network interface to MIF + mif6ctl.mif6c_mifi = kMifIndexThread; + mif6ctl.mif6c_pifi = if_nametoindex(gNetifName); + VerifyOrDie(mif6ctl.mif6c_pifi > 0, OT_EXIT_ERROR_ERRNO); + VerifyOrDie(0 == setsockopt(mMulticastRouterSock, IPPROTO_IPV6, MRT6_ADD_MIF, &mif6ctl, sizeof(mif6ctl)), + OT_EXIT_ERROR_ERRNO); + + // Add Backbone network interface to MIF + mif6ctl.mif6c_mifi = kMifIndexBackbone; + mif6ctl.mif6c_pifi = if_nametoindex(gBackboneNetifName); + VerifyOrDie(mif6ctl.mif6c_pifi > 0, OT_EXIT_ERROR_ERRNO); + VerifyOrDie(0 == setsockopt(mMulticastRouterSock, IPPROTO_IPV6, MRT6_ADD_MIF, &mif6ctl, sizeof(mif6ctl)), + OT_EXIT_ERROR_ERRNO); +} + +void MulticastRoutingManager::FinalizeMulticastRouterSock(void) +{ + VerifyOrExit(IsEnabled()); + + close(mMulticastRouterSock); + mMulticastRouterSock = -1; + +exit: + return; +} + +void MulticastRoutingManager::ProcessMulticastRouterMessages(void) +{ + otError error = OT_ERROR_NONE; + char buf[sizeof(struct mrt6msg)]; + int nr; + struct mrt6msg *mrt6msg; + Ip6::Address src, dst; + + nr = read(mMulticastRouterSock, buf, sizeof(buf)); + + VerifyOrExit(nr >= static_cast(sizeof(struct mrt6msg)), error = OT_ERROR_FAILED); + + mrt6msg = reinterpret_cast(buf); + + VerifyOrExit(mrt6msg->im6_mbz == 0); + VerifyOrExit(mrt6msg->im6_msgtype == MRT6MSG_NOCACHE); + + src.SetBytes(mrt6msg->im6_src.s6_addr); + dst.SetBytes(mrt6msg->im6_dst.s6_addr); + + error = AddMulticastForwardingCache(src, dst, static_cast(mrt6msg->im6_mif)); + +exit: + otLogResultPlat(error, "MulticastRoutingManager: %s", __FUNCTION__); +} + +otError MulticastRoutingManager::AddMulticastForwardingCache(const Ip6::Address &aSrcAddr, + const Ip6::Address &aGroupAddr, + MifIndex aIif) +{ + otError error = OT_ERROR_NONE; + struct mf6cctl mf6cctl; + MifIndex forwardMif = kMifIndexNone; + + VerifyOrExit(aIif == kMifIndexThread || aIif == kMifIndexBackbone, error = OT_ERROR_INVALID_ARGS); + + ExpireMulticastForwardingCache(); + + if (aIif == kMifIndexBackbone) + { + // Forward multicast traffic from Backbone to Thread if the group address is subscribed by any Thread device via + // MLR. + if (HasMulticastListener(aGroupAddr)) + { + forwardMif = kMifIndexThread; + } + } + else + { + // Forward multicast traffic from Thread to Backbone if multicast scope > kRealmLocalScope + // TODO: (MLR) allow scope configuration of outbound multicast routing + if (aGroupAddr.GetScope() > Ip6::Address::kRealmLocalScope) + { + forwardMif = kMifIndexBackbone; + } + } + + memset(&mf6cctl, 0, sizeof(mf6cctl)); + + memcpy(mf6cctl.mf6cc_origin.sin6_addr.s6_addr, aSrcAddr.GetBytes(), sizeof(mf6cctl.mf6cc_origin.sin6_addr.s6_addr)); + memcpy(mf6cctl.mf6cc_mcastgrp.sin6_addr.s6_addr, aGroupAddr.GetBytes(), + sizeof(mf6cctl.mf6cc_mcastgrp.sin6_addr.s6_addr)); + mf6cctl.mf6cc_parent = aIif; + + if (forwardMif != kMifIndexNone) + { + IF_SET(forwardMif, &mf6cctl.mf6cc_ifset); + } + + // Note that kernel reports repetitive `MRT6MSG_NOCACHE` upcalls with a rate limit (e.g. once per 10s for Linux). + // Because of it, we need to add a "blocking" MFC even if there is no forwarding for this group address. + // When a Multicast Listener is later added, the "blocking" MFC will be altered to be a "forwarding" MFC so that + // corresponding multicast traffic can be forwarded instantly. + VerifyOrExit(0 == setsockopt(mMulticastRouterSock, IPPROTO_IPV6, MRT6_ADD_MFC, &mf6cctl, sizeof(mf6cctl)), + error = OT_ERROR_FAILED); + + SaveMulticastForwardingCache(aSrcAddr, aGroupAddr, aIif, forwardMif); +exit: + otLogResultPlat(error, "MulticastRoutingManager: %s: add dynamic route: %s %s => %s %s", __FUNCTION__, + MifIndexToString(aIif), aSrcAddr.ToString().AsCString(), aGroupAddr.ToString().AsCString(), + MifIndexToString(forwardMif)); + + return error; +} + +void MulticastRoutingManager::UnblockInboundMulticastForwardingCache(const Ip6::Address &aGroupAddr) +{ + struct mf6cctl mf6cctl; + + memset(&mf6cctl, 0, sizeof(mf6cctl)); + memcpy(mf6cctl.mf6cc_mcastgrp.sin6_addr.s6_addr, aGroupAddr.GetBytes(), + sizeof(mf6cctl.mf6cc_mcastgrp.sin6_addr.s6_addr)); + mf6cctl.mf6cc_parent = kMifIndexBackbone; + IF_SET(kMifIndexThread, &mf6cctl.mf6cc_ifset); + + for (MulticastForwardingCache &mfc : mMulticastForwardingCacheTable) + { + otError error; + + if (!mfc.IsValid() || mfc.mIif != kMifIndexBackbone || mfc.mOif == kMifIndexThread || + mfc.mGroupAddr != aGroupAddr) + { + continue; + } + + // Unblock this inbound route + memcpy(mf6cctl.mf6cc_origin.sin6_addr.s6_addr, mfc.mSrcAddr.GetBytes(), + sizeof(mf6cctl.mf6cc_origin.sin6_addr.s6_addr)); + + error = (0 == setsockopt(mMulticastRouterSock, IPPROTO_IPV6, MRT6_ADD_MFC, &mf6cctl, sizeof(mf6cctl))) + ? OT_ERROR_NONE + : OT_ERROR_FAILED; + + mfc.Set(kMifIndexBackbone, kMifIndexThread); + + otLogResultPlat(error, "MulticastRoutingManager: %s: %s %s => %s %s", __FUNCTION__, MifIndexToString(mfc.mIif), + mfc.mSrcAddr.ToString().AsCString(), mfc.mGroupAddr.ToString().AsCString(), + MifIndexToString(kMifIndexThread)); + } +} + +void MulticastRoutingManager::RemoveInboundMulticastForwardingCache(const Ip6::Address &aGroupAddr) +{ + for (MulticastForwardingCache &mfc : mMulticastForwardingCacheTable) + { + if (mfc.IsValid() && mfc.mIif == kMifIndexBackbone && mfc.mGroupAddr == aGroupAddr) + { + RemoveMulticastForwardingCache(mfc); + } + } +} + +void MulticastRoutingManager::ExpireMulticastForwardingCache(void) +{ + struct sioc_sg_req6 sioc_sg_req6; + uint64_t now = otPlatTimeGet(); + struct mf6cctl mf6cctl; + + VerifyOrExit(now >= mLastExpireTime + kMulticastForwardingCacheExpiringInterval * US_PER_S); + + mLastExpireTime = now; + + memset(&mf6cctl, 0, sizeof(mf6cctl)); + memset(&sioc_sg_req6, 0, sizeof(sioc_sg_req6)); + + for (MulticastForwardingCache &mfc : mMulticastForwardingCacheTable) + { + if (mfc.IsValid() && mfc.mLastUseTime + kMulticastForwardingCacheExpireTimeout * US_PER_S < now) + { + if (!UpdateMulticastRouteInfo(mfc)) + { + // The multicast route is expired + RemoveMulticastForwardingCache(mfc); + } + } + } + + DumpMulticastForwardingCache(); + +exit: + return; +} + +bool MulticastRoutingManager::UpdateMulticastRouteInfo(MulticastForwardingCache &aMfc) const +{ + bool updated = false; + struct sioc_sg_req6 sioc_sg_req6; + + memset(&sioc_sg_req6, 0, sizeof(sioc_sg_req6)); + + memcpy(sioc_sg_req6.src.sin6_addr.s6_addr, aMfc.mSrcAddr.GetBytes(), sizeof(sioc_sg_req6.src.sin6_addr.s6_addr)); + memcpy(sioc_sg_req6.grp.sin6_addr.s6_addr, aMfc.mGroupAddr.GetBytes(), sizeof(sioc_sg_req6.grp.sin6_addr.s6_addr)); + + if (ioctl(mMulticastRouterSock, SIOCGETSGCNT_IN6, &sioc_sg_req6) != -1) + { + unsigned long validPktCnt; + + otLogDebgPlat("MulticastRoutingManager: %s: SIOCGETSGCNT_IN6 %s => %s: bytecnt=%lu, pktcnt=%lu, wrong_if=%lu", + __FUNCTION__, aMfc.mSrcAddr.ToString().AsCString(), aMfc.mGroupAddr.ToString().AsCString(), + sioc_sg_req6.bytecnt, sioc_sg_req6.pktcnt, sioc_sg_req6.wrong_if); + + validPktCnt = sioc_sg_req6.pktcnt - sioc_sg_req6.wrong_if; + if (validPktCnt != aMfc.mValidPktCnt) + { + aMfc.SetValidPktCnt(validPktCnt); + + updated = true; + } + } + else + { + otLogWarnPlat("MulticastRoutingManager: %s: SIOCGETSGCNT_IN6 %s => %s failed: %s", __FUNCTION__, + aMfc.mSrcAddr.ToString().AsCString(), aMfc.mGroupAddr.ToString().AsCString(), strerror(errno)); + } + + return updated; +} + +const char *MulticastRoutingManager::MifIndexToString(MifIndex aMif) +{ + const char *string = "Unknown"; + + switch (aMif) + { + case kMifIndexNone: + string = "None"; + break; + case kMifIndexThread: + string = "Thread"; + break; + case kMifIndexBackbone: + string = "Backbone"; + break; + } + + return string; +} + +void MulticastRoutingManager::DumpMulticastForwardingCache(void) const +{ +#if OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_DEBG + otLogDebgPlat("MulticastRoutingManager: ==================== MFC ENTRIES ===================="); + + for (const MulticastForwardingCache &mfc : mMulticastForwardingCacheTable) + { + if (mfc.IsValid()) + { + otLogDebgPlat("MulticastRoutingManager: %s %s => %s %s", MifIndexToString(mfc.mIif), + mfc.mSrcAddr.ToString().AsCString(), mfc.mGroupAddr.ToString().AsCString(), + MifIndexToString(mfc.mOif)); + } + } + + otLogDebgPlat("MulticastRoutingManager: ====================================================="); +#endif +} + +void MulticastRoutingManager::HandleStateChange(otInstance *aInstance, otChangedFlags aFlags) +{ + if (aFlags & OT_CHANGED_THREAD_BACKBONE_ROUTER_STATE) + { + otBackboneRouterState state = otBackboneRouterGetState(aInstance); + + switch (state) + { + case OT_BACKBONE_ROUTER_STATE_DISABLED: + case OT_BACKBONE_ROUTER_STATE_SECONDARY: + Disable(); + break; + case OT_BACKBONE_ROUTER_STATE_PRIMARY: + Enable(); + break; + } + } +} + +void MulticastRoutingManager::MulticastForwardingCache::Set(MulticastRoutingManager::MifIndex aIif, + MulticastRoutingManager::MifIndex aOif) +{ + mIif = aIif; + mOif = aOif; + mValidPktCnt = 0; + mLastUseTime = otPlatTimeGet(); +} + +void MulticastRoutingManager::MulticastForwardingCache::Set(const Ip6::Address &aSrcAddr, + const Ip6::Address &aGroupAddr, + MifIndex aIif, + MifIndex aOif) +{ + mSrcAddr = aSrcAddr; + mGroupAddr = aGroupAddr; + Set(aIif, aOif); +} + +void MulticastRoutingManager::MulticastForwardingCache::SetValidPktCnt(unsigned long aValidPktCnt) +{ + mValidPktCnt = aValidPktCnt; + mLastUseTime = otPlatTimeGet(); +} + +void MulticastRoutingManager::SaveMulticastForwardingCache(const Ip6::Address & aSrcAddr, + const Ip6::Address & aGroupAddr, + MulticastRoutingManager::MifIndex aIif, + MulticastRoutingManager::MifIndex aOif) +{ + MulticastForwardingCache *invalid = nullptr; + MulticastForwardingCache *oldest = nullptr; + + for (MulticastForwardingCache &mfc : mMulticastForwardingCacheTable) + { + if (mfc.IsValid()) + { + if (mfc.mSrcAddr == aSrcAddr && mfc.mGroupAddr == aGroupAddr) + { + mfc.Set(aIif, aOif); + ExitNow(); + } + + if (oldest == nullptr || mfc.mLastUseTime < oldest->mLastUseTime) + { + oldest = &mfc; + } + } + else if (invalid == nullptr) + { + invalid = &mfc; + } + } + + if (invalid != nullptr) + { + invalid->Set(aSrcAddr, aGroupAddr, aIif, aOif); + } + else + { + RemoveMulticastForwardingCache(*oldest); + oldest->Set(aSrcAddr, aGroupAddr, aIif, aOif); + } + +exit: + return; +} + +void MulticastRoutingManager::RemoveMulticastForwardingCache( + MulticastRoutingManager::MulticastForwardingCache &aMfc) const +{ + otError error; + struct mf6cctl mf6cctl; + + memset(&mf6cctl, 0, sizeof(mf6cctl)); + + memcpy(mf6cctl.mf6cc_origin.sin6_addr.s6_addr, aMfc.mSrcAddr.GetBytes(), + sizeof(mf6cctl.mf6cc_origin.sin6_addr.s6_addr)); + memcpy(mf6cctl.mf6cc_mcastgrp.sin6_addr.s6_addr, aMfc.mGroupAddr.GetBytes(), + sizeof(mf6cctl.mf6cc_mcastgrp.sin6_addr.s6_addr)); + + mf6cctl.mf6cc_parent = aMfc.mIif; + + error = (0 == setsockopt(mMulticastRouterSock, IPPROTO_IPV6, MRT6_DEL_MFC, &mf6cctl, sizeof(mf6cctl))) + ? OT_ERROR_NONE + : OT_ERROR_FAILED; + + otLogResultPlat(error, "MulticastRoutingManager: %s: %s %s => %s %s", __FUNCTION__, MifIndexToString(aMfc.mIif), + aMfc.mSrcAddr.ToString().AsCString(), aMfc.mGroupAddr.ToString().AsCString(), + MifIndexToString(aMfc.mOif)); + + aMfc.Erase(); +} + +} // namespace Posix +} // namespace ot + +#endif // OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE diff --git a/src/posix/platform/multicast_routing.hpp b/src/posix/platform/multicast_routing.hpp new file mode 100644 index 000000000..e5f38f6ec --- /dev/null +++ b/src/posix/platform/multicast_routing.hpp @@ -0,0 +1,180 @@ +/* + * 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. + */ + +#ifndef OT_POSIX_PLATFORM_MULTICAST_ROUTING_HPP_ +#define OT_POSIX_PLATFORM_MULTICAST_ROUTING_HPP_ + +#include "openthread-posix-config.h" + +#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + +#include +#include + +#include +#include + +#include "platform-posix.h" +#include "core/common/non_copyable.hpp" +#include "core/net/ip6_address.hpp" +#include "lib/url/url.hpp" + +namespace ot { +namespace Posix { + +/** + * This class implements Multicast Routing management. + * + */ +class MulticastRoutingManager : private NonCopyable +{ +public: + /** + * This constructor initializes a Multicast Routing manager instance. + * + */ + explicit MulticastRoutingManager() + : mLastExpireTime(0) + , mMulticastRouterSock(-1) + , mInstance(nullptr) + { + } + + /** + * This method initializes the Multicast Routing manager. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + */ + void Init(otInstance *aInstance); + + /** + * This method updates the fd_set and timeout for mainloop. + * + * @param[inout] aReadFdSet A reference to fd_set for polling read. + * @param[inout] aMaxFd A reference to the current max fd in fd_sets. + * + */ + void UpdateFdSet(fd_set &aReadFdSet, int &aMaxFd) const; + + /** + * This method performs Multicast Routing processing. + * + * @param[in] aReadFdSet A reference to read file descriptors. + * + */ + void Process(const fd_set &aReadFdSet); + + /** + * This method handles Thread state changes. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aFlags Flags that denote the state change events. + * + */ + void HandleStateChange(otInstance *aInstance, otChangedFlags aFlags); + +private: + enum + { + kMulticastForwardingCacheExpireTimeout = 300, //< Expire timeout of Multicast Forwarding Cache (in seconds) + kMulticastForwardingCacheExpiringInterval = 60, //< Expire interval of Multicast Forwarding Cache (in seconds) + kMulitcastForwardingCacheTableSize = + OPENTHREAD_POSIX_CONFIG_MAX_MULTICAST_FORWARDING_CACHE_TABLE, //< The max size of MFC table. + }; + + enum MifIndex : uint8_t + { + kMifIndexNone = 0xff, + kMifIndexThread = 0, + kMifIndexBackbone = 1, + }; + + class MulticastForwardingCache + { + friend class MulticastRoutingManager; + + private: + MulticastForwardingCache() + : mIif(kMifIndexNone) + { + } + + bool IsValid() const { return mIif != kMifIndexNone; } + void Set(MifIndex aIif, MifIndex aOif); + void Set(const Ip6::Address &aSrcAddr, const Ip6::Address &aGroupAddr, MifIndex aIif, MifIndex aOif); + void Erase(void) { mIif = kMifIndexNone; } + void SetValidPktCnt(unsigned long aValidPktCnt); + + Ip6::Address mSrcAddr; + Ip6::Address mGroupAddr; + uint64_t mLastUseTime; + unsigned long mValidPktCnt; + MifIndex mIif; + MifIndex mOif; + }; + + void Enable(void); + void Disable(void); + void Add(const Ip6::Address &aAddress); + void Remove(const Ip6::Address &aAddress); + bool HasMulticastListener(const Ip6::Address &aAddress) const; + bool IsEnabled(void) const { return mMulticastRouterSock >= 0; } + void InitMulticastRouterSock(void); + void FinalizeMulticastRouterSock(void); + void ProcessMulticastRouterMessages(void); + otError AddMulticastForwardingCache(const Ip6::Address &aSrcAddr, const Ip6::Address &aGroupAddr, MifIndex aIif); + void SaveMulticastForwardingCache(const Ip6::Address &aSrcAddr, + const Ip6::Address &aGroupAddr, + MifIndex aIif, + MifIndex aOif); + void UnblockInboundMulticastForwardingCache(const Ip6::Address &aGroupAddr); + void RemoveInboundMulticastForwardingCache(const Ip6::Address &aGroupAddr); + void ExpireMulticastForwardingCache(void); + bool UpdateMulticastRouteInfo(MulticastForwardingCache &aMfc) const; + void RemoveMulticastForwardingCache(MulticastForwardingCache &aMfc) const; + static const char *MifIndexToString(MifIndex aMif); + void DumpMulticastForwardingCache(void) const; + static void HandleBackboneMulticastListenerEvent(void * aContext, + otBackboneRouterMulticastListenerEvent aEvent, + const otIp6Address * aAddress); + void HandleBackboneMulticastListenerEvent(otBackboneRouterMulticastListenerEvent aEvent, + const Ip6::Address & aAddress); + + MulticastForwardingCache mMulticastForwardingCacheTable[kMulitcastForwardingCacheTableSize]; + uint64_t mLastExpireTime; + int mMulticastRouterSock; + otInstance * mInstance; +}; + +} // namespace Posix +} // namespace ot + +#endif // OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + +#endif // OT_POSIX_PLATFORM_MULTICAST_ROUTING_HPP_ diff --git a/src/posix/platform/netif.cpp b/src/posix/platform/netif.cpp index 3797c1dab..c84c21cb6 100644 --- a/src/posix/platform/netif.cpp +++ b/src/posix/platform/netif.cpp @@ -537,11 +537,11 @@ static void processAddressChange(const otIp6AddressInfo *aAddressInfo, bool aIsA } } -static void processStateChange(otChangedFlags aFlags, void *aContext) +void platformNetifStateChange(otInstance *aInstance, otChangedFlags aFlags) { if (OT_CHANGED_THREAD_NETIF_STATE & aFlags) { - UpdateLink(static_cast(aContext)); + UpdateLink(aInstance); } } @@ -1500,7 +1500,6 @@ void platformNetifInit(otInstance *aInstance, const char *aInterfaceName) otIcmp6SetEchoMode(aInstance, OT_ICMP6_ECHO_HANDLER_DISABLED); otIp6SetReceiveCallback(aInstance, processReceive, aInstance); otIp6SetAddressCallback(aInstance, processAddressChange, aInstance); - SuccessOrDie(otSetStateChangedCallback(aInstance, processStateChange, aInstance)); #if OPENTHREAD_POSIX_MULTICAST_PROMISCUOUS_REQUIRED otIp6SetMulticastPromiscuousEnabled(aInstance, true); #endif diff --git a/src/posix/platform/openthread-posix-config.h b/src/posix/platform/openthread-posix-config.h index 223e8e26d..c851d8392 100644 --- a/src/posix/platform/openthread-posix-config.h +++ b/src/posix/platform/openthread-posix-config.h @@ -101,6 +101,16 @@ #define OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE 0 #endif +/** + * @def OPENTHREAD_POSIX_CONFIG_MAX_MULTICAST_FORWARDING_CACHE_TABLE + * + * This setting configures the maximum number of Multicast Forwarding Cache table for POSIX native multicast routing. + * + */ +#ifndef OPENTHREAD_POSIX_CONFIG_MAX_MULTICAST_FORWARDING_CACHE_TABLE +#define OPENTHREAD_POSIX_CONFIG_MAX_MULTICAST_FORWARDING_CACHE_TABLE (OPENTHREAD_CONFIG_MAX_MULTICAST_LISTENERS * 10) +#endif + #ifdef __APPLE__ /** diff --git a/src/posix/platform/platform-posix.h b/src/posix/platform/platform-posix.h index 6d360df9b..1c9d274bc 100644 --- a/src/posix/platform/platform-posix.h +++ b/src/posix/platform/platform-posix.h @@ -270,6 +270,15 @@ void platformNetifUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, fd_set *a */ void platformNetifProcess(const fd_set *aReadFdSet, const fd_set *aWriteFdSet, const fd_set *aErrorFdSet); +/** + * This function performs notifies state changes to platform netif. + * + * @param[in] aInstance A pointer to the OpenThread instance. + * @param[in] aFlags Flags that denote the state change events. + * + */ +void platformNetifStateChange(otInstance *aInstance, otChangedFlags aFlags); + /** * This function initialize virtual time simulation. * @@ -407,7 +416,6 @@ extern char gNetifName[IFNAMSIZ]; */ extern unsigned int gNetifIndex; -#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE /** * This function initializes platform Backbone network. * @@ -417,6 +425,32 @@ extern unsigned int gNetifIndex; */ void platformBackboneInit(otInstance *aInstance, const char *aInterfaceName); +/** + * This function updates the file descriptor sets with file descriptors used by the platform Backbone network. + * + * @param[inout] aReadFdSet A reference to the read file descriptors. + * @param[inout] aMaxFd A reference to the max file descriptor. + * + */ +void platformBackboneUpdateFdSet(fd_set &aReadFdSet, int &aMaxFd); + +/** + * This function performs platform Backbone network processing. + * + * @param[in] aReadFdSet A reference to the read file descriptors. + * + */ +void platformBackboneProcess(const fd_set &aReadSet); + +/** + * This function performs notifies state changes to platform Backbone network. + * + * @param[in] aInstance A pointer to the OpenThread instance. + * @param[in] aFlags Flags that denote the state change events. + * + */ +void platformBackboneStateChange(otInstance *aInstance, otChangedFlags aFlags); + /** * The name of Backbone network interface. * @@ -428,7 +462,6 @@ extern char gBackboneNetifName[IFNAMSIZ]; * */ extern unsigned int gBackboneNetifIndex; -#endif #ifdef __cplusplus } diff --git a/src/posix/platform/system.cpp b/src/posix/platform/system.cpp index 74ad6e288..86063ff6a 100644 --- a/src/posix/platform/system.cpp +++ b/src/posix/platform/system.cpp @@ -46,6 +46,24 @@ #include "common/code_utils.hpp" +#if OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE || OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE +static void processStateChange(otChangedFlags aFlags, void *aContext) +{ + otInstance *instance = static_cast(aContext); + + OT_UNUSED_VARIABLE(instance); + OT_UNUSED_VARIABLE(aFlags); + +#if OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE + platformNetifStateChange(instance, aFlags); +#endif + +#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + platformBackboneStateChange(instance, aFlags); +#endif +} +#endif + otInstance *otSysInit(otPlatformConfig *aPlatformConfig) { otInstance * instance = nullptr; @@ -82,6 +100,10 @@ otInstance *otSysInit(otPlatformConfig *aPlatformConfig) platformUdpInit(aPlatformConfig->mInterfaceName); #endif +#if OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE || OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE + SuccessOrDie(otSetStateChangedCallback(instance, processStateChange, instance)); +#endif + return instance; } @@ -142,6 +164,9 @@ void otSysMainloopUpdate(otInstance *aInstance, otSysMainloopContext *aMainloop) platformNetifUpdateFdSet(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mErrorFdSet, &aMainloop->mMaxFd); #endif +#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + platformBackboneUpdateFdSet(aMainloop->mReadFdSet, aMainloop->mMaxFd); +#endif #if OPENTHREAD_POSIX_VIRTUAL_TIME virtualTimeUpdateFdSet(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mErrorFdSet, &aMainloop->mMaxFd, &aMainloop->mTimeout); @@ -214,6 +239,9 @@ void otSysMainloopProcess(otInstance *aInstance, const otSysMainloopContext *aMa #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE platformUdpProcess(aInstance, &aMainloop->mReadFdSet); #endif +#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + platformBackboneProcess(aMainloop->mReadFdSet); +#endif } #if OPENTHREAD_CONFIG_OTNS_ENABLE