fune/netwerk/system/netlink/NetlinkService.cpp
Valentin Gosu 1a1f42da37 Bug 1714307 - Run modernize-use-default-member-init --fix check on netwerk r=necko-reviewers,kershaw
This changeset is the result of adding modernize-use-default-member-init to
tools/clang-tidy/config.yaml then proceeding to run
`./mach static-analysis check netwerk/ --fix`
I then went through the resulting fix and manually updated all of the member
variables which were missed due to them having a non-trivial constructor.

Note that the tool was only run on Linux, so code that only runs on some
platforms may have been missed.

The member variables that are still initialized in the contructor definition
are:
  - bitfields (not all currently supported compilers allow default-member-init
  - variables that are initialized via a parameter
  - variables that use code not visible in the header file

There are a few advantages to landing this change:
- fewer lines of code - now declaration is in the same place as initialization
  this also makes it easier to see when looking at the header.
- it makes it harder to miss initializing a member when adding a new contructor
- variables that depend on an include guard look much nicer now

Additionally I removed some unnecessary reinitialization of NetAddr members
(it has a constructor that does that now), and changed nsWifiScannerDBus to
use the thread-safe strtok_r instead of strtok.

Differential Revision: https://phabricator.services.mozilla.com/D116980
2021-06-11 07:10:41 +00:00

1905 lines
58 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set et sw=2 ts=4: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <arpa/inet.h>
#include <netinet/ether.h>
#include <net/if.h>
#include <poll.h>
#include <linux/rtnetlink.h>
#include "nsThreadUtils.h"
#include "nsServiceManagerUtils.h"
#include "NetlinkService.h"
#include "nsIThread.h"
#include "nsString.h"
#include "nsPrintfCString.h"
#include "mozilla/Logging.h"
#include "../../base/IPv6Utils.h"
#include "../NetworkLinkServiceDefines.h"
#include "mozilla/Base64.h"
#include "mozilla/FileUtils.h"
#include "mozilla/FunctionTypeTraits.h"
#include "mozilla/Services.h"
#include "mozilla/Sprintf.h"
#include "mozilla/Telemetry.h"
#include "mozilla/DebugOnly.h"
#if defined(HAVE_RES_NINIT)
# include <netinet/in.h>
# include <arpa/nameser.h>
# include <resolv.h>
#endif
namespace mozilla::net {
template <typename F>
static auto eintr_retry(F&& func) ->
typename FunctionTypeTraits<decltype(func)>::ReturnType {
typename FunctionTypeTraits<decltype(func)>::ReturnType _rc;
do {
_rc = func();
} while (_rc == -1 && errno == EINTR);
return _rc;
}
#define EINTR_RETRY(expr) eintr_retry([&]() { return expr; })
// period during which to absorb subsequent network change events, in
// milliseconds
static const unsigned int kNetworkChangeCoalescingPeriod = 1000;
static LazyLogModule gNlSvcLog("NetlinkService");
#define LOG(args) MOZ_LOG(gNlSvcLog, mozilla::LogLevel::Debug, args)
#undef LOG_ENABLED
#define LOG_ENABLED() MOZ_LOG_TEST(gNlSvcLog, mozilla::LogLevel::Debug)
using in_common_addr = union {
struct in_addr addr4;
struct in6_addr addr6;
};
static void GetAddrStr(const in_common_addr* aAddr, uint8_t aFamily,
nsACString& _retval) {
char addr[INET6_ADDRSTRLEN];
addr[0] = 0;
if (aFamily == AF_INET) {
inet_ntop(AF_INET, &(aAddr->addr4), addr, INET_ADDRSTRLEN);
} else {
inet_ntop(AF_INET6, &(aAddr->addr6), addr, INET6_ADDRSTRLEN);
}
_retval.Assign(addr);
}
class NetlinkAddress {
public:
NetlinkAddress() = default;
uint8_t Family() const { return mIfam.ifa_family; }
uint32_t GetIndex() const { return mIfam.ifa_index; }
uint8_t GetPrefixLen() const { return mIfam.ifa_prefixlen; }
bool ScopeIsUniverse() const { return mIfam.ifa_scope == RT_SCOPE_UNIVERSE; }
const in_common_addr* GetAddrPtr() const { return &mAddr; }
bool MsgEquals(const NetlinkAddress& aOther) const {
return !memcmp(&mIfam, &(aOther.mIfam), sizeof(mIfam));
}
bool Equals(const NetlinkAddress& aOther) const {
if (mIfam.ifa_family != aOther.mIfam.ifa_family) {
return false;
}
if (mIfam.ifa_index != aOther.mIfam.ifa_index) {
// addresses are different when they are on a different interface
return false;
}
if (mIfam.ifa_prefixlen != aOther.mIfam.ifa_prefixlen) {
// It's possible to have two equal addresses with a different netmask on
// the same interface, so we need to check prefixlen too.
return false;
}
size_t addrSize = (mIfam.ifa_family == AF_INET) ? sizeof(mAddr.addr4)
: sizeof(mAddr.addr6);
return memcmp(&mAddr, aOther.GetAddrPtr(), addrSize) == 0;
}
bool ContainsAddr(const in_common_addr* aAddr) {
int32_t addrSize = (mIfam.ifa_family == AF_INET)
? (int32_t)sizeof(mAddr.addr4)
: (int32_t)sizeof(mAddr.addr6);
uint8_t maskit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe};
int32_t bits = mIfam.ifa_prefixlen;
if (bits > addrSize * 8) {
MOZ_ASSERT(false, "Unexpected prefix length!");
LOG(("Unexpected prefix length %d, maximum for this family is %d", bits,
addrSize * 8));
return false;
}
for (int32_t i = 0; i < addrSize; i++) {
uint8_t mask = (bits >= 8) ? 0xff : maskit[bits];
if ((((unsigned char*)aAddr)[i] & mask) !=
(((unsigned char*)(&mAddr))[i] & mask)) {
return false;
}
bits -= 8;
if (bits <= 0) {
return true;
}
}
return true;
}
bool Init(struct nlmsghdr* aNlh) {
struct ifaddrmsg* ifam;
struct rtattr* attr;
int len;
ifam = (ifaddrmsg*)NLMSG_DATA(aNlh);
len = IFA_PAYLOAD(aNlh);
if (ifam->ifa_family != AF_INET && ifam->ifa_family != AF_INET6) {
return false;
}
bool hasAddr = false;
for (attr = IFA_RTA(ifam); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
if (attr->rta_type == IFA_ADDRESS || attr->rta_type == IFA_LOCAL) {
memcpy(&mAddr, RTA_DATA(attr),
ifam->ifa_family == AF_INET ? sizeof(mAddr.addr4)
: sizeof(mAddr.addr6));
hasAddr = true;
if (attr->rta_type == IFA_LOCAL) {
// local address is preferred, so don't continue parsing other
// attributes
break;
}
}
}
if (!hasAddr) {
return false;
}
memcpy(&mIfam, (ifaddrmsg*)NLMSG_DATA(aNlh), sizeof(mIfam));
return true;
}
private:
in_common_addr mAddr;
struct ifaddrmsg mIfam;
};
class NetlinkNeighbor {
public:
NetlinkNeighbor() = default;
uint8_t Family() const { return mNeigh.ndm_family; }
uint32_t GetIndex() const { return mNeigh.ndm_ifindex; }
const in_common_addr* GetAddrPtr() const { return &mAddr; }
const uint8_t* GetMACPtr() const { return mMAC; }
bool HasMAC() const { return mHasMAC; };
void GetAsString(nsACString& _retval) const {
nsAutoCString addrStr;
_retval.Assign("addr=");
GetAddrStr(&mAddr, mNeigh.ndm_family, addrStr);
_retval.Append(addrStr);
if (mNeigh.ndm_family == AF_INET) {
_retval.Append(" family=AF_INET if=");
} else {
_retval.Append(" family=AF_INET6 if=");
}
_retval.AppendInt(mNeigh.ndm_ifindex);
if (mHasMAC) {
_retval.Append(" mac=");
_retval.Append(nsPrintfCString("%02x:%02x:%02x:%02x:%02x:%02x", mMAC[0],
mMAC[1], mMAC[2], mMAC[3], mMAC[4],
mMAC[5]));
}
}
bool Init(struct nlmsghdr* aNlh) {
struct ndmsg* neigh;
struct rtattr* attr;
int len;
neigh = (ndmsg*)NLMSG_DATA(aNlh);
len = aNlh->nlmsg_len - NLMSG_LENGTH(sizeof(*neigh));
if (neigh->ndm_family != AF_INET && neigh->ndm_family != AF_INET6) {
return false;
}
bool hasDST = false;
for (attr = RTM_RTA(neigh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
if (attr->rta_type == NDA_LLADDR) {
memcpy(mMAC, RTA_DATA(attr), ETH_ALEN);
mHasMAC = true;
}
if (attr->rta_type == NDA_DST) {
memcpy(&mAddr, RTA_DATA(attr),
neigh->ndm_family == AF_INET ? sizeof(mAddr.addr4)
: sizeof(mAddr.addr6));
hasDST = true;
}
}
if (!hasDST) {
return false;
}
memcpy(&mNeigh, (ndmsg*)NLMSG_DATA(aNlh), sizeof(mNeigh));
return true;
}
private:
bool mHasMAC{false};
uint8_t mMAC[ETH_ALEN]{};
in_common_addr mAddr{};
struct ndmsg mNeigh {};
};
class NetlinkLink {
public:
NetlinkLink() = default;
bool IsUp() const {
return (mIface.ifi_flags & IFF_RUNNING) &&
!(mIface.ifi_flags & IFF_LOOPBACK);
}
void GetName(nsACString& _retval) const { _retval = mName; }
bool IsTypeEther() const { return mIface.ifi_type == ARPHRD_ETHER; }
uint32_t GetIndex() const { return mIface.ifi_index; }
uint32_t GetFlags() const { return mIface.ifi_flags; }
uint16_t GetType() const { return mIface.ifi_type; }
bool Init(struct nlmsghdr* aNlh) {
struct ifinfomsg* iface;
struct rtattr* attr;
int len;
iface = (ifinfomsg*)NLMSG_DATA(aNlh);
len = aNlh->nlmsg_len - NLMSG_LENGTH(sizeof(*iface));
bool hasName = false;
for (attr = IFLA_RTA(iface); RTA_OK(attr, len);
attr = RTA_NEXT(attr, len)) {
if (attr->rta_type == IFLA_IFNAME) {
mName.Assign((char*)RTA_DATA(attr));
hasName = true;
break;
}
}
if (!hasName) {
return false;
}
memcpy(&mIface, (ifinfomsg*)NLMSG_DATA(aNlh), sizeof(mIface));
return true;
}
private:
nsCString mName;
struct ifinfomsg mIface {};
};
class NetlinkRoute {
public:
NetlinkRoute()
: mHasGWAddr(false),
mHasPrefSrcAddr(false),
mHasDstAddr(false),
mHasOif(false),
mHasPrio(false) {}
bool IsUnicast() const { return mRtm.rtm_type == RTN_UNICAST; }
bool ScopeIsUniverse() const { return mRtm.rtm_scope == RT_SCOPE_UNIVERSE; }
bool IsDefault() const { return mRtm.rtm_dst_len == 0; }
bool HasOif() const { return mHasOif; }
uint8_t Oif() const { return mOif; }
uint8_t Family() const { return mRtm.rtm_family; }
bool HasPrefSrcAddr() const { return mHasPrefSrcAddr; }
const in_common_addr* GetGWAddrPtr() const {
return mHasGWAddr ? &mGWAddr : nullptr;
}
const in_common_addr* GetPrefSrcAddrPtr() const {
return mHasPrefSrcAddr ? &mPrefSrcAddr : nullptr;
}
bool Equals(const NetlinkRoute& aOther) const {
size_t addrSize = (mRtm.rtm_family == AF_INET) ? sizeof(mDstAddr.addr4)
: sizeof(mDstAddr.addr6);
if (memcmp(&mRtm, &(aOther.mRtm), sizeof(mRtm)) != 0) {
return false;
}
if (mHasOif != aOther.mHasOif || mOif != aOther.mOif) {
return false;
}
if (mHasPrio != aOther.mHasPrio || mPrio != aOther.mPrio) {
return false;
}
if ((mHasGWAddr != aOther.mHasGWAddr) ||
(mHasGWAddr && memcmp(&mGWAddr, &(aOther.mGWAddr), addrSize) != 0)) {
return false;
}
if ((mHasDstAddr != aOther.mHasDstAddr) ||
(mHasDstAddr && memcmp(&mDstAddr, &(aOther.mDstAddr), addrSize) != 0)) {
return false;
}
if ((mHasPrefSrcAddr != aOther.mHasPrefSrcAddr) ||
(mHasPrefSrcAddr &&
memcmp(&mPrefSrcAddr, &(aOther.mPrefSrcAddr), addrSize) != 0)) {
return false;
}
return true;
}
bool GatewayEquals(const NetlinkNeighbor& aNeigh) const {
if (!mHasGWAddr) {
return false;
}
if (aNeigh.Family() != mRtm.rtm_family) {
return false;
}
size_t addrSize = (mRtm.rtm_family == AF_INET) ? sizeof(mGWAddr.addr4)
: sizeof(mGWAddr.addr6);
return memcmp(&mGWAddr, aNeigh.GetAddrPtr(), addrSize) == 0;
}
bool GatewayEquals(const NetlinkRoute* aRoute) const {
if (!mHasGWAddr || !aRoute->mHasGWAddr) {
return false;
}
if (mRtm.rtm_family != aRoute->mRtm.rtm_family) {
return false;
}
size_t addrSize = (mRtm.rtm_family == AF_INET) ? sizeof(mGWAddr.addr4)
: sizeof(mGWAddr.addr6);
return memcmp(&mGWAddr, &(aRoute->mGWAddr), addrSize) == 0;
}
bool PrefSrcAddrEquals(const NetlinkAddress& aAddress) const {
if (!mHasPrefSrcAddr) {
return false;
}
if (mRtm.rtm_family != aAddress.Family()) {
return false;
}
size_t addrSize = (mRtm.rtm_family == AF_INET) ? sizeof(mPrefSrcAddr.addr4)
: sizeof(mPrefSrcAddr.addr6);
return memcmp(&mPrefSrcAddr, aAddress.GetAddrPtr(), addrSize) == 0;
}
void GetAsString(nsACString& _retval) const {
nsAutoCString addrStr;
_retval.Assign("table=");
_retval.AppendInt(mRtm.rtm_table);
_retval.Append(" type=");
_retval.AppendInt(mRtm.rtm_type);
_retval.Append(" scope=");
_retval.AppendInt(mRtm.rtm_scope);
if (mRtm.rtm_family == AF_INET) {
_retval.Append(" family=AF_INET dst=");
addrStr.Assign("0.0.0.0/");
} else {
_retval.Append(" family=AF_INET6 dst=");
addrStr.Assign("::/");
}
if (mHasDstAddr) {
GetAddrStr(&mDstAddr, mRtm.rtm_family, addrStr);
addrStr.Append("/");
}
_retval.Append(addrStr);
_retval.AppendInt(mRtm.rtm_dst_len);
if (mHasPrefSrcAddr) {
_retval.Append(" src=");
GetAddrStr(&mPrefSrcAddr, mRtm.rtm_family, addrStr);
_retval.Append(addrStr);
}
if (mHasGWAddr) {
_retval.Append(" via=");
GetAddrStr(&mGWAddr, mRtm.rtm_family, addrStr);
_retval.Append(addrStr);
}
if (mHasOif) {
_retval.Append(" oif=");
_retval.AppendInt(mOif);
}
if (mHasPrio) {
_retval.Append(" prio=");
_retval.AppendInt(mPrio);
}
}
bool Init(struct nlmsghdr* aNlh) {
struct rtmsg* rtm;
struct rtattr* attr;
int len;
rtm = (rtmsg*)NLMSG_DATA(aNlh);
len = RTM_PAYLOAD(aNlh);
if (rtm->rtm_family != AF_INET && rtm->rtm_family != AF_INET6) {
return false;
}
for (attr = RTM_RTA(rtm); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
if (attr->rta_type == RTA_DST) {
memcpy(&mDstAddr, RTA_DATA(attr),
(rtm->rtm_family == AF_INET) ? sizeof(mDstAddr.addr4)
: sizeof(mDstAddr.addr6));
mHasDstAddr = true;
} else if (attr->rta_type == RTA_GATEWAY) {
memcpy(&mGWAddr, RTA_DATA(attr),
(rtm->rtm_family == AF_INET) ? sizeof(mGWAddr.addr4)
: sizeof(mGWAddr.addr6));
mHasGWAddr = true;
} else if (attr->rta_type == RTA_PREFSRC) {
memcpy(&mPrefSrcAddr, RTA_DATA(attr),
(rtm->rtm_family == AF_INET) ? sizeof(mPrefSrcAddr.addr4)
: sizeof(mPrefSrcAddr.addr6));
mHasPrefSrcAddr = true;
} else if (attr->rta_type == RTA_OIF) {
mOif = *(uint32_t*)RTA_DATA(attr);
mHasOif = true;
} else if (attr->rta_type == RTA_PRIORITY) {
mPrio = *(uint32_t*)RTA_DATA(attr);
mHasPrio = true;
}
}
memcpy(&mRtm, (rtmsg*)NLMSG_DATA(aNlh), sizeof(mRtm));
return true;
}
private:
bool mHasGWAddr : 1;
bool mHasPrefSrcAddr : 1;
bool mHasDstAddr : 1;
bool mHasOif : 1;
bool mHasPrio : 1;
in_common_addr mGWAddr{};
in_common_addr mDstAddr{};
in_common_addr mPrefSrcAddr{};
uint32_t mOif{};
uint32_t mPrio{};
struct rtmsg mRtm {};
};
class NetlinkMsg {
public:
static uint8_t const kGenMsg = 1;
static uint8_t const kRtMsg = 2;
NetlinkMsg() = default;
virtual ~NetlinkMsg() = default;
virtual bool Send(int aFD) = 0;
virtual bool IsPending() { return mIsPending; }
virtual uint32_t SeqId() = 0;
virtual uint8_t Family() = 0;
virtual uint8_t MsgType() = 0;
protected:
bool SendRequest(int aFD, void* aRequest, uint32_t aRequestLength) {
MOZ_ASSERT(!mIsPending, "Request has been already sent!");
struct sockaddr_nl kernel {};
memset(&kernel, 0, sizeof(kernel));
kernel.nl_family = AF_NETLINK;
kernel.nl_groups = 0;
struct iovec io {};
memset(&io, 0, sizeof(io));
io.iov_base = aRequest;
io.iov_len = aRequestLength;
struct msghdr rtnl_msg {};
memset(&rtnl_msg, 0, sizeof(rtnl_msg));
rtnl_msg.msg_iov = &io;
rtnl_msg.msg_iovlen = 1;
rtnl_msg.msg_name = &kernel;
rtnl_msg.msg_namelen = sizeof(kernel);
ssize_t rc = EINTR_RETRY(sendmsg(aFD, (struct msghdr*)&rtnl_msg, 0));
if (rc > 0 && (uint32_t)rc == aRequestLength) {
mIsPending = true;
}
return mIsPending;
}
bool mIsPending{false};
};
class NetlinkGenMsg : public NetlinkMsg {
public:
NetlinkGenMsg(uint16_t aMsgType, uint8_t aFamily, uint32_t aSeqId) {
memset(&mReq, 0, sizeof(mReq));
mReq.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
mReq.hdr.nlmsg_type = aMsgType;
mReq.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
mReq.hdr.nlmsg_seq = aSeqId;
mReq.hdr.nlmsg_pid = 0;
mReq.gen.rtgen_family = aFamily;
}
virtual bool Send(int aFD) {
return SendRequest(aFD, &mReq, mReq.hdr.nlmsg_len);
}
virtual uint32_t SeqId() { return mReq.hdr.nlmsg_seq; }
virtual uint8_t Family() { return mReq.gen.rtgen_family; }
virtual uint8_t MsgType() { return kGenMsg; }
private:
struct {
struct nlmsghdr hdr;
struct rtgenmsg gen;
} mReq{};
};
class NetlinkRtMsg : public NetlinkMsg {
public:
NetlinkRtMsg(uint8_t aFamily, void* aAddress, uint32_t aSeqId) {
MOZ_ASSERT(aFamily == AF_INET || aFamily == AF_INET6);
memset(&mReq, 0, sizeof(mReq));
mReq.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
mReq.hdr.nlmsg_type = RTM_GETROUTE;
mReq.hdr.nlmsg_flags = NLM_F_REQUEST;
mReq.hdr.nlmsg_seq = aSeqId;
mReq.hdr.nlmsg_pid = 0;
mReq.rtm.rtm_family = aFamily;
mReq.rtm.rtm_flags = 0;
mReq.rtm.rtm_dst_len = aFamily == AF_INET ? 32 : 128;
struct rtattr* rta;
rta = (struct rtattr*)(((char*)&mReq) + NLMSG_ALIGN(mReq.hdr.nlmsg_len));
rta->rta_type = RTA_DST;
size_t addrSize =
aFamily == AF_INET ? sizeof(struct in_addr) : sizeof(struct in6_addr);
rta->rta_len = RTA_LENGTH(addrSize);
memcpy(RTA_DATA(rta), aAddress, addrSize);
mReq.hdr.nlmsg_len = NLMSG_ALIGN(mReq.hdr.nlmsg_len) + RTA_LENGTH(addrSize);
}
virtual bool Send(int aFD) {
return SendRequest(aFD, &mReq, mReq.hdr.nlmsg_len);
}
virtual uint32_t SeqId() { return mReq.hdr.nlmsg_seq; }
virtual uint8_t Family() { return mReq.rtm.rtm_family; }
virtual uint8_t MsgType() { return kRtMsg; }
private:
struct {
struct nlmsghdr hdr;
struct rtmsg rtm;
unsigned char data[1024];
} mReq{};
};
NetlinkService::LinkInfo::LinkInfo(UniquePtr<NetlinkLink>&& aLink)
: mLink(std::move(aLink)), mIsUp(false) {}
NetlinkService::LinkInfo::~LinkInfo() = default;
bool NetlinkService::LinkInfo::UpdateStatus() {
LOG(("NetlinkService::LinkInfo::UpdateStatus"));
bool oldIsUp = mIsUp;
mIsUp = false;
if (!mLink->IsUp()) {
// The link is not up or is a loopback
LOG(("The link is down or is a loopback"));
} else {
// Link is up when there is non-local address associated with it.
for (uint32_t i = 0; i < mAddresses.Length(); ++i) {
if (LOG_ENABLED()) {
nsAutoCString dbgStr;
GetAddrStr(mAddresses[i]->GetAddrPtr(), mAddresses[i]->Family(),
dbgStr);
LOG(("checking address %s", dbgStr.get()));
}
if (mAddresses[i]->ScopeIsUniverse()) {
mIsUp = true;
LOG(("global address found"));
break;
}
}
}
return mIsUp == oldIsUp;
}
NS_IMPL_ISUPPORTS(NetlinkService, nsIRunnable)
NetlinkService::NetlinkService() : mPid(getpid()) {}
NetlinkService::~NetlinkService() {
MOZ_ASSERT(!mThread, "NetlinkService thread shutdown failed");
if (mShutdownPipe[0] != -1) {
EINTR_RETRY(close(mShutdownPipe[0]));
}
if (mShutdownPipe[1] != -1) {
EINTR_RETRY(close(mShutdownPipe[1]));
}
}
void NetlinkService::OnNetlinkMessage(int aNetlinkSocket) {
// The buffer size 4096 is a common page size, which is a recommended limit
// for netlink messages.
char buffer[4096];
struct sockaddr_nl kernel {};
memset(&kernel, 0, sizeof(kernel));
kernel.nl_family = AF_NETLINK;
kernel.nl_groups = 0;
struct iovec io {};
memset(&io, 0, sizeof(io));
io.iov_base = buffer;
io.iov_len = sizeof(buffer);
struct msghdr rtnl_reply {};
memset(&rtnl_reply, 0, sizeof(rtnl_reply));
rtnl_reply.msg_iov = &io;
rtnl_reply.msg_iovlen = 1;
rtnl_reply.msg_name = &kernel;
rtnl_reply.msg_namelen = sizeof(kernel);
ssize_t rc = EINTR_RETRY(recvmsg(aNetlinkSocket, &rtnl_reply, MSG_DONTWAIT));
if (rc < 0) {
return;
}
size_t netlink_bytes = rc;
struct nlmsghdr* nlh = reinterpret_cast<struct nlmsghdr*>(buffer);
for (; NLMSG_OK(nlh, netlink_bytes); nlh = NLMSG_NEXT(nlh, netlink_bytes)) {
// If PID in the message is our PID, then it's a response to our request.
// Otherwise it's a multicast message.
bool isResponse = (pid_t)nlh->nlmsg_pid == mPid;
if (isResponse) {
if (!mOutgoingMessages.Length() || !mOutgoingMessages[0]->IsPending()) {
// There is no enqueued message pending?
LOG((
"Ignoring message seq_id %u, because there is no associated message"
" pending",
nlh->nlmsg_seq));
continue;
}
if (mOutgoingMessages[0]->SeqId() != nlh->nlmsg_seq) {
LOG(("Received unexpected seq_id [received=%u, expected=%u]",
nlh->nlmsg_seq, mOutgoingMessages[0]->SeqId()));
RemovePendingMsg();
continue;
}
}
switch (nlh->nlmsg_type) {
case NLMSG_DONE: /* Message signalling end of dump for responses to
request containing NLM_F_DUMP flag */
LOG(("received NLMSG_DONE"));
if (isResponse) {
RemovePendingMsg();
}
break;
case NLMSG_ERROR:
LOG(("received NLMSG_ERROR"));
if (isResponse) {
if (mOutgoingMessages[0]->MsgType() == NetlinkMsg::kRtMsg) {
OnRouteCheckResult(nullptr);
}
RemovePendingMsg();
}
break;
case RTM_NEWLINK:
case RTM_DELLINK:
MOZ_ASSERT(!isResponse ||
(nlh->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI);
OnLinkMessage(nlh);
break;
case RTM_NEWADDR:
case RTM_DELADDR:
MOZ_ASSERT(!isResponse ||
(nlh->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI);
OnAddrMessage(nlh);
break;
case RTM_NEWROUTE:
case RTM_DELROUTE:
if (isResponse && ((nlh->nlmsg_flags & NLM_F_MULTI) != NLM_F_MULTI)) {
// If it's not multipart message, then it must be response to a route
// check.
MOZ_ASSERT(mOutgoingMessages[0]->MsgType() == NetlinkMsg::kRtMsg);
OnRouteCheckResult(nlh);
RemovePendingMsg();
} else {
OnRouteMessage(nlh);
}
break;
case RTM_NEWNEIGH:
case RTM_DELNEIGH:
MOZ_ASSERT(!isResponse ||
(nlh->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI);
OnNeighborMessage(nlh);
break;
default:
break;
}
}
}
void NetlinkService::OnLinkMessage(struct nlmsghdr* aNlh) {
LOG(("NetlinkService::OnLinkMessage [type=%s]",
aNlh->nlmsg_type == RTM_NEWLINK ? "new" : "del"));
UniquePtr<NetlinkLink> link(new NetlinkLink());
if (!link->Init(aNlh)) {
return;
}
const uint32_t linkIndex = link->GetIndex();
mLinks.WithEntryHandle(linkIndex, [&](auto&& entry) {
nsAutoCString linkName;
link->GetName(linkName);
if (aNlh->nlmsg_type == RTM_NEWLINK) {
if (!entry) {
LOG(("Creating new link [index=%u, name=%s, flags=%u, type=%u]",
linkIndex, linkName.get(), link->GetFlags(), link->GetType()));
entry.Insert(MakeUnique<LinkInfo>(std::move(link)));
} else {
LOG(("Updating link [index=%u, name=%s, flags=%u, type=%u]", linkIndex,
linkName.get(), link->GetFlags(), link->GetType()));
auto* linkInfo = entry->get();
// Check whether administrative state has changed.
if (linkInfo->mLink->GetFlags() & IFF_UP &&
!(link->GetFlags() & IFF_UP)) {
LOG((" link went down"));
// If the link went down, remove all routes and neighbors, but keep
// addresses.
linkInfo->mDefaultRoutes.Clear();
linkInfo->mNeighbors.Clear();
}
linkInfo->mLink = std::move(link);
linkInfo->UpdateStatus();
}
} else {
if (!entry) {
// This can happen during startup
LOG(("Link info doesn't exist [index=%u, name=%s]", linkIndex,
linkName.get()));
} else {
LOG(("Removing link [index=%u, name=%s]", linkIndex, linkName.get()));
entry.Remove();
}
}
});
}
void NetlinkService::OnAddrMessage(struct nlmsghdr* aNlh) {
LOG(("NetlinkService::OnAddrMessage [type=%s]",
aNlh->nlmsg_type == RTM_NEWADDR ? "new" : "del"));
UniquePtr<NetlinkAddress> address(new NetlinkAddress());
if (!address->Init(aNlh)) {
return;
}
uint32_t ifIdx = address->GetIndex();
nsAutoCString addrStr;
GetAddrStr(address->GetAddrPtr(), address->Family(), addrStr);
LinkInfo* linkInfo = nullptr;
mLinks.Get(ifIdx, &linkInfo);
if (!linkInfo) {
// This can happen during startup
LOG(("Cannot find link info [ifIdx=%u, addr=%s/%u", ifIdx, addrStr.get(),
address->GetPrefixLen()));
return;
}
// There might be already an equal address in the array even in case of
// RTM_NEWADDR message, e.g. when lifetime of IPv6 address is renewed. Equal
// in this case means that IP and prefix is the same but some attributes
// might be different. Remove existing equal address in case of RTM_DELADDR
// as well as RTM_NEWADDR message and add a new one in the latter case.
for (uint32_t i = 0; i < linkInfo->mAddresses.Length(); ++i) {
if (aNlh->nlmsg_type == RTM_NEWADDR &&
linkInfo->mAddresses[i]->MsgEquals(*address)) {
// If the new address is exactly the same, there is nothing to do.
LOG(("Exactly the same address already exists [ifIdx=%u, addr=%s/%u",
ifIdx, addrStr.get(), address->GetPrefixLen()));
return;
}
if (linkInfo->mAddresses[i]->Equals(*address)) {
LOG(("Removing address [ifIdx=%u, addr=%s/%u]", ifIdx, addrStr.get(),
address->GetPrefixLen()));
linkInfo->mAddresses.RemoveElementAt(i);
break;
}
}
if (aNlh->nlmsg_type == RTM_NEWADDR) {
LOG(("Adding address [ifIdx=%u, addr=%s/%u]", ifIdx, addrStr.get(),
address->GetPrefixLen()));
linkInfo->mAddresses.AppendElement(std::move(address));
} else {
// Remove all routes associated with this address
for (uint32_t i = linkInfo->mDefaultRoutes.Length(); i-- > 0;) {
MOZ_ASSERT(linkInfo->mDefaultRoutes[i]->GetGWAddrPtr(),
"Stored routes must have gateway!");
if (linkInfo->mDefaultRoutes[i]->Family() == address->Family() &&
address->ContainsAddr(linkInfo->mDefaultRoutes[i]->GetGWAddrPtr())) {
if (LOG_ENABLED()) {
nsAutoCString routeDbgStr;
linkInfo->mDefaultRoutes[i]->GetAsString(routeDbgStr);
LOG(("Removing default route: %s", routeDbgStr.get()));
}
linkInfo->mDefaultRoutes.RemoveElementAt(i);
}
}
// Remove all neighbors associated with this address
for (auto iter = linkInfo->mNeighbors.Iter(); !iter.Done(); iter.Next()) {
NetlinkNeighbor* neigh = iter.UserData();
if (neigh->Family() == address->Family() &&
address->ContainsAddr(neigh->GetAddrPtr())) {
if (LOG_ENABLED()) {
nsAutoCString neighDbgStr;
neigh->GetAsString(neighDbgStr);
LOG(("Removing neighbor %s", neighDbgStr.get()));
}
iter.Remove();
}
}
}
// Address change on the interface can change its status
linkInfo->UpdateStatus();
// Don't treat address changes during initial scan as a network change
if (mInitialScanFinished) {
// Send network event change regardless of whether the ID has changed or
// not
mSendNetworkChangeEvent = true;
TriggerNetworkIDCalculation();
}
}
void NetlinkService::OnRouteMessage(struct nlmsghdr* aNlh) {
LOG(("NetlinkService::OnRouteMessage [type=%s]",
aNlh->nlmsg_type == RTM_NEWROUTE ? "new" : "del"));
UniquePtr<NetlinkRoute> route(new NetlinkRoute());
if (!route->Init(aNlh)) {
return;
}
if (!route->IsUnicast() || !route->ScopeIsUniverse()) {
// Use only unicast routes
if (LOG_ENABLED()) {
nsAutoCString routeDbgStr;
route->GetAsString(routeDbgStr);
LOG(("Not an unicast global route: %s", routeDbgStr.get()));
}
return;
}
// Adding/removing any unicast route might change network ID
TriggerNetworkIDCalculation();
if (!route->IsDefault()) {
// Store only default routes
if (LOG_ENABLED()) {
nsAutoCString routeDbgStr;
route->GetAsString(routeDbgStr);
LOG(("Not a default route: %s", routeDbgStr.get()));
}
return;
}
if (!route->HasOif()) {
if (LOG_ENABLED()) {
nsAutoCString routeDbgStr;
route->GetAsString(routeDbgStr);
LOG(("There is no output interface in route: %s", routeDbgStr.get()));
}
return;
}
if (!route->GetGWAddrPtr()) {
// We won't use the route if there is no gateway, so don't store it
if (LOG_ENABLED()) {
nsAutoCString routeDbgStr;
route->GetAsString(routeDbgStr);
LOG(("There is no gateway in route: %s", routeDbgStr.get()));
}
return;
}
if (route->Family() == AF_INET6 &&
net::utils::ipv6_scope((const unsigned char*)route->GetGWAddrPtr()) !=
IPV6_SCOPE_GLOBAL) {
if (LOG_ENABLED()) {
nsAutoCString routeDbgStr;
route->GetAsString(routeDbgStr);
LOG(("Scope of GW isn't global: %s", routeDbgStr.get()));
}
return;
}
LinkInfo* linkInfo = nullptr;
mLinks.Get(route->Oif(), &linkInfo);
if (!linkInfo) {
// This can happen during startup
if (LOG_ENABLED()) {
nsAutoCString routeDbgStr;
route->GetAsString(routeDbgStr);
LOG(("Cannot find link info for route: %s", routeDbgStr.get()));
}
return;
}
for (uint32_t i = 0; i < linkInfo->mDefaultRoutes.Length(); ++i) {
if (linkInfo->mDefaultRoutes[i]->Equals(*route)) {
// We shouldn't find equal route when adding a new one, but just in case
// it can happen remove the old one to avoid duplicities.
if (LOG_ENABLED()) {
nsAutoCString routeDbgStr;
route->GetAsString(routeDbgStr);
LOG(("Removing default route: %s", routeDbgStr.get()));
}
linkInfo->mDefaultRoutes.RemoveElementAt(i);
break;
}
}
if (aNlh->nlmsg_type == RTM_NEWROUTE) {
if (LOG_ENABLED()) {
nsAutoCString routeDbgStr;
route->GetAsString(routeDbgStr);
LOG(("Adding default route: %s", routeDbgStr.get()));
}
linkInfo->mDefaultRoutes.AppendElement(std::move(route));
}
}
void NetlinkService::OnNeighborMessage(struct nlmsghdr* aNlh) {
LOG(("NetlinkService::OnNeighborMessage [type=%s]",
aNlh->nlmsg_type == RTM_NEWNEIGH ? "new" : "del"));
UniquePtr<NetlinkNeighbor> neigh(new NetlinkNeighbor());
if (!neigh->Init(aNlh)) {
return;
}
LinkInfo* linkInfo = nullptr;
mLinks.Get(neigh->GetIndex(), &linkInfo);
if (!linkInfo) {
// This can happen during startup
if (LOG_ENABLED()) {
nsAutoCString neighDbgStr;
neigh->GetAsString(neighDbgStr);
LOG(("Cannot find link info for neighbor: %s", neighDbgStr.get()));
}
return;
}
if (!linkInfo->mLink->IsTypeEther()) {
if (LOG_ENABLED()) {
nsAutoCString neighDbgStr;
neigh->GetAsString(neighDbgStr);
LOG(("Ignoring message on non-ethernet link: %s", neighDbgStr.get()));
}
return;
}
nsAutoCString key;
GetAddrStr(neigh->GetAddrPtr(), neigh->Family(), key);
if (aNlh->nlmsg_type == RTM_NEWNEIGH) {
if (!mRecalculateNetworkId && neigh->HasMAC()) {
NetlinkNeighbor* oldNeigh = nullptr;
linkInfo->mNeighbors.Get(key, &oldNeigh);
if (!oldNeigh || !oldNeigh->HasMAC()) {
// The MAC address was added, if it's a host from some of the saved
// routing tables we should recalculate network ID
for (uint32_t i = 0; i < linkInfo->mDefaultRoutes.Length(); ++i) {
if (linkInfo->mDefaultRoutes[i]->GatewayEquals(*neigh)) {
TriggerNetworkIDCalculation();
break;
}
}
if ((mIPv4RouteCheckResult &&
mIPv4RouteCheckResult->GatewayEquals(*neigh)) ||
(mIPv6RouteCheckResult &&
mIPv6RouteCheckResult->GatewayEquals(*neigh))) {
TriggerNetworkIDCalculation();
}
}
}
if (LOG_ENABLED()) {
nsAutoCString neighDbgStr;
neigh->GetAsString(neighDbgStr);
LOG(("Adding neighbor: %s", neighDbgStr.get()));
}
linkInfo->mNeighbors.InsertOrUpdate(key, std::move(neigh));
} else {
if (LOG_ENABLED()) {
nsAutoCString neighDbgStr;
neigh->GetAsString(neighDbgStr);
LOG(("Removing neighbor %s", neighDbgStr.get()));
}
linkInfo->mNeighbors.Remove(key);
}
}
void NetlinkService::OnRouteCheckResult(struct nlmsghdr* aNlh) {
LOG(("NetlinkService::OnRouteCheckResult"));
UniquePtr<NetlinkRoute> route;
if (aNlh) {
route = MakeUnique<NetlinkRoute>();
if (!route->Init(aNlh)) {
route = nullptr;
} else {
if (!route->IsUnicast() || !route->ScopeIsUniverse()) {
if (LOG_ENABLED()) {
nsAutoCString routeDbgStr;
route->GetAsString(routeDbgStr);
LOG(("Not an unicast global route: %s", routeDbgStr.get()));
}
route = nullptr;
} else if (!route->HasOif()) {
if (LOG_ENABLED()) {
nsAutoCString routeDbgStr;
route->GetAsString(routeDbgStr);
LOG(("There is no output interface in route: %s", routeDbgStr.get()));
}
route = nullptr;
}
}
}
if (LOG_ENABLED()) {
if (route) {
nsAutoCString routeDbgStr;
route->GetAsString(routeDbgStr);
LOG(("Storing route: %s", routeDbgStr.get()));
} else {
LOG(("Clearing result for the check"));
}
}
if (mOutgoingMessages[0]->Family() == AF_INET) {
mIPv4RouteCheckResult = std::move(route);
} else {
mIPv6RouteCheckResult = std::move(route);
}
}
void NetlinkService::EnqueueGenMsg(uint16_t aMsgType, uint8_t aFamily) {
NetlinkGenMsg* msg = new NetlinkGenMsg(aMsgType, aFamily, ++mMsgId);
mOutgoingMessages.AppendElement(msg);
}
void NetlinkService::EnqueueRtMsg(uint8_t aFamily, void* aAddress) {
NetlinkRtMsg* msg = new NetlinkRtMsg(aFamily, aAddress, ++mMsgId);
mOutgoingMessages.AppendElement(msg);
}
void NetlinkService::RemovePendingMsg() {
LOG(("NetlinkService::RemovePendingMsg [seqId=%u]",
mOutgoingMessages[0]->SeqId()));
MOZ_ASSERT(mOutgoingMessages[0]->IsPending());
DebugOnly<bool> isRtMessage =
(mOutgoingMessages[0]->MsgType() == NetlinkMsg::kRtMsg);
mOutgoingMessages.RemoveElementAt(0);
if (!mOutgoingMessages.Length()) {
if (!mInitialScanFinished) {
// Now we've received all initial data from the kernel. Perform a link
// check and trigger network ID calculation even if it wasn't triggered
// by the incoming messages.
mInitialScanFinished = true;
TriggerNetworkIDCalculation();
// Link status should be known by now.
RefPtr<NetlinkServiceListener> listener;
{
MutexAutoLock lock(mMutex);
listener = mListener;
}
if (listener) {
listener->OnLinkStatusKnown();
}
} else {
// We've received last response for route check, calculate ID now
MOZ_ASSERT(isRtMessage);
CalculateNetworkID();
}
}
}
NS_IMETHODIMP
NetlinkService::Run() {
int netlinkSocket = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (netlinkSocket < 0) {
return NS_ERROR_FAILURE;
}
struct sockaddr_nl addr {};
memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_LINK |
RTMGRP_NEIGH | RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE;
if (bind(netlinkSocket, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
// failure!
EINTR_RETRY(close(netlinkSocket));
return NS_ERROR_FAILURE;
}
struct pollfd fds[2];
fds[0].fd = mShutdownPipe[0];
fds[0].events = POLLIN;
fds[0].revents = 0;
fds[1].fd = netlinkSocket;
fds[1].events = POLLIN;
fds[1].revents = 0;
// send all requests to get initial network information
EnqueueGenMsg(RTM_GETLINK, AF_PACKET);
EnqueueGenMsg(RTM_GETNEIGH, AF_INET);
EnqueueGenMsg(RTM_GETNEIGH, AF_INET6);
EnqueueGenMsg(RTM_GETADDR, AF_PACKET);
EnqueueGenMsg(RTM_GETROUTE, AF_PACKET);
nsresult rv = NS_OK;
bool shutdown = false;
while (!shutdown) {
if (mOutgoingMessages.Length() && !mOutgoingMessages[0]->IsPending()) {
if (!mOutgoingMessages[0]->Send(netlinkSocket)) {
LOG(("Failed to send netlink message"));
mOutgoingMessages.RemoveElementAt(0);
// try to send another message if available before polling
continue;
}
}
int rc = EINTR_RETRY(poll(fds, 2, GetPollWait()));
if (rc > 0) {
if (fds[0].revents & POLLIN) {
// shutdown, abort the loop!
LOG(("thread shutdown received, dying...\n"));
shutdown = true;
} else if (fds[1].revents & POLLIN) {
LOG(("netlink message received, handling it...\n"));
OnNetlinkMessage(netlinkSocket);
}
} else if (rc < 0) {
rv = NS_ERROR_FAILURE;
break;
}
}
EINTR_RETRY(close(netlinkSocket));
return rv;
}
nsresult NetlinkService::Init(NetlinkServiceListener* aListener) {
nsresult rv;
mListener = aListener;
if (inet_pton(AF_INET, ROUTE_CHECK_IPV4, &mRouteCheckIPv4) != 1) {
LOG(("Cannot parse address " ROUTE_CHECK_IPV4));
MOZ_DIAGNOSTIC_ASSERT(false, "Cannot parse address " ROUTE_CHECK_IPV4);
return NS_ERROR_UNEXPECTED;
}
if (inet_pton(AF_INET6, ROUTE_CHECK_IPV6, &mRouteCheckIPv6) != 1) {
LOG(("Cannot parse address " ROUTE_CHECK_IPV6));
MOZ_DIAGNOSTIC_ASSERT(false, "Cannot parse address " ROUTE_CHECK_IPV6);
return NS_ERROR_UNEXPECTED;
}
if (pipe(mShutdownPipe) == -1) {
LOG(("Cannot create pipe"));
return NS_ERROR_FAILURE;
}
rv = NS_NewNamedThread("Netlink Monitor", getter_AddRefs(mThread), this);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult NetlinkService::Shutdown() {
LOG(("write() to signal thread shutdown\n"));
{
MutexAutoLock lock(mMutex);
mListener = nullptr;
}
// awake the thread to make it terminate
ssize_t rc = EINTR_RETRY(write(mShutdownPipe[1], "1", 1));
LOG(("write() returned %d, errno == %d\n", (int)rc, errno));
nsresult rv = mThread->Shutdown();
// Have to break the cycle here, otherwise NetlinkService holds
// onto the thread and the thread holds onto the NetlinkService
// via its mRunnable
mThread = nullptr;
return rv;
}
/*
* A network event that might change network ID has been registered. Delay
* network ID calculation and sending of the event in case it changed for
* a while. Absorbing potential subsequent events increases chance of successful
* network ID calculation (e.g. MAC address of the router might be discovered in
* the meantime)
*/
void NetlinkService::TriggerNetworkIDCalculation() {
LOG(("NetlinkService::TriggerNetworkIDCalculation"));
if (mRecalculateNetworkId) {
return;
}
mRecalculateNetworkId = true;
mTriggerTime = TimeStamp::Now();
}
int NetlinkService::GetPollWait() {
if (!mRecalculateNetworkId) {
return -1;
}
if (mOutgoingMessages.Length()) {
MOZ_ASSERT(mOutgoingMessages[0]->IsPending());
// Message is pending, we don't have to set timeout because we'll receive
// reply from kernel ASAP
return -1;
}
MOZ_ASSERT(mInitialScanFinished);
double period = (TimeStamp::Now() - mTriggerTime).ToMilliseconds();
if (period >= kNetworkChangeCoalescingPeriod) {
// Coalescing time has elapsed, send route check messages to find out
// where IPv4 and IPv6 traffic is routed and calculate network ID after
// the response is received.
EnqueueRtMsg(AF_INET, &mRouteCheckIPv4);
EnqueueRtMsg(AF_INET6, &mRouteCheckIPv6);
// Return 0 to make sure we start sending enqueued messages immediately
return 0;
}
return static_cast<int>(kNetworkChangeCoalescingPeriod - period);
}
class NeighborComparator {
public:
bool Equals(const NetlinkNeighbor* a, const NetlinkNeighbor* b) const {
return (memcmp(a->GetMACPtr(), b->GetMACPtr(), ETH_ALEN) == 0);
}
bool LessThan(const NetlinkNeighbor* a, const NetlinkNeighbor* b) const {
return (memcmp(a->GetMACPtr(), b->GetMACPtr(), ETH_ALEN) < 0);
}
};
class LinknameComparator {
public:
bool LessThan(const nsCString& aA, const nsCString& aB) const {
return aA < aB;
}
bool Equals(const nsCString& aA, const nsCString& aB) const {
return aA == aB;
}
};
// Get Gateway Neighbours for a particular Address Family, for which we know MAC
// address
void NetlinkService::GetGWNeighboursForFamily(
uint8_t aFamily, nsTArray<NetlinkNeighbor*>& aGwNeighbors) {
LOG(("NetlinkService::GetGWNeighboursForFamily"));
// Check only routes on links that are up
for (const auto& linkInfo : mLinks.Values()) {
nsAutoCString linkName;
linkInfo->mLink->GetName(linkName);
if (!linkInfo->mIsUp) {
LOG((" %s is down", linkName.get()));
continue;
}
if (!linkInfo->mLink->IsTypeEther()) {
LOG((" %s is not ethernet link", linkName.get()));
continue;
}
LOG((" checking link %s", linkName.get()));
// Check all default routes and try to get MAC of the gateway
for (uint32_t i = 0; i < linkInfo->mDefaultRoutes.Length(); ++i) {
if (LOG_ENABLED()) {
nsAutoCString routeDbgStr;
linkInfo->mDefaultRoutes[i]->GetAsString(routeDbgStr);
LOG(("Checking default route: %s", routeDbgStr.get()));
}
if (linkInfo->mDefaultRoutes[i]->Family() != aFamily) {
LOG((" skipping due to different family"));
continue;
}
MOZ_ASSERT(linkInfo->mDefaultRoutes[i]->GetGWAddrPtr(),
"Stored routes must have gateway!");
nsAutoCString neighKey;
GetAddrStr(linkInfo->mDefaultRoutes[i]->GetGWAddrPtr(), aFamily,
neighKey);
NetlinkNeighbor* neigh = nullptr;
if (!linkInfo->mNeighbors.Get(neighKey, &neigh)) {
LOG(("Neighbor %s not found in hashtable.", neighKey.get()));
continue;
}
if (!neigh->HasMAC()) {
// We don't know MAC address
LOG(("We have no MAC for neighbor %s.", neighKey.get()));
continue;
}
if (aGwNeighbors.IndexOf(neigh, 0, NeighborComparator()) !=
nsTArray<NetlinkNeighbor*>::NoIndex) {
// avoid host duplicities
LOG(("MAC of neighbor %s is already selected for hashing.",
neighKey.get()));
continue;
}
LOG(("MAC of neighbor %s will be used for network ID.", neighKey.get()));
aGwNeighbors.AppendElement(neigh);
}
}
}
bool NetlinkService::CalculateIDForEthernetLink(uint8_t aFamily,
NetlinkRoute* aRouteCheckResult,
uint32_t aRouteCheckIfIdx,
LinkInfo* aRouteCheckLinkInfo,
SHA1Sum* aSHA1) {
LOG(("NetlinkService::CalculateIDForEthernetLink"));
bool retval = false;
const in_common_addr* addrPtr = aRouteCheckResult->GetGWAddrPtr();
if (!addrPtr) {
// This shouldn't normally happen, missing next hop in case of ethernet
// device would mean that the checked host is on the same network.
if (LOG_ENABLED()) {
nsAutoCString routeDbgStr;
aRouteCheckResult->GetAsString(routeDbgStr);
LOG(("There is no next hop in route: %s", routeDbgStr.get()));
}
return retval;
}
// If we know MAC address of the next hop for mRouteCheckIPv4/6 host, hash
// it even if it's MAC of some of the default routes we've checked above.
// This ensures that if we have 2 different default routes and next hop for
// mRouteCheckIPv4/6 changes from one default route to the other, we'll
// detect it as a network change.
nsAutoCString neighKey;
GetAddrStr(addrPtr, aFamily, neighKey);
LOG(("Next hop for the checked host is %s on ifIdx %u.", neighKey.get(),
aRouteCheckIfIdx));
NetlinkNeighbor* neigh = nullptr;
if (!aRouteCheckLinkInfo->mNeighbors.Get(neighKey, &neigh)) {
LOG(("Neighbor %s not found in hashtable.", neighKey.get()));
return retval;
}
if (!neigh->HasMAC()) {
LOG(("We have no MAC for neighbor %s.", neighKey.get()));
return retval;
}
if (LOG_ENABLED()) {
nsAutoCString neighDbgStr;
neigh->GetAsString(neighDbgStr);
LOG(("Hashing MAC address of neighbor: %s", neighDbgStr.get()));
}
aSHA1->update(neigh->GetMACPtr(), ETH_ALEN);
retval = true;
return retval;
}
bool NetlinkService::CalculateIDForNonEthernetLink(
uint8_t aFamily, NetlinkRoute* aRouteCheckResult,
nsTArray<nsCString>& aLinkNamesToHash, uint32_t aRouteCheckIfIdx,
LinkInfo* aRouteCheckLinkInfo, SHA1Sum* aSHA1) {
LOG(("NetlinkService::CalculateIDForNonEthernetLink"));
bool retval = false;
const in_common_addr* addrPtr = aRouteCheckResult->GetGWAddrPtr();
nsAutoCString routeCheckLinkName;
aRouteCheckLinkInfo->mLink->GetName(routeCheckLinkName);
if (addrPtr) {
// The route contains next hop. Hash the name of the interface (e.g.
// "tun1") and the IP address of the next hop.
nsAutoCString addrStr;
GetAddrStr(addrPtr, aFamily, addrStr);
size_t addrSize =
(aFamily == AF_INET) ? sizeof(addrPtr->addr4) : sizeof(addrPtr->addr6);
LOG(("Hashing link name %s", routeCheckLinkName.get()));
aSHA1->update(routeCheckLinkName.get(), routeCheckLinkName.Length());
// Don't hash GW address if it's rmnet_data device.
if (!aLinkNamesToHash.Contains(routeCheckLinkName)) {
LOG(("Hashing GW address %s", addrStr.get()));
aSHA1->update(addrPtr, addrSize);
}
retval = true;
} else {
// The traffic is routed directly via an interface. Hash the name of the
// interface and the network address. Using host address would cause that
// network ID would be different every time we get a different IP address
// in this network/VPN.
bool hasSrcAddr = aRouteCheckResult->HasPrefSrcAddr();
if (!hasSrcAddr) {
LOG(("There is no preferred source address."));
}
NetlinkAddress* linkAddress = nullptr;
// Find network address of the interface matching the source address. In
// theory there could be multiple addresses with different prefix length.
// Get the one with smallest prefix length.
for (uint32_t i = 0; i < aRouteCheckLinkInfo->mAddresses.Length(); ++i) {
if (!hasSrcAddr) {
// there is no preferred src, match just the family
if (aRouteCheckLinkInfo->mAddresses[i]->Family() != aFamily) {
continue;
}
} else if (!aRouteCheckResult->PrefSrcAddrEquals(
*aRouteCheckLinkInfo->mAddresses[i])) {
continue;
}
if (!linkAddress ||
linkAddress->GetPrefixLen() >
aRouteCheckLinkInfo->mAddresses[i]->GetPrefixLen()) {
// We have no address yet or this one has smaller prefix length,
// use it.
linkAddress = aRouteCheckLinkInfo->mAddresses[i].get();
}
}
if (!linkAddress) {
// There is no address in our array?
if (LOG_ENABLED()) {
nsAutoCString dbgStr;
aRouteCheckResult->GetAsString(dbgStr);
LOG(("No address found for preferred source address in route: %s",
dbgStr.get()));
}
return retval;
}
in_common_addr prefix;
int32_t prefixSize = (aFamily == AF_INET) ? (int32_t)sizeof(prefix.addr4)
: (int32_t)sizeof(prefix.addr6);
memcpy(&prefix, linkAddress->GetAddrPtr(), prefixSize);
uint8_t maskit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe};
int32_t bits = linkAddress->GetPrefixLen();
if (bits > prefixSize * 8) {
MOZ_ASSERT(false, "Unexpected prefix length!");
LOG(("Unexpected prefix length %d, maximum for this family is %d", bits,
prefixSize * 8));
return retval;
}
for (int32_t i = 0; i < prefixSize; i++) {
uint8_t mask = (bits >= 8) ? 0xff : maskit[bits];
((unsigned char*)&prefix)[i] &= mask;
bits -= 8;
if (bits <= 0) {
bits = 0;
}
}
nsAutoCString addrStr;
GetAddrStr(&prefix, aFamily, addrStr);
LOG(("Hashing link name %s and network address %s/%u",
routeCheckLinkName.get(), addrStr.get(), linkAddress->GetPrefixLen()));
aSHA1->update(routeCheckLinkName.get(), routeCheckLinkName.Length());
aSHA1->update(&prefix, prefixSize);
bits = linkAddress->GetPrefixLen();
aSHA1->update(&bits, sizeof(bits));
retval = true;
}
return retval;
}
bool NetlinkService::CalculateIDForFamily(uint8_t aFamily, SHA1Sum* aSHA1) {
LOG(("NetlinkService::CalculateIDForFamily [family=%s]",
aFamily == AF_INET ? "AF_INET" : "AF_INET6"));
bool retval = false;
if (!mLinkUp) {
// Skip ID calculation if the link is down, we have no ID...
LOG(("Link is down, skipping ID calculation."));
return retval;
}
NetlinkRoute* routeCheckResult;
if (aFamily == AF_INET) {
routeCheckResult = mIPv4RouteCheckResult.get();
} else {
routeCheckResult = mIPv6RouteCheckResult.get();
}
// All GW neighbors for which we know MAC address. We'll probably have at
// most only one, but in case we have more default routes, we hash them all
// even though the routing rules sends the traffic only via one of them.
// If the system switches between them, we'll detect the change with
// mIPv4/6RouteCheckResult.
nsTArray<NetlinkNeighbor*> gwNeighbors;
GetGWNeighboursForFamily(aFamily, gwNeighbors);
// Sort them so we always have the same network ID on the same network
gwNeighbors.Sort(NeighborComparator());
for (uint32_t i = 0; i < gwNeighbors.Length(); ++i) {
if (LOG_ENABLED()) {
nsAutoCString neighDbgStr;
gwNeighbors[i]->GetAsString(neighDbgStr);
LOG(("Hashing MAC address of neighbor: %s", neighDbgStr.get()));
}
aSHA1->update(gwNeighbors[i]->GetMACPtr(), ETH_ALEN);
retval = true;
}
nsTArray<nsCString> linkNamesToHash;
if (!gwNeighbors.Length()) {
// If we don't know MAC of the gateway and link is up, it's probably not
// an ethernet link. If the name of the link begins with "rmnet" then
// the mobile data is used. We cannot easily differentiate when user
// switches sim cards so let's treat mobile data as a single network. We'll
// simply hash link name. If the traffic is redirected via some VPN, it'll
// still be detected below.
// TODO: maybe we could get operator name via AndroidBridge
for (const auto& linkInfo : mLinks.Values()) {
if (linkInfo->mIsUp) {
nsAutoCString linkName;
linkInfo->mLink->GetName(linkName);
if (StringBeginsWith(linkName, "rmnet"_ns)) {
// Check whether there is some non-local address associated with this
// link.
for (uint32_t i = 0; i < linkInfo->mAddresses.Length(); ++i) {
if (linkInfo->mAddresses[i]->Family() == aFamily &&
linkInfo->mAddresses[i]->ScopeIsUniverse()) {
linkNamesToHash.AppendElement(linkName);
break;
}
}
}
}
}
// Sort link names to ensure consistent results
linkNamesToHash.Sort(LinknameComparator());
for (uint32_t i = 0; i < linkNamesToHash.Length(); ++i) {
LOG(("Hashing name of adapter: %s", linkNamesToHash[i].get()));
aSHA1->update(linkNamesToHash[i].get(), linkNamesToHash[i].Length());
retval = true;
}
}
if (!routeCheckResult) {
// If we don't have result for route check to mRouteCheckIPv4/6 host, the
// network is unreachable and there is no more to do.
LOG(("There is no route check result."));
return retval;
}
LinkInfo* routeCheckLinkInfo = nullptr;
uint32_t routeCheckIfIdx = routeCheckResult->Oif();
if (!mLinks.Get(routeCheckIfIdx, &routeCheckLinkInfo)) {
LOG(("Cannot find link with index %u ??", routeCheckIfIdx));
return retval;
}
if (routeCheckLinkInfo->mLink->IsTypeEther()) {
// The traffic is routed through an ethernet device.
retval |= CalculateIDForEthernetLink(
aFamily, routeCheckResult, routeCheckIfIdx, routeCheckLinkInfo, aSHA1);
} else {
// The traffic is routed through a non-ethernet device.
retval |= CalculateIDForNonEthernetLink(aFamily, routeCheckResult,
linkNamesToHash, routeCheckIfIdx,
routeCheckLinkInfo, aSHA1);
}
return retval;
}
void NetlinkService::ExtractDNSProperties() {
MOZ_ASSERT(!NS_IsMainThread(), "Must not be called on the main thread");
nsTArray<nsCString> suffixList;
nsTArray<NetAddr> resolvers;
#if defined(HAVE_RES_NINIT)
[&]() {
struct __res_state res {};
int ret = res_ninit(&res);
if (ret != 0) {
LOG(("Call to res_ninit failed: %d", ret));
return;
}
// Get DNS suffixes
for (int i = 0; i < MAXDNSRCH; i++) {
if (!res.dnsrch[i]) {
break;
}
suffixList.AppendElement(nsCString(res.dnsrch[i]));
}
// Get DNS resolvers
// Chromium's dns_config_service_posix.cc is the origin of this code
// Initially, glibc stores IPv6 in |_ext.nsaddrs| and IPv4 in |nsaddr_list|.
// In res_send.c:res_nsend, it merges |nsaddr_list| into |nsaddrs|,
// but we have to combine the two arrays ourselves.
for (int i = 0; i < res.nscount; ++i) {
const struct sockaddr* addr = nullptr;
size_t addr_len = 0;
if (res.nsaddr_list[i].sin_family) { // The indicator used by res_nsend.
addr = reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]);
addr_len = sizeof res.nsaddr_list[i];
} else if (res._u._ext.nsaddrs[i]) {
addr = reinterpret_cast<const struct sockaddr*>(res._u._ext.nsaddrs[i]);
addr_len = sizeof *res._u._ext.nsaddrs[i];
} else {
LOG(("Bad ext struct"));
return;
}
const socklen_t kSockaddrInSize = sizeof(struct sockaddr_in);
const socklen_t kSockaddrIn6Size = sizeof(struct sockaddr_in6);
if ((addr->sa_family == AF_INET && addr_len < kSockaddrInSize) ||
(addr->sa_family == AF_INET6 && addr_len < kSockaddrIn6Size)) {
LOG(("Bad address size"));
return;
}
NetAddr ip;
if (addr->sa_family == AF_INET) {
const struct sockaddr_in* sin = (const struct sockaddr_in*)addr;
ip.inet.family = AF_INET;
ip.inet.ip = sin->sin_addr.s_addr;
ip.inet.port = sin->sin_port;
} else if (addr->sa_family == AF_INET6) {
const struct sockaddr_in6* sin6 = (const struct sockaddr_in6*)addr;
ip.inet6.family = AF_INET6;
memcpy(&ip.inet6.ip.u8, &sin6->sin6_addr, sizeof(ip.inet6.ip.u8));
ip.inet6.port = sin6->sin6_port;
} else {
MOZ_ASSERT_UNREACHABLE("Unexpected sa_family");
return;
}
resolvers.AppendElement(ip);
}
res_nclose(&res);
}();
#endif
RefPtr<NetlinkServiceListener> listener;
{
MutexAutoLock lock(mMutex);
listener = mListener;
mDNSSuffixList = std::move(suffixList);
mDNSResolvers = std::move(resolvers);
}
if (listener) {
listener->OnDnsSuffixListUpdated();
}
}
void NetlinkService::UpdateLinkStatus() {
LOG(("NetlinkService::UpdateLinkStatus"));
MOZ_ASSERT(!mRecalculateNetworkId);
MOZ_ASSERT(mInitialScanFinished);
// Link is up when we have a route for ROUTE_CHECK_IPV4 or ROUTE_CHECK_IPV6
bool newLinkUp = mIPv4RouteCheckResult || mIPv6RouteCheckResult;
if (mLinkUp == newLinkUp) {
LOG(("Link status hasn't changed [linkUp=%d]", mLinkUp));
} else {
LOG(("Link status has changed [linkUp=%d]", newLinkUp));
RefPtr<NetlinkServiceListener> listener;
{
MutexAutoLock lock(mMutex);
listener = mListener;
mLinkUp = newLinkUp;
}
if (mLinkUp) {
if (listener) {
listener->OnLinkUp();
}
} else {
if (listener) {
listener->OnLinkDown();
}
}
}
}
// Figure out the "network identification".
void NetlinkService::CalculateNetworkID() {
LOG(("NetlinkService::CalculateNetworkID"));
MOZ_ASSERT(!NS_IsMainThread(), "Must not be called on the main thread");
MOZ_ASSERT(mRecalculateNetworkId);
mRecalculateNetworkId = false;
SHA1Sum sha1;
UpdateLinkStatus();
ExtractDNSProperties();
bool idChanged = false;
bool found4 = CalculateIDForFamily(AF_INET, &sha1);
bool found6 = CalculateIDForFamily(AF_INET6, &sha1);
if (found4 || found6) {
// This 'addition' could potentially be a fixed number from the
// profile or something.
nsAutoCString addition("local-rubbish");
nsAutoCString output;
sha1.update(addition.get(), addition.Length());
uint8_t digest[SHA1Sum::kHashSize];
sha1.finish(digest);
nsAutoCString newString(reinterpret_cast<char*>(digest),
SHA1Sum::kHashSize);
nsresult rv = Base64Encode(newString, output);
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
LOG(("networkid: id %s\n", output.get()));
MutexAutoLock lock(mMutex);
if (mNetworkId != output) {
// new id
if (found4 && !found6) {
Telemetry::Accumulate(Telemetry::NETWORK_ID2, 1); // IPv4 only
} else if (!found4 && found6) {
Telemetry::Accumulate(Telemetry::NETWORK_ID2, 3); // IPv6 only
} else {
Telemetry::Accumulate(Telemetry::NETWORK_ID2, 4); // Both!
}
mNetworkId = output;
idChanged = true;
} else {
// same id
LOG(("Same network id"));
Telemetry::Accumulate(Telemetry::NETWORK_ID2, 2);
}
} else {
// no id
LOG(("No network id"));
MutexAutoLock lock(mMutex);
if (!mNetworkId.IsEmpty()) {
mNetworkId.Truncate();
idChanged = true;
Telemetry::Accumulate(Telemetry::NETWORK_ID2, 0);
}
}
// If this is first time we calculate network ID, don't report it as a network
// change. We've started with an empty ID and we've just calculated the
// correct ID. The network hasn't really changed.
static bool initialIDCalculation = true;
RefPtr<NetlinkServiceListener> listener;
{
MutexAutoLock lock(mMutex);
listener = mListener;
}
if (!initialIDCalculation && idChanged && listener) {
listener->OnNetworkIDChanged();
mSendNetworkChangeEvent = true;
}
if (mSendNetworkChangeEvent && listener) {
listener->OnNetworkChanged();
}
initialIDCalculation = false;
mSendNetworkChangeEvent = false;
}
void NetlinkService::GetNetworkID(nsACString& aNetworkID) {
MutexAutoLock lock(mMutex);
aNetworkID = mNetworkId;
}
nsresult NetlinkService::GetDnsSuffixList(nsTArray<nsCString>& aDnsSuffixList) {
#if defined(HAVE_RES_NINIT)
MutexAutoLock lock(mMutex);
aDnsSuffixList = mDNSSuffixList.Clone();
return NS_OK;
#else
return NS_ERROR_NOT_IMPLEMENTED;
#endif
}
nsresult NetlinkService::GetResolvers(nsTArray<NetAddr>& aResolvers) {
#if defined(HAVE_RES_NINIT)
MutexAutoLock lock(mMutex);
aResolvers = mDNSResolvers.Clone();
return NS_OK;
#else
return NS_ERROR_NOT_IMPLEMENTED;
#endif
}
void NetlinkService::GetIsLinkUp(bool* aIsUp) {
MutexAutoLock lock(mMutex);
*aIsUp = mLinkUp;
}
} // namespace mozilla::net