Bug 1567616 - network id based on default gateway is wrong when VPN overrides default gateway by more specific routes r=dragana

This patch implements NetlinkService which communicates with kernel via netlink socket. It keeps track of addresses, default routes, interfaces and neighbors and uses it to calculate network ID.

Differential Revision: https://phabricator.services.mozilla.com/D43175

--HG--
rename : netwerk/system/linux/nsNotifyAddrListener_Linux.cpp => netwerk/system/linux/nsNetworkLinkService.cpp
rename : netwerk/system/linux/nsNotifyAddrListener_Linux.h => netwerk/system/linux/nsNetworkLinkService.h
extra : moz-landing-system : lando
This commit is contained in:
Michal Novotny 2019-08-26 21:46:32 +00:00
parent 8afaba2056
commit 390918caab
18 changed files with 1839 additions and 773 deletions

View File

@ -6076,6 +6076,37 @@
value: 30000
mirror: always
# Allow the network changed event to get sent when a network topology or setup
# change is noticed while running.
- name: network.notify.changed
type: RelaxedAtomicBool
value: true
mirror: always
# Allow network detection of IPv6 related changes (bug 1245059)
- name: network.notify.IPv6
type: RelaxedAtomicBool
# ifdef XP_WIN
value: false
# else
value: true
# endif
mirror: always
# IP addresses that are used by netlink service to check whether default route
# is used for outgoing traffic. They are used just to check routing rules,
# no packets are sent to those hosts. Initially, addresses of host
# detectportal.firefox.com were used but they don't necessarily need to be
# updated when addresses of this host change.
- name: network.netlink.route.check.IPv4
type: String
value: "23.219.91.27"
mirror: never
- name: network.netlink.route.check.IPv6
type: String
value: "2a02:26f0:40::17db:5b1b"
mirror: never
#---------------------------------------------------------------------------
# Prefs starting with "nglayout."
#---------------------------------------------------------------------------

View File

@ -1489,17 +1489,6 @@ pref("logging.config.clear_on_startup", true);
// prevents necko connecting to ports 1-5 unless the protocol
// overrides.
// Allow the network changed event to get sent when a network topology or
// setup change is noticed while running.
pref("network.notify.changed", true);
// Allow network detection of IPv6 related changes (bug 1245059)
#if defined(XP_WIN)
pref("network.notify.IPv6", false);
#else
pref("network.notify.IPv6", true);
#endif
// Transmit UDP busy-work to the LAN when anticipating low latency
// network reads and on wifi to mitigate 802.11 Power Save Polling delays
pref("network.tickle-wifi.enabled", false);

View File

@ -61,6 +61,7 @@
#include "nsContentSecurityManager.h"
#include "nsContentUtils.h"
#include "nsExceptionHandler.h"
#include "mozilla/StaticPrefs_network.h"
#ifdef MOZ_WIDGET_GTK
# include "nsGIOProtocolHandler.h"
@ -83,7 +84,6 @@ using mozilla::dom::ServiceWorkerDescriptor;
// but the old names are still used to preserve backward compatibility.
#define NECKO_BUFFER_CACHE_COUNT_PREF "network.buffer.cache.count"
#define NECKO_BUFFER_CACHE_SIZE_PREF "network.buffer.cache.size"
#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
#define NETWORK_CAPTIVE_PORTAL_PREF "network.captive-portal-service.enabled"
#define WEBRTC_PREF_PREFIX "media.peerconnection."
#define NETWORK_DNS_PREF "network.dns."
@ -202,7 +202,6 @@ nsIOService::nsIOService()
mHttpHandlerAlreadyShutingDown(false),
mNetworkLinkServiceInitialized(false),
mChannelEventSinks(NS_CHANNEL_EVENT_SINK_CATEGORY),
mNetworkNotifyChanged(true),
mTotalRequests(0),
mCacheWon(0),
mNetWon(0),
@ -217,7 +216,6 @@ static const char* gCallbackPrefs[] = {
MANAGE_OFFLINE_STATUS_PREF,
NECKO_BUFFER_CACHE_COUNT_PREF,
NECKO_BUFFER_CACHE_SIZE_PREF,
NETWORK_NOTIFY_CHANGED_PREF,
NETWORK_CAPTIVE_PORTAL_PREF,
nullptr,
};
@ -1267,14 +1265,6 @@ void nsIOService::PrefsChanged(const char* pref) {
"network segment size is not a power of 2!");
}
if (!pref || strcmp(pref, NETWORK_NOTIFY_CHANGED_PREF) == 0) {
bool allow;
nsresult rv = Preferences::GetBool(NETWORK_NOTIFY_CHANGED_PREF, &allow);
if (NS_SUCCEEDED(rv)) {
mNetworkNotifyChanged = allow;
}
}
if (!pref || strcmp(pref, NETWORK_CAPTIVE_PORTAL_PREF) == 0) {
nsresult rv = Preferences::GetBool(NETWORK_CAPTIVE_PORTAL_PREF,
&gCaptivePortalEnabled);
@ -1346,7 +1336,7 @@ nsIOService::NotifyWakeup() {
NS_ASSERTION(observerService, "The observer service should not be null");
if (observerService && mNetworkNotifyChanged) {
if (observerService && StaticPrefs::network_notify_changed()) {
(void)observerService->NotifyObservers(nullptr, NS_NETWORK_LINK_TOPIC,
(u"" NS_NETWORK_LINK_DATA_CHANGED));
}

View File

@ -216,8 +216,6 @@ class nsIOService final : public nsIIOService,
nsTArray<int32_t> mRestrictedPortList;
bool mNetworkNotifyChanged;
static bool sIsDataURIUniqueOpaqueOrigin;
static bool sBlockToplevelDataUriNavigations;

View File

@ -607,8 +607,8 @@ elif toolkit == 'android':
}
elif buildconfig.substs['OS_ARCH'] == 'Linux':
link_service = {
'type': 'nsNotifyAddrListener',
'headers': ['/netwerk/system/linux/nsNotifyAddrListener_Linux.h'],
'type': 'nsNetworkLinkService',
'headers': ['/netwerk/system/linux/nsNetworkLinkService.h'],
'init_method': 'Init',
}

View File

@ -6,7 +6,7 @@
if CONFIG['OS_ARCH'] == 'Linux':
SOURCES += [
'nsNotifyAddrListener_Linux.cpp',
'nsNetworkLinkService.cpp',
]
FINAL_LIBRARY = 'xul'

View File

@ -0,0 +1,150 @@
/* -*- 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 "nsIObserverService.h"
#include "nsServiceManagerUtils.h"
#include "nsNetworkLinkService.h"
#include "nsString.h"
#include "mozilla/Logging.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/Services.h"
using namespace mozilla;
static LazyLogModule gNotifyAddrLog("nsNetworkLinkService");
#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
NS_IMPL_ISUPPORTS(nsNetworkLinkService, nsINetworkLinkService, nsIObserver)
nsNetworkLinkService::nsNetworkLinkService() : mStatusIsKnown(false) {}
NS_IMETHODIMP
nsNetworkLinkService::GetIsLinkUp(bool* aIsUp) {
if (!mNetlinkSvc) {
return NS_ERROR_NOT_AVAILABLE;
}
mNetlinkSvc->GetIsLinkUp(aIsUp);
return NS_OK;
}
NS_IMETHODIMP
nsNetworkLinkService::GetLinkStatusKnown(bool* aIsKnown) {
*aIsKnown = mStatusIsKnown;
return NS_OK;
}
NS_IMETHODIMP
nsNetworkLinkService::GetLinkType(uint32_t* aLinkType) {
NS_ENSURE_ARG_POINTER(aLinkType);
// XXX This function has not yet been implemented for this platform
*aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
return NS_OK;
}
NS_IMETHODIMP
nsNetworkLinkService::GetNetworkID(nsACString& aNetworkID) {
if (!mNetlinkSvc) {
return NS_ERROR_NOT_AVAILABLE;
}
mNetlinkSvc->GetNetworkID(aNetworkID);
return NS_OK;
}
NS_IMETHODIMP
nsNetworkLinkService::Observe(nsISupports* subject, const char* topic,
const char16_t* data) {
if (!strcmp("xpcom-shutdown-threads", topic)) {
Shutdown();
}
return NS_OK;
}
nsresult nsNetworkLinkService::Init() {
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (!observerService) {
return NS_ERROR_FAILURE;
}
nsresult rv;
rv = observerService->AddObserver(this, "xpcom-shutdown-threads", false);
NS_ENSURE_SUCCESS(rv, rv);
mNetlinkSvc = new mozilla::net::NetlinkService();
rv = mNetlinkSvc->Init(this);
if (NS_FAILED(rv)) {
mNetlinkSvc = nullptr;
LOG(("Cannot initialize NetlinkService [rv=0x%08" PRIx32 "]",
static_cast<uint32_t>(rv)));
return rv;
}
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult nsNetworkLinkService::Shutdown() {
// remove xpcom shutdown observer
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (observerService)
observerService->RemoveObserver(this, "xpcom-shutdown-threads");
if (mNetlinkSvc) {
mNetlinkSvc->Shutdown();
mNetlinkSvc = nullptr;
}
return NS_OK;
}
void nsNetworkLinkService::OnNetworkChanged() {
if (StaticPrefs::network_notify_changed()) {
RefPtr<nsNetworkLinkService> self = this;
NS_DispatchToMainThread(NS_NewRunnableFunction(
"nsNetworkLinkService::OnNetworkChanged",
[self]() { self->SendEvent(NS_NETWORK_LINK_DATA_CHANGED); }));
}
}
void nsNetworkLinkService::OnLinkUp() {
RefPtr<nsNetworkLinkService> self = this;
NS_DispatchToMainThread(NS_NewRunnableFunction(
"nsNetworkLinkService::OnLinkUp",
[self]() { self->SendEvent(NS_NETWORK_LINK_DATA_UP); }));
}
void nsNetworkLinkService::OnLinkDown() {
RefPtr<nsNetworkLinkService> self = this;
NS_DispatchToMainThread(NS_NewRunnableFunction(
"nsNetworkLinkService::OnLinkDown",
[self]() { self->SendEvent(NS_NETWORK_LINK_DATA_DOWN); }));
}
void nsNetworkLinkService::OnLinkStatusKnown() { mStatusIsKnown = true; }
/* Sends the given event. Assumes aEventID never goes out of scope (static
* strings are ideal).
*/
void nsNetworkLinkService::SendEvent(const char* aEventID) {
MOZ_ASSERT(NS_IsMainThread());
LOG(("SendEvent: %s\n", aEventID));
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (observerService) {
observerService->NotifyObservers(static_cast<nsINetworkLinkService*>(this),
NS_NETWORK_LINK_TOPIC,
NS_ConvertASCIItoUTF16(aEventID).get());
}
}

View File

@ -0,0 +1,45 @@
/* -*- 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/. */
#ifndef NSNETWORKLINKSERVICE_LINUX_H_
#define NSNETWORKLINKSERVICE_LINUX_H_
#include "nsINetworkLinkService.h"
#include "nsIObserver.h"
#include "../netlink/NetlinkService.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Atomics.h"
class nsNetworkLinkService : public nsINetworkLinkService,
public nsIObserver,
public mozilla::net::NetlinkServiceListener {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSINETWORKLINKSERVICE
NS_DECL_NSIOBSERVER
nsNetworkLinkService();
nsresult Init();
void OnNetworkChanged() override;
void OnLinkUp() override;
void OnLinkDown() override;
void OnLinkStatusKnown() override;
private:
virtual ~nsNetworkLinkService() = default;
// Called when xpcom-shutdown-threads is received.
nsresult Shutdown();
// Sends the network event.
void SendEvent(const char* aEventID);
mozilla::Atomic<bool, mozilla::Relaxed> mStatusIsKnown;
RefPtr<mozilla::net::NetlinkService> mNetlinkSvc;
};
#endif /* NSNETWORKLINKSERVICE_LINUX_H_ */

View File

@ -1,617 +0,0 @@
/* -*- 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 <stdarg.h>
#include <fcntl.h>
#include <poll.h>
#include <errno.h>
#include <ifaddrs.h>
#include <net/if.h>
#include "nsThreadUtils.h"
#include "nsIObserverService.h"
#include "nsServiceManagerUtils.h"
#include "nsNotifyAddrListener_Linux.h"
#include "nsString.h"
#include "mozilla/Logging.h"
#include "mozilla/Base64.h"
#include "mozilla/FileUtils.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/SHA1.h"
#include "mozilla/Sprintf.h"
#include "mozilla/Telemetry.h"
#include "../../base/IPv6Utils.h"
/* a shorter name that better explains what it does */
#define EINTR_RETRY(x) MOZ_TEMP_FAILURE_RETRY(x)
// period during which to absorb subsequent network change events, in
// milliseconds
static const unsigned int kNetworkChangeCoalescingPeriod = 1000;
using namespace mozilla;
static LazyLogModule gNotifyAddrLog("nsNotifyAddr");
#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
NS_IMPL_ISUPPORTS(nsNotifyAddrListener, nsINetworkLinkService, nsIRunnable,
nsIObserver)
nsNotifyAddrListener::nsNotifyAddrListener()
: mMutex("nsNotifyAddrListener::mMutex"),
mLinkUp(true), // assume true by default
mStatusKnown(false),
mAllowChangedEvent(true),
mCoalescingActive(false) {
mShutdownPipe[0] = -1;
mShutdownPipe[1] = -1;
}
nsNotifyAddrListener::~nsNotifyAddrListener() {
MOZ_ASSERT(!mThread, "nsNotifyAddrListener thread shutdown failed");
if (mShutdownPipe[0] != -1) {
EINTR_RETRY(close(mShutdownPipe[0]));
}
if (mShutdownPipe[1] != -1) {
EINTR_RETRY(close(mShutdownPipe[1]));
}
}
NS_IMETHODIMP
nsNotifyAddrListener::GetIsLinkUp(bool* aIsUp) {
// XXX This function has not yet been implemented for this platform
*aIsUp = mLinkUp;
return NS_OK;
}
NS_IMETHODIMP
nsNotifyAddrListener::GetLinkStatusKnown(bool* aIsUp) {
// XXX This function has not yet been implemented for this platform
*aIsUp = mStatusKnown;
return NS_OK;
}
NS_IMETHODIMP
nsNotifyAddrListener::GetLinkType(uint32_t* aLinkType) {
NS_ENSURE_ARG_POINTER(aLinkType);
// XXX This function has not yet been implemented for this platform
*aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
return NS_OK;
}
NS_IMETHODIMP
nsNotifyAddrListener::GetNetworkID(nsACString& aNetworkID) {
MutexAutoLock lock(mMutex);
aNetworkID = mNetworkId;
return NS_OK;
}
//
// Figure out the current IPv4 "network identification" string.
//
// It detects the IP of the default gateway in the routing table, then the MAC
// address of that IP in the ARP table before it hashes that string (to avoid
// information leakage).
//
static bool ipv4NetworkId(SHA1Sum* sha1) {
const char* kProcRoute = "/proc/net/route"; /* IPv4 routes */
const char* kProcArp = "/proc/net/arp";
bool found = false;
FILE* froute = fopen(kProcRoute, "r");
if (froute) {
char buffer[512];
uint32_t gw = 0;
char* l = fgets(buffer, sizeof(buffer), froute);
if (l) {
/* skip the title line */
while (l) {
char interf[32];
uint32_t dest;
uint32_t gateway;
l = fgets(buffer, sizeof(buffer), froute);
if (l) {
buffer[511] = 0; /* as a precaution */
int val = sscanf(buffer, "%31s %x %x", interf, &dest, &gateway);
if ((3 == val) && !dest) {
gw = gateway;
break;
}
}
}
}
fclose(froute);
if (gw) {
/* create a string to search for in the arp table */
char searchfor[16];
SprintfLiteral(searchfor, "%d.%d.%d.%d", gw & 0xff, (gw >> 8) & 0xff,
(gw >> 16) & 0xff, gw >> 24);
FILE* farp = fopen(kProcArp, "r");
if (farp) {
l = fgets(buffer, sizeof(buffer), farp);
while (l) {
/* skip the title line */
l = fgets(buffer, sizeof(buffer), farp);
if (l) {
buffer[511] = 0; /* as a precaution */
int p[4];
char type[16];
char flags[16];
char hw[32];
if (7 == sscanf(buffer, "%u.%u.%u.%u %15s %15s %31s", &p[0], &p[1],
&p[2], &p[3], type, flags, hw)) {
uint32_t searchip =
p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
if (gw == searchip) {
LOG(("networkid: MAC %s\n", hw));
nsAutoCString mac(hw);
sha1->update(mac.get(), mac.Length());
found = true;
break;
}
}
}
}
fclose(farp);
} /* if (farp) */
} /* if (gw) */
} /* if (froute) */
return found;
}
// Figure out the current IPv6 "network identification" string.
//
static bool ipv6NetworkId(SHA1Sum* sha1) {
bool found = false;
FILE* ifs = fopen("/proc/net/if_inet6", "r");
if (ifs) {
char buffer[512];
char ip6[40];
int devnum;
int preflen;
int scope;
int flags;
char name[40];
char* l = fgets(buffer, sizeof(buffer), ifs);
// 2a001a28120000090000000000000002 02 40 00 80 eth0
// +------------------------------+ ++ ++ ++ ++ ++
// | | | | | |
// 1 2 3 4 5 6
//
// 1. IPv6 address displayed in 32 hexadecimal chars without colons as
// separator
//
// 2. Netlink device number (interface index) in hexadecimal.
//
// 3. Prefix length in hexadecimal number of bits
//
// 4. Scope value (see kernel source include/net/ipv6.h and
// net/ipv6/addrconf.c for more)
//
// 5. Interface flags (see include/linux/rtnetlink.h and net/ipv6/addrconf.c
// for more)
//
// 6. Device name
//
while (l) {
memset(ip6, 0, sizeof(ip6));
if (6 == sscanf(buffer, "%32[0-9a-f] %02x %02x %02x %02x %31s", ip6,
&devnum, &preflen, &scope, &flags, name)) {
unsigned char id6[16];
memset(id6, 0, sizeof(id6));
for (int i = 0; i < 16; i++) {
char buf[3];
buf[0] = ip6[i * 2];
buf[1] = ip6[i * 2 + 1];
buf[2] = 0;
// convert from hex
id6[i] = (unsigned char)strtol(buf, nullptr, 16);
}
if (net::utils::ipv6_scope(id6) == IPV6_SCOPE_GLOBAL) {
unsigned char prefix[16];
memset(prefix, 0, sizeof(prefix));
uint8_t maskit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe};
int bits = preflen;
for (int i = 0; i < 16; i++) {
uint8_t mask = (bits >= 8) ? 0xff : maskit[bits];
prefix[i] = id6[i] & mask;
bits -= 8;
if (bits <= 0) {
break;
}
}
// We hash the IPv6 prefix and prefix length in order to
// differentiate between networks with a different prefix length
// For example: 2a00:/16 and 2a00:0/32
sha1->update(prefix, 16);
sha1->update(&preflen, sizeof(preflen));
found = true;
LOG(("networkid: found global IPv6 address %s/%d\n", ip6, preflen));
}
}
l = fgets(buffer, sizeof(buffer), ifs);
}
fclose(ifs);
}
return found;
}
// Figure out the "network identification".
//
void nsNotifyAddrListener::calculateNetworkId(void) {
MOZ_ASSERT(!NS_IsMainThread(), "Must not be called on the main thread");
SHA1Sum sha1;
bool found4 = ipv4NetworkId(&sha1);
bool found6 = ipv6NetworkId(&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;
} else {
// same id
LOG(("Same network id"));
Telemetry::Accumulate(Telemetry::NETWORK_ID2, 2);
}
} else {
// no id
LOG(("No network id"));
MutexAutoLock lock(mMutex);
mNetworkId.Truncate();
Telemetry::Accumulate(Telemetry::NETWORK_ID2, 0);
}
}
//
// Check if there's a network interface available to do networking on.
//
void nsNotifyAddrListener::checkLink(void) {
struct ifaddrs* list;
struct ifaddrs* ifa;
bool link = false;
bool prevLinkUp = mLinkUp;
if (getifaddrs(&list)) return;
// Walk through the linked list, maintaining head pointer so we can free
// list later
for (ifa = list; ifa != nullptr; ifa = ifa->ifa_next) {
int family;
if (ifa->ifa_addr == nullptr) continue;
family = ifa->ifa_addr->sa_family;
if ((family == AF_INET || family == AF_INET6) &&
(ifa->ifa_flags & IFF_RUNNING) && !(ifa->ifa_flags & IFF_LOOPBACK)) {
// An interface that is UP and not loopback
link = true;
break;
}
}
mLinkUp = link;
freeifaddrs(list);
if (prevLinkUp != mLinkUp) {
// UP/DOWN status changed, send appropriate UP/DOWN event
SendEvent(mLinkUp ? NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN);
}
}
void nsNotifyAddrListener::OnNetlinkMessage(int aNetlinkSocket) {
struct nlmsghdr* nlh;
// The buffer size below, (4095) was chosen partly based on testing and
// partly on existing sample source code using this size. It needs to be
// large enough to hold the netlink messages from the kernel.
char buffer[4095];
struct rtattr* attr;
int attr_len;
const struct ifaddrmsg* newifam;
ssize_t rc = EINTR_RETRY(recv(aNetlinkSocket, buffer, sizeof(buffer), 0));
if (rc < 0) {
return;
}
size_t netlink_bytes = rc;
nlh = reinterpret_cast<struct nlmsghdr*>(buffer);
bool networkChange = false;
for (; NLMSG_OK(nlh, netlink_bytes); nlh = NLMSG_NEXT(nlh, netlink_bytes)) {
char prefixaddr[INET6_ADDRSTRLEN];
char localaddr[INET6_ADDRSTRLEN];
char* addr = nullptr;
prefixaddr[0] = localaddr[0] = '\0';
if (NLMSG_DONE == nlh->nlmsg_type) {
break;
}
LOG(("nsNotifyAddrListener::OnNetlinkMessage: new/deleted address\n"));
newifam = reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(nlh));
if ((newifam->ifa_family != AF_INET) && (newifam->ifa_family != AF_INET6)) {
continue;
}
attr = IFA_RTA(newifam);
attr_len = IFA_PAYLOAD(nlh);
for (; attr_len && RTA_OK(attr, attr_len);
attr = RTA_NEXT(attr, attr_len)) {
if (attr->rta_type == IFA_ADDRESS) {
if (newifam->ifa_family == AF_INET) {
struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
inet_ntop(AF_INET, in, prefixaddr, INET_ADDRSTRLEN);
} else {
struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
inet_ntop(AF_INET6, in, prefixaddr, INET6_ADDRSTRLEN);
}
} else if (attr->rta_type == IFA_LOCAL) {
if (newifam->ifa_family == AF_INET) {
struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
inet_ntop(AF_INET, in, localaddr, INET_ADDRSTRLEN);
} else {
struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
inet_ntop(AF_INET6, in, localaddr, INET6_ADDRSTRLEN);
}
}
}
if (localaddr[0]) {
addr = localaddr;
} else if (prefixaddr[0]) {
addr = prefixaddr;
} else {
continue;
}
if (nlh->nlmsg_type == RTM_NEWADDR) {
LOG(
("nsNotifyAddrListener::OnNetlinkMessage: a new address "
"- %s.",
addr));
struct ifaddrmsg* ifam;
nsCString addrStr;
addrStr.Assign(addr);
if (auto entry = mAddressInfo.LookupForAdd(addrStr)) {
ifam = entry.Data();
LOG(
("nsNotifyAddrListener::OnNetlinkMessage: the address "
"already known."));
if (memcmp(ifam, newifam, sizeof(struct ifaddrmsg))) {
LOG(
("nsNotifyAddrListener::OnNetlinkMessage: but "
"the address info has been changed."));
networkChange = true;
memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
}
} else {
networkChange = true;
ifam = (struct ifaddrmsg*)malloc(sizeof(struct ifaddrmsg));
memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
entry.OrInsert([ifam]() { return ifam; });
}
} else {
LOG(
("nsNotifyAddrListener::OnNetlinkMessage: an address "
"has been deleted - %s.",
addr));
networkChange = true;
nsCString addrStr;
addrStr.Assign(addr);
mAddressInfo.Remove(addrStr);
}
}
if (networkChange && mAllowChangedEvent) {
NetworkChanged();
}
if (networkChange) {
checkLink();
}
}
NS_IMETHODIMP
nsNotifyAddrListener::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)); // clear addr
addr.nl_family = AF_NETLINK;
addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
if (bind(netlinkSocket, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
// failure!
EINTR_RETRY(close(netlinkSocket));
return NS_ERROR_FAILURE;
}
// switch the socket into non-blocking
int flags = fcntl(netlinkSocket, F_GETFL, 0);
(void)fcntl(netlinkSocket, F_SETFL, flags | O_NONBLOCK);
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;
calculateNetworkId();
nsresult rv = NS_OK;
bool shutdown = false;
int pollWait = -1;
while (!shutdown) {
int rc = EINTR_RETRY(poll(fds, 2, pollWait));
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;
}
if (mCoalescingActive) {
// check if coalescing period should continue
double period = (TimeStamp::Now() - mChangeTime).ToMilliseconds();
if (period >= kNetworkChangeCoalescingPeriod) {
SendEvent(NS_NETWORK_LINK_DATA_CHANGED);
mCoalescingActive = false;
pollWait = -1; // restore to default
} else {
// wait no longer than to the end of the period
pollWait = static_cast<int>(kNetworkChangeCoalescingPeriod - period);
}
}
}
EINTR_RETRY(close(netlinkSocket));
return rv;
}
NS_IMETHODIMP
nsNotifyAddrListener::Observe(nsISupports* subject, const char* topic,
const char16_t* data) {
if (!strcmp("xpcom-shutdown-threads", topic)) {
Shutdown();
}
return NS_OK;
}
nsresult nsNotifyAddrListener::Init(void) {
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (!observerService) return NS_ERROR_FAILURE;
nsresult rv =
observerService->AddObserver(this, "xpcom-shutdown-threads", false);
NS_ENSURE_SUCCESS(rv, rv);
Preferences::AddBoolVarCache(&mAllowChangedEvent, NETWORK_NOTIFY_CHANGED_PREF,
true);
if (-1 == pipe(mShutdownPipe)) {
return NS_ERROR_FAILURE;
}
rv = NS_NewNamedThread("Link Monitor", getter_AddRefs(mThread), this);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult nsNotifyAddrListener::Shutdown(void) {
// remove xpcom shutdown observer
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (observerService)
observerService->RemoveObserver(this, "xpcom-shutdown-threads");
LOG(("write() to signal thread shutdown\n"));
// 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 nsNotifyAddrListener holds
// onto the thread and the thread holds onto the nsNotifyAddrListener
// via its mRunnable
mThread = nullptr;
return rv;
}
/*
* A network event has been registered. Delay the actual sending of the event
* for a while and absorb subsequent events in the mean time in an effort to
* squash potentially many triggers into a single event.
* Only ever called from the same thread.
*/
nsresult nsNotifyAddrListener::NetworkChanged() {
if (mCoalescingActive) {
LOG(("NetworkChanged: absorbed an event (coalescing active)\n"));
} else {
// A fresh trigger!
mChangeTime = TimeStamp::Now();
mCoalescingActive = true;
LOG(("NetworkChanged: coalescing period started\n"));
}
return NS_OK;
}
/* Sends the given event. Assumes aEventID never goes out of scope (static
* strings are ideal).
*/
nsresult nsNotifyAddrListener::SendEvent(const char* aEventID) {
if (!aEventID) return NS_ERROR_NULL_POINTER;
LOG(("SendEvent: %s\n", aEventID));
nsresult rv = NS_OK;
calculateNetworkId();
nsCOMPtr<nsIRunnable> event = new ChangeEvent(this, aEventID);
if (NS_FAILED(rv = NS_DispatchToMainThread(event)))
NS_WARNING("Failed to dispatch ChangeEvent");
return rv;
}
NS_IMETHODIMP
nsNotifyAddrListener::ChangeEvent::Run() {
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (observerService)
observerService->NotifyObservers(mService, NS_NETWORK_LINK_TOPIC,
NS_ConvertASCIItoUTF16(mEventID).get());
return NS_OK;
}

View File

@ -1,104 +0,0 @@
/* -*- 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/. */
#ifndef NSNOTIFYADDRLISTENER_LINUX_H_
#define NSNOTIFYADDRLISTENER_LINUX_H_
#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "nsINetworkLinkService.h"
#include "nsIRunnable.h"
#include "nsIObserver.h"
#include "nsThreadUtils.h"
#include "nsCOMPtr.h"
#include "mozilla/Atomics.h"
#include "mozilla/Mutex.h"
#include "mozilla/TimeStamp.h"
#include "nsITimer.h"
#include "nsClassHashtable.h"
class nsNotifyAddrListener : public nsINetworkLinkService,
public nsIRunnable,
public nsIObserver {
virtual ~nsNotifyAddrListener();
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSINETWORKLINKSERVICE
NS_DECL_NSIRUNNABLE
NS_DECL_NSIOBSERVER
nsNotifyAddrListener();
nsresult Init(void);
private:
class ChangeEvent : public mozilla::Runnable {
public:
NS_DECL_NSIRUNNABLE
ChangeEvent(nsINetworkLinkService* aService, const char* aEventID)
: mozilla::Runnable("nsNotifyAddrListener::ChangeEvent"),
mService(aService),
mEventID(aEventID) {}
private:
nsCOMPtr<nsINetworkLinkService> mService;
const char* mEventID;
};
// Called when xpcom-shutdown-threads is received.
nsresult Shutdown(void);
// Called when a network change was detected
nsresult NetworkChanged();
// Sends the network event.
nsresult SendEvent(const char* aEventID);
// Figure out the current "network identification"
void calculateNetworkId(void);
mozilla::Mutex mMutex;
nsCString mNetworkId;
// Checks if there's a network "link"
void checkLink(void);
// Deals with incoming NETLINK messages.
void OnNetlinkMessage(int NetlinkSocket);
nsCOMPtr<nsIThread> mThread;
// The network is up.
bool mLinkUp;
// The network's up/down status is known.
bool mStatusKnown;
// A pipe to signal shutdown with.
int mShutdownPipe[2];
// Network changed events are enabled
bool mAllowChangedEvent;
// Flag set while coalescing change events
bool mCoalescingActive;
// Time stamp for first event during coalescing
mozilla::TimeStamp mChangeTime;
// Seen Ip addresses. For Ipv6 addresses some time router renews their
// lifetime and we should not detect this as a network link change, so we
// keep info about all seen addresses.
nsClassHashtable<nsCStringHashKey, struct ifaddrmsg> mAddressInfo;
};
#endif /* NSNOTIFYADDRLISTENER_LINUX_H_ */

View File

@ -30,9 +30,6 @@ class nsNetworkLinkService : public nsINetworkLinkService, public nsIObserver {
bool mLinkUp;
bool mStatusKnown;
// Toggles allowing the sending of network-changed event.
bool mAllowChangedEvent;
SCNetworkReachabilityRef mReachability;
CFRunLoopRef mCFRunLoop;
CFRunLoopSourceRef mRunLoopSource;

View File

@ -23,7 +23,7 @@
#include "nsNetCID.h"
#include "nsThreadUtils.h"
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/SHA1.h"
#include "mozilla/Base64.h"
#include "mozilla/Telemetry.h"
@ -72,7 +72,6 @@ NS_IMPL_ISUPPORTS(nsNetworkLinkService, nsINetworkLinkService, nsIObserver)
nsNetworkLinkService::nsNetworkLinkService()
: mLinkUp(true),
mStatusKnown(false),
mAllowChangedEvent(true),
mReachability(nullptr),
mCFRunLoop(nullptr),
mRunLoopSource(nullptr),
@ -407,8 +406,6 @@ nsresult nsNetworkLinkService::Init(void) {
rv = observerService->AddObserver(this, "xpcom-shutdown", false);
NS_ENSURE_SUCCESS(rv, rv);
Preferences::AddBoolVarCache(&mAllowChangedEvent, NETWORK_NOTIFY_CHANGED_PREF, true);
// If the network reachability API can reach 0.0.0.0 without
// requiring a connection, there is a network interface available.
struct sockaddr_in addr;
@ -553,7 +550,7 @@ void nsNetworkLinkService::SendEvent(bool aNetworkChanged) {
const char* event;
if (aNetworkChanged) {
if (!mAllowChangedEvent) {
if (!StaticPrefs::network_notify_changed()) {
return;
}
event = NS_NETWORK_LINK_DATA_CHANGED;

View File

@ -14,4 +14,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
DIRS += ['android']
elif CONFIG['OS_ARCH'] == 'Linux':
DIRS += ['linux']
DIRS += [
'linux',
'netlink'
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,136 @@
/* -*- 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/. */
#ifndef NETLINKSERVICE_H_
#define NETLINKSERVICE_H_
#include <netinet/in.h>
#include <linux/netlink.h>
#include "nsINetworkLinkService.h"
#include "nsIRunnable.h"
#include "nsThreadUtils.h"
#include "nsCOMPtr.h"
#include "mozilla/Mutex.h"
#include "mozilla/TimeStamp.h"
#include "nsITimer.h"
#include "nsClassHashtable.h"
#include "mozilla/SHA1.h"
namespace mozilla {
namespace net {
#if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION) || defined(DEBUG)
# define NL_DEBUG_LOG
#endif
class NetlinkAddress;
class NetlinkNeighbor;
class NetlinkLink;
class NetlinkRoute;
class NetlinkMsg;
class NetlinkServiceListener : public nsISupports {
public:
virtual void OnNetworkChanged() = 0;
virtual void OnLinkUp() = 0;
virtual void OnLinkDown() = 0;
virtual void OnLinkStatusKnown() = 0;
protected:
virtual ~NetlinkServiceListener() = default;
};
class NetlinkService : public nsIRunnable {
virtual ~NetlinkService();
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIRUNNABLE
NetlinkService();
nsresult Init(NetlinkServiceListener* aListener);
nsresult Shutdown();
void GetNetworkID(nsACString& aNetworkID);
void GetIsLinkUp(bool* aIsUp);
private:
void EnqueueGenMsg(uint16_t aMsgType, uint8_t aFamily);
void EnqueueRtMsg(uint8_t aFamily, void* aAddress);
void RemovePendingMsg();
mozilla::Mutex mMutex;
void OnNetlinkMessage(int aNetlinkSocket);
void OnLinkMessage(struct nlmsghdr* aNlh);
void OnAddrMessage(struct nlmsghdr* aNlh);
void OnRouteMessage(struct nlmsghdr* aNlh);
void OnNeighborMessage(struct nlmsghdr* aNlh);
void OnRouteCheckResult(struct nlmsghdr* aNlh);
void CheckLinks();
void TriggerNetworkIDCalculation();
int GetPollWait();
bool CalculateIDForFamily(uint8_t aFamily, mozilla::SHA1Sum* aSHA1);
void CalculateNetworkID();
nsCOMPtr<nsIThread> mThread;
bool mInitialScanFinished;
// A pipe to signal shutdown with.
int mShutdownPipe[2];
// Is true if preference network.netlink.route.check.IPv4 was successfully
// parsed and stored to mRouteCheckIPv4
bool mDoRouteCheckIPv4;
struct in_addr mRouteCheckIPv4;
// Is true if preference network.netlink.route.check.IPv6 was successfully
// parsed and stored to mRouteCheckIPv6
bool mDoRouteCheckIPv6;
struct in6_addr mRouteCheckIPv6;
pid_t mPid;
uint32_t mMsgId;
bool mLinkUp;
// Flag indicating that network ID could change and should be recalculated.
// Calculation is postponed until we receive responses to all enqueued
// messages.
bool mRecalculateNetworkId;
// Time stamp of setting mRecalculateNetworkId to true
mozilla::TimeStamp mTriggerTime;
nsCString mNetworkId;
// All IPv4 and IPv6 addresses received via netlink
nsTArray<nsAutoPtr<NetlinkAddress> > mAddresses;
// All neighbors, key is an address
nsClassHashtable<nsCStringHashKey, NetlinkNeighbor> mNeighbors;
// All interfaces keyed by interface index
nsClassHashtable<nsUint32HashKey, NetlinkLink> mLinks;
// Default IPv4 routes
nsTArray<nsAutoPtr<NetlinkRoute> > mIPv4Routes;
// Default IPv6 routes
nsTArray<nsAutoPtr<NetlinkRoute> > mIPv6Routes;
// Route for mRouteCheckIPv4 address
nsAutoPtr<NetlinkRoute> mIPv4RouteCheckResult;
// Route for mRouteCheckIPv6 address
nsAutoPtr<NetlinkRoute> mIPv6RouteCheckResult;
nsTArray<nsAutoPtr<NetlinkMsg> > mOutgoingMessages;
RefPtr<NetlinkServiceListener> mListener;
};
} // namespace net
} // namespace mozilla
#endif /* NETLINKSERVICE_H_ */

View File

@ -0,0 +1,12 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
if CONFIG['OS_ARCH'] == 'Linux':
SOURCES += [
'NetlinkService.cpp',
]
FINAL_LIBRARY = 'xul'

View File

@ -34,7 +34,7 @@
#include "nsAutoPtr.h"
#include "mozilla/Services.h"
#include "nsCRT.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/SHA1.h"
#include "mozilla/Base64.h"
#include "mozilla/ScopeExit.h"
@ -115,8 +115,6 @@ nsNotifyAddrListener::nsNotifyAddrListener()
mCheckEvent(nullptr),
mShutdown(false),
mIPInterfaceChecksum(0),
mAllowChangedEvent(true),
mIPv6Changes(false),
mCoalescingActive(false) {
InitIphlpapi();
}
@ -276,7 +274,8 @@ nsNotifyAddrListener::Run() {
DWORD waitTime = INFINITE;
if (!sNotifyIpInterfaceChange || !sCancelMibChangeNotify2 || !mIPv6Changes) {
if (!sNotifyIpInterfaceChange || !sCancelMibChangeNotify2 ||
!StaticPrefs::network_notify_IPv6()) {
// For Windows versions which are older than Vista which lack
// NotifyIpInterfaceChange. Note this means no IPv6 support.
HANDLE ev = CreateEvent(nullptr, FALSE, FALSE, nullptr);
@ -350,10 +349,6 @@ nsresult nsNotifyAddrListener::Init(void) {
observerService->AddObserver(this, "xpcom-shutdown-threads", false);
NS_ENSURE_SUCCESS(rv, rv);
Preferences::AddBoolVarCache(&mAllowChangedEvent, NETWORK_NOTIFY_CHANGED_PREF,
true);
Preferences::AddBoolVarCache(&mIPv6Changes, NETWORK_NOTIFY_IPV6_PREF, false);
mCheckEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
NS_ENSURE_TRUE(mCheckEvent, NS_ERROR_OUT_OF_MEMORY);
@ -649,7 +644,8 @@ void nsNotifyAddrListener::CheckLinkStatus(void) {
// Network is online. Topology has changed. Always send CHANGED
// before UP - if allowed to and having cooled down.
if (mAllowChangedEvent && (since.ToMilliseconds() > 2000)) {
if (StaticPrefs::network_notify_changed() &&
(since.ToMilliseconds() > 2000)) {
NetworkChanged();
}
}

View File

@ -88,12 +88,6 @@ class nsNotifyAddrListener : public nsINetworkLinkService,
// start time of the checking
mozilla::TimeStamp mStartTime;
// Network changed events are enabled
bool mAllowChangedEvent;
// Check for IPv6 network changes
bool mIPv6Changes;
// Flag set while coalescing change events
bool mCoalescingActive;