blob: 6580782eaa7070336e71b021c116d247c1f38f82 [file] [log] [blame]
/*
* 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_MULTICAST_ROUTING_ENABLE
#include <assert.h>
#include <net/if.h>
#include <netinet/icmp6.h>
#include <netinet/in.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#if __linux__
#include <linux/mroute6.h>
#else
#error "Multicast Routing feature is not ported to non-Linux platforms yet."
#endif
#include <openthread/backbone_router_ftd.h>
#include <openthread/logging.h>
#include "core/common/arg_macros.hpp"
#include "core/common/debug.hpp"
namespace ot {
namespace Posix {
#define LogResult(aError, ...) \
do \
{ \
otError _err = (aError); \
\
if (_err == OT_ERROR_NONE) \
{ \
otLogInfoPlat(OT_FIRST_ARG(__VA_ARGS__) ": %s" OT_REST_ARGS(__VA_ARGS__), otThreadErrorToString(_err)); \
} \
else \
{ \
otLogWarnPlat(OT_FIRST_ARG(__VA_ARGS__) ": %s" OT_REST_ARGS(__VA_ARGS__), otThreadErrorToString(_err)); \
} \
} while (false)
void MulticastRoutingManager::SetUp(void)
{
OT_ASSERT(gInstance != nullptr);
otBackboneRouterSetMulticastListenerCallback(gInstance,
&MulticastRoutingManager::HandleBackboneMulticastListenerEvent, this);
Mainloop::Manager::Get().Add(*this);
}
void MulticastRoutingManager::TearDown(void)
{
OT_ASSERT(gInstance != nullptr);
otBackboneRouterSetMulticastListenerCallback(gInstance, nullptr, nullptr);
Mainloop::Manager::Get().Remove(*this);
}
void MulticastRoutingManager::HandleBackboneMulticastListenerEvent(void * aContext,
otBackboneRouterMulticastListenerEvent aEvent,
const otIp6Address * aAddress)
{
static_cast<MulticastRoutingManager *>(aContext)->HandleBackboneMulticastListenerEvent(
aEvent, static_cast<const Ip6::Address &>(*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();
LogResult(OT_ERROR_NONE, "MulticastRoutingManager: %s", __FUNCTION__);
exit:
return;
}
void MulticastRoutingManager::Disable(void)
{
FinalizeMulticastRouterSock();
LogResult(OT_ERROR_NONE, "MulticastRoutingManager: %s", __FUNCTION__);
}
void MulticastRoutingManager::Add(const Ip6::Address &aAddress)
{
VerifyOrExit(IsEnabled());
UnblockInboundMulticastForwardingCache(aAddress);
UpdateMldReport(aAddress, true);
LogResult(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);
UpdateMldReport(aAddress, false);
LogResult(error, "MulticastRoutingManager: %s: %s", __FUNCTION__, aAddress.ToString().AsCString());
exit:
return;
}
void MulticastRoutingManager::UpdateMldReport(const Ip6::Address &aAddress, bool isAdd)
{
struct ipv6_mreq ipv6mr;
otError error = OT_ERROR_NONE;
ipv6mr.ipv6mr_interface = if_nametoindex(gBackboneNetifName);
memcpy(&ipv6mr.ipv6mr_multiaddr, aAddress.GetBytes(), sizeof(ipv6mr.ipv6mr_multiaddr));
error = (setsockopt(mMulticastRouterSock, IPPROTO_IPV6, (isAdd ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP),
(void *)&ipv6mr, sizeof(ipv6mr))
? OT_ERROR_FAILED
: OT_ERROR_NONE);
LogResult(error, "MulticastRoutingManager: %s: address %s %s", __FUNCTION__, aAddress.ToString().AsCString(),
(isAdd ? "Added" : "Removed"));
}
bool MulticastRoutingManager::HasMulticastListener(const Ip6::Address &aAddress) const
{
bool found = false;
otBackboneRouterMulticastListenerIterator iter = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ITERATOR_INIT;
otBackboneRouterMulticastListenerInfo listenerInfo;
while (otBackboneRouterMulticastListenerGetNext(gInstance, &iter, &listenerInfo) == OT_ERROR_NONE)
{
VerifyOrExit(static_cast<const Ip6::Address &>(listenerInfo.mAddress) != aAddress, found = true);
}
exit:
return found;
}
void MulticastRoutingManager::Update(otSysMainloopContext &aContext)
{
VerifyOrExit(IsEnabled());
FD_SET(mMulticastRouterSock, &aContext.mReadFdSet);
aContext.mMaxFd = OT_MAX(aContext.mMaxFd, mMulticastRouterSock);
exit:
return;
}
void MulticastRoutingManager::Process(const otSysMainloopContext &aContext)
{
VerifyOrExit(IsEnabled());
ExpireMulticastForwardingCache();
if (FD_ISSET(mMulticastRouterSock, &aContext.mReadFdSet))
{
ProcessMulticastRouterMessages();
}
exit:
return;
}
void MulticastRoutingManager::InitMulticastRouterSock(void)
{
int one = 1;
struct icmp6_filter filter;
struct mif6ctl mif6ctl;
// Create a Multicast Routing socket
mMulticastRouterSock = SocketWithCloseExec(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6, kSocketBlock);
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<int>(sizeof(struct mrt6msg)), error = OT_ERROR_FAILED);
mrt6msg = reinterpret_cast<struct mrt6msg *>(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<MifIndex>(mrt6msg->im6_mif));
exit:
LogResult(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:
LogResult(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);
LogResult(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_PLATFORM && (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;
LogResult(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_MULTICAST_ROUTING_ENABLE