blob: 0f2760bec1d5527711db56fe6c5b0cee8b60920e [file] [edit]
/*
* Copyright (c) 2022, 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.
*/
/**
* @file
* The file implements the Infrastructure Link Selector.
*/
#if __linux__
#define OTBR_LOG_TAG "ILS"
#include "utils/infra_link_selector.hpp"
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <openthread/backbone_router_ftd.h>
#include "utils/socket_utils.hpp"
namespace otbr {
namespace Utils {
constexpr Milliseconds InfraLinkSelector::kInfraLinkSelectionDelay;
bool InfraLinkSelector::LinkInfo::Update(LinkState aState)
{
bool changed = mState != aState;
VerifyOrExit(changed);
if (mState == kUpAndRunning && aState != kUpAndRunning)
{
mWasUpAndRunning = true;
mLastRunningTime = Clock::now();
}
mState = aState;
exit:
return changed;
}
InfraLinkSelector::InfraLinkSelector(std::vector<const char *> aInfraLinkNames)
: mInfraLinkNames(std::move(aInfraLinkNames))
{
if (mInfraLinkNames.size() >= 2)
{
mNetlinkSocket = CreateNetLinkRouteSocket(RTMGRP_LINK);
VerifyOrDie(mNetlinkSocket != -1, "Failed to create netlink socket");
}
for (const char *name : mInfraLinkNames)
{
mInfraLinkInfos[name].Update(QueryInfraLinkState(name));
}
}
InfraLinkSelector::~InfraLinkSelector(void)
{
if (mNetlinkSocket != -1)
{
close(mNetlinkSocket);
mNetlinkSocket = -1;
}
}
const char *InfraLinkSelector::Select(void)
{
const char *sel;
#if OTBR_ENABLE_VENDOR_INFRA_LINK_SELECT
sel = otbrVendorInfraLinkSelect();
if (sel == nullptr)
{
sel = SelectGeneric();
}
#else
sel = SelectGeneric();
#endif
return sel;
}
const char *InfraLinkSelector::SelectGeneric(void)
{
const char *prevInfraLink = mCurrentInfraLink;
InfraLinkSelector::LinkState currentInfraLinkState = kInvalid;
auto now = Clock::now();
LinkInfo *currentInfraLinkInfo = nullptr;
VerifyOrExit(!mInfraLinkNames.empty(), mCurrentInfraLink = kDefaultInfraLinkName);
VerifyOrExit(mInfraLinkNames.size() > 1, mCurrentInfraLink = mInfraLinkNames.front());
VerifyOrExit(mRequireReselect, assert(mCurrentInfraLink != nullptr));
otbrLogInfo("Evaluating infra link among %zu netifs:", mInfraLinkNames.size());
// Prefer `mCurrentInfraLink` if it's up and running.
if (mCurrentInfraLink != nullptr)
{
currentInfraLinkInfo = &mInfraLinkInfos[mCurrentInfraLink];
otbrLogInfo("\tInfra link %s is in state %s", mCurrentInfraLink,
LinkStateToString(currentInfraLinkInfo->mState));
VerifyOrExit(currentInfraLinkInfo->mState != kUpAndRunning);
}
// Select an infra link with best state.
{
const char *bestInfraLink = mCurrentInfraLink;
InfraLinkSelector::LinkState bestState = currentInfraLinkState;
for (const char *name : mInfraLinkNames)
{
if (name != mCurrentInfraLink)
{
LinkInfo &linkInfo = mInfraLinkInfos[name];
otbrLogInfo("\tInfra link %s is in state %s", name, LinkStateToString(linkInfo.mState));
if (bestInfraLink == nullptr || linkInfo.mState > bestState)
{
bestInfraLink = name;
bestState = linkInfo.mState;
}
}
}
VerifyOrExit(bestInfraLink != mCurrentInfraLink);
// Prefer `mCurrentInfraLink` if no other infra link is up and running
VerifyOrExit(mCurrentInfraLink == nullptr || bestState == kUpAndRunning);
// Prefer `mCurrentInfraLink` if it's down for less than `kInfraLinkSelectionDelay`
if (mCurrentInfraLink != nullptr && currentInfraLinkInfo->mWasUpAndRunning)
{
Milliseconds timeSinceLastRunning =
std::chrono::duration_cast<Milliseconds>(now - currentInfraLinkInfo->mLastRunningTime);
if (timeSinceLastRunning < kInfraLinkSelectionDelay)
{
Milliseconds delay = kInfraLinkSelectionDelay - timeSinceLastRunning;
otbrLogInfo("Infra link %s was running %lldms ago, wait for %lldms to recheck.", mCurrentInfraLink,
timeSinceLastRunning.count(), delay.count());
mTaskRunner.Post(delay, [this]() { mRequireReselect = true; });
ExitNow();
}
}
// Current infra link changed.
mCurrentInfraLink = bestInfraLink;
}
exit:
if (mRequireReselect)
{
mRequireReselect = false;
if (prevInfraLink != mCurrentInfraLink)
{
if (prevInfraLink == nullptr)
{
otbrLogNotice("Infra link selected: %s", mCurrentInfraLink);
}
else
{
otbrLogWarning("Infra link switched from %s to %s", prevInfraLink, mCurrentInfraLink);
}
}
else
{
otbrLogInfo("Infra link unchanged: %s", mCurrentInfraLink);
}
}
return mCurrentInfraLink;
}
InfraLinkSelector::LinkState InfraLinkSelector::QueryInfraLinkState(const char *aInfraLinkName)
{
int sock = 0;
struct ifreq ifReq;
InfraLinkSelector::LinkState state = kInvalid;
sock = SocketWithCloseExec(AF_INET6, SOCK_DGRAM, IPPROTO_IP, kSocketBlock);
VerifyOrDie(sock != -1, "Failed to create AF_INET6 socket.");
memset(&ifReq, 0, sizeof(ifReq));
strncpy(ifReq.ifr_name, aInfraLinkName, sizeof(ifReq.ifr_name) - 1);
VerifyOrExit(ioctl(sock, SIOCGIFFLAGS, &ifReq) != -1);
state = (ifReq.ifr_flags & IFF_UP) ? ((ifReq.ifr_flags & IFF_RUNNING) ? kUpAndRunning : kUp) : kDown;
exit:
if (sock != 0)
{
close(sock);
}
return state;
}
void InfraLinkSelector::Update(MainloopContext &aMainloop)
{
if (mNetlinkSocket != -1)
{
FD_SET(mNetlinkSocket, &aMainloop.mReadFdSet);
aMainloop.mMaxFd = std::max(mNetlinkSocket, aMainloop.mMaxFd);
}
}
void InfraLinkSelector::Process(const MainloopContext &aMainloop)
{
OTBR_UNUSED_VARIABLE(aMainloop);
if (mNetlinkSocket != -1 && FD_ISSET(mNetlinkSocket, &aMainloop.mReadFdSet))
{
ReceiveNetLinkMessage();
}
}
void InfraLinkSelector::ReceiveNetLinkMessage(void)
{
const size_t kMaxNetLinkBufSize = 8192;
ssize_t len;
union
{
nlmsghdr mHeader;
uint8_t mBuffer[kMaxNetLinkBufSize];
} msgBuffer;
len = recv(mNetlinkSocket, msgBuffer.mBuffer, sizeof(msgBuffer.mBuffer), 0);
if (len < 0)
{
otbrLogWarning("Failed to receive netlink message: %s", strerror(errno));
ExitNow();
}
for (struct nlmsghdr *header = &msgBuffer.mHeader; NLMSG_OK(header, static_cast<size_t>(len));
header = NLMSG_NEXT(header, len))
{
struct ifinfomsg *ifinfo;
switch (header->nlmsg_type)
{
case RTM_NEWLINK:
case RTM_DELLINK:
ifinfo = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(header));
HandleInfraLinkStateChange(ifinfo->ifi_index);
break;
case NLMSG_ERROR:
{
struct nlmsgerr *errMsg = reinterpret_cast<struct nlmsgerr *>(NLMSG_DATA(header));
otbrLogWarning("netlink NLMSG_ERROR response: seq=%u, error=%d", header->nlmsg_seq, errMsg->error);
break;
}
default:
break;
}
}
exit:
return;
}
void InfraLinkSelector::HandleInfraLinkStateChange(uint32_t aInfraLinkIndex)
{
const char *infraLinkName = nullptr;
for (const char *name : mInfraLinkNames)
{
if (aInfraLinkIndex == if_nametoindex(name))
{
infraLinkName = name;
break;
}
}
VerifyOrExit(infraLinkName != nullptr);
{
LinkInfo &linkInfo = mInfraLinkInfos[infraLinkName];
LinkState prevState = linkInfo.mState;
if (linkInfo.Update(QueryInfraLinkState(infraLinkName)))
{
otbrLogInfo("Infra link name %s index %lu state changed: %s -> %s", infraLinkName, aInfraLinkIndex,
LinkStateToString(prevState), LinkStateToString(linkInfo.mState));
mRequireReselect = true;
}
}
exit:
return;
}
const char *InfraLinkSelector::LinkStateToString(LinkState aState)
{
const char *str = "";
switch (aState)
{
case kInvalid:
str = "INVALID";
break;
case kDown:
str = "DOWN";
break;
case kUp:
str = "UP";
break;
case kUpAndRunning:
str = "UP+RUNNING";
break;
}
return str;
}
} // namespace Utils
} // namespace otbr
#endif // __linux__