summaryrefslogtreecommitdiffstats
path: root/netwerk/system
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /netwerk/system
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'netwerk/system')
-rw-r--r--netwerk/system/android/moz.build14
-rw-r--r--netwerk/system/android/nsAndroidNetworkLinkService.cpp63
-rw-r--r--netwerk/system/android/nsAndroidNetworkLinkService.h24
-rw-r--r--netwerk/system/linux/moz.build12
-rw-r--r--netwerk/system/linux/nsNotifyAddrListener_Linux.cpp549
-rw-r--r--netwerk/system/linux/nsNotifyAddrListener_Linux.h100
-rw-r--r--netwerk/system/mac/moz.build14
-rw-r--r--netwerk/system/mac/nsNetworkLinkService.h54
-rw-r--r--netwerk/system/mac/nsNetworkLinkService.mm530
-rw-r--r--netwerk/system/moz.build17
-rw-r--r--netwerk/system/win32/moz.build12
-rw-r--r--netwerk/system/win32/nsNotifyAddrListener.cpp743
-rw-r--r--netwerk/system/win32/nsNotifyAddrListener.h100
13 files changed, 2232 insertions, 0 deletions
diff --git a/netwerk/system/android/moz.build b/netwerk/system/android/moz.build
new file mode 100644
index 000000000..68288ad10
--- /dev/null
+++ b/netwerk/system/android/moz.build
@@ -0,0 +1,14 @@
+# -*- 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/.
+
+SOURCES += [
+ 'nsAndroidNetworkLinkService.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+]
diff --git a/netwerk/system/android/nsAndroidNetworkLinkService.cpp b/netwerk/system/android/nsAndroidNetworkLinkService.cpp
new file mode 100644
index 000000000..692b69a7e
--- /dev/null
+++ b/netwerk/system/android/nsAndroidNetworkLinkService.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "nsAndroidNetworkLinkService.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+
+#include "AndroidBridge.h"
+
+namespace java = mozilla::java;
+
+NS_IMPL_ISUPPORTS(nsAndroidNetworkLinkService,
+ nsINetworkLinkService)
+
+nsAndroidNetworkLinkService::nsAndroidNetworkLinkService()
+{
+}
+
+nsAndroidNetworkLinkService::~nsAndroidNetworkLinkService()
+{
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetIsLinkUp(bool *aIsUp)
+{
+ if (!mozilla::AndroidBridge::Bridge()) {
+ // Fail soft here and assume a connection exists
+ NS_WARNING("GetIsLinkUp is not supported without a bridge connection");
+ *aIsUp = true;
+ return NS_OK;
+ }
+
+ *aIsUp = java::GeckoAppShell::IsNetworkLinkUp();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetLinkStatusKnown(bool *aIsKnown)
+{
+ NS_ENSURE_TRUE(mozilla::AndroidBridge::Bridge(), NS_ERROR_NOT_IMPLEMENTED);
+
+ *aIsKnown = java::GeckoAppShell::IsNetworkLinkKnown();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetLinkType(uint32_t *aLinkType)
+{
+ NS_ENSURE_ARG_POINTER(aLinkType);
+
+ if (!mozilla::AndroidBridge::Bridge()) {
+ // Fail soft here and assume a connection exists
+ NS_WARNING("GetLinkType is not supported without a bridge connection");
+ *aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
+ return NS_OK;
+ }
+
+ *aLinkType = java::GeckoAppShell::GetNetworkLinkType();
+ return NS_OK;
+}
diff --git a/netwerk/system/android/nsAndroidNetworkLinkService.h b/netwerk/system/android/nsAndroidNetworkLinkService.h
new file mode 100644
index 000000000..9fdda7cae
--- /dev/null
+++ b/netwerk/system/android/nsAndroidNetworkLinkService.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 NSANDROIDNETWORKLINKSERVICE_H_
+#define NSANDROIDNETWORKLINKSERVICE_H_
+
+#include "nsINetworkLinkService.h"
+
+class nsAndroidNetworkLinkService: public nsINetworkLinkService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETWORKLINKSERVICE
+
+ nsAndroidNetworkLinkService();
+
+private:
+ virtual ~nsAndroidNetworkLinkService();
+};
+
+#endif /* NSANDROIDNETWORKLINKSERVICE_H_ */
diff --git a/netwerk/system/linux/moz.build b/netwerk/system/linux/moz.build
new file mode 100644
index 000000000..21fc5e2d2
--- /dev/null
+++ b/netwerk/system/linux/moz.build
@@ -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 += [
+ 'nsNotifyAddrListener_Linux.cpp',
+ ]
+
+FINAL_LIBRARY = 'xul'
diff --git a/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp b/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp
new file mode 100644
index 000000000..c0ec9d90e
--- /dev/null
+++ b/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp
@@ -0,0 +1,549 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set et sw=4 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>
+#ifndef MOZ_WIDGET_GONK
+#include <ifaddrs.h>
+#include <net/if.h>
+#endif
+
+#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"
+
+#ifdef MOZ_WIDGET_GONK
+#include <cutils/properties.h>
+#endif
+
+/* 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()
+ : 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;
+}
+
+//
+// Figure out the current "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).
+//
+void nsNotifyAddrListener::calculateNetworkId(void)
+{
+ 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);
+ // This 'addition' could potentially be a
+ // fixed number from the profile or something.
+ nsAutoCString addition("local-rubbish");
+ nsAutoCString output;
+ SHA1Sum sha1;
+ nsCString combined(mac + addition);
+ sha1.update(combined.get(), combined.Length());
+ uint8_t digest[SHA1Sum::kHashSize];
+ sha1.finish(digest);
+ nsCString 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()));
+ if (mNetworkId != output) {
+ // new id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 1);
+ mNetworkId = output;
+ }
+ else {
+ // same id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 2);
+ }
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+ fclose(farp);
+ } /* if (farp) */
+ } /* if (gw) */
+ } /* if (froute) */
+ if (!found) {
+ // no id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 0);
+ }
+}
+
+//
+// Check if there's a network interface available to do networking on.
+//
+void nsNotifyAddrListener::checkLink(void)
+{
+#ifdef MOZ_WIDGET_GONK
+ // b2g instead has NetworkManager.js which handles UP/DOWN
+#else
+ 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 != NULL; ifa = ifa->ifa_next) {
+ int family;
+ if (ifa->ifa_addr == NULL)
+ 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);
+ }
+#endif
+}
+
+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 (mAddressInfo.Get(addrStr, &ifam)) {
+ 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));
+ mAddressInfo.Put(addrStr,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);
+ calculateNetworkId();
+ 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;
+ 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;
+}
diff --git a/netwerk/system/linux/nsNotifyAddrListener_Linux.h b/netwerk/system/linux/nsNotifyAddrListener_Linux.h
new file mode 100644
index 000000000..a2b8f2277
--- /dev/null
+++ b/netwerk/system/linux/nsNotifyAddrListener_Linux.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set et sw=4 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/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)
+ : 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);
+ 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_ */
diff --git a/netwerk/system/mac/moz.build b/netwerk/system/mac/moz.build
new file mode 100644
index 000000000..b90b8d67d
--- /dev/null
+++ b/netwerk/system/mac/moz.build
@@ -0,0 +1,14 @@
+# -*- 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/.
+
+SOURCES += [
+ 'nsNetworkLinkService.mm',
+]
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['CLANG_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/system/mac/nsNetworkLinkService.h b/netwerk/system/mac/nsNetworkLinkService.h
new file mode 100644
index 000000000..ee5462247
--- /dev/null
+++ b/netwerk/system/mac/nsNetworkLinkService.h
@@ -0,0 +1,54 @@
+/* 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 NSNETWORKLINKSERVICEMAC_H_
+#define NSNETWORKLINKSERVICEMAC_H_
+
+#include "nsINetworkLinkService.h"
+#include "nsIObserver.h"
+
+#include <SystemConfiguration/SCNetworkReachability.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+
+class nsNetworkLinkService : public nsINetworkLinkService,
+ public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETWORKLINKSERVICE
+ NS_DECL_NSIOBSERVER
+
+ nsNetworkLinkService();
+
+ nsresult Init();
+ nsresult Shutdown();
+
+protected:
+ virtual ~nsNetworkLinkService();
+
+private:
+ bool mLinkUp;
+ bool mStatusKnown;
+
+ // Toggles allowing the sending of network-changed event.
+ bool mAllowChangedEvent;
+
+ SCNetworkReachabilityRef mReachability;
+ CFRunLoopRef mCFRunLoop;
+ CFRunLoopSourceRef mRunLoopSource;
+ SCDynamicStoreRef mStoreRef;
+
+ void UpdateReachability();
+ void SendEvent(bool aNetworkChanged);
+ static void ReachabilityChanged(SCNetworkReachabilityRef target,
+ SCNetworkConnectionFlags flags,
+ void *info);
+ static void IPConfigChanged(SCDynamicStoreRef store,
+ CFArrayRef changedKeys,
+ void *info);
+ void calculateNetworkId(void);
+ nsCString mNetworkId;
+};
+
+#endif /* NSNETWORKLINKSERVICEMAC_H_ */
diff --git a/netwerk/system/mac/nsNetworkLinkService.mm b/netwerk/system/mac/nsNetworkLinkService.mm
new file mode 100644
index 000000000..ac6a015fb
--- /dev/null
+++ b/netwerk/system/mac/nsNetworkLinkService.mm
@@ -0,0 +1,530 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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 <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <arpa/inet.h>
+#include "nsCOMPtr.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/Base64.h"
+#include "mozilla/Telemetry.h"
+#include "nsNetworkLinkService.h"
+
+#import <Cocoa/Cocoa.h>
+#import <netinet/in.h>
+
+#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
+
+using namespace mozilla;
+
+static LazyLogModule gNotifyAddrLog("nsNotifyAddr");
+#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
+
+// If non-successful, extract the error code and return it. This
+// error code dance is inspired by
+// http://developer.apple.com/technotes/tn/tn1145.html
+static OSStatus getErrorCodeBool(Boolean success)
+{
+ OSStatus err = noErr;
+ if (!success) {
+ int scErr = ::SCError();
+ if (scErr == kSCStatusOK) {
+ scErr = kSCStatusFailed;
+ }
+ err = scErr;
+ }
+ return err;
+}
+
+// If given a NULL pointer, return the error code.
+static OSStatus getErrorCodePtr(const void *value)
+{
+ return getErrorCodeBool(value != NULL);
+}
+
+// Convenience function to allow NULL input.
+static void CFReleaseSafe(CFTypeRef cf)
+{
+ if (cf) {
+ // "If cf is NULL, this will cause a runtime error and your
+ // application will crash." / Apple docs
+ ::CFRelease(cf);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsNetworkLinkService,
+ nsINetworkLinkService,
+ nsIObserver)
+
+nsNetworkLinkService::nsNetworkLinkService()
+ : mLinkUp(true)
+ , mStatusKnown(false)
+ , mAllowChangedEvent(true)
+ , mReachability(nullptr)
+ , mCFRunLoop(nullptr)
+ , mRunLoopSource(nullptr)
+ , mStoreRef(nullptr)
+{
+}
+
+nsNetworkLinkService::~nsNetworkLinkService() = default;
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetIsLinkUp(bool *aIsUp)
+{
+ *aIsUp = mLinkUp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetLinkStatusKnown(bool *aIsUp)
+{
+ *aIsUp = mStatusKnown;
+ 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;
+}
+
+#ifndef SA_SIZE
+#define SA_SIZE(sa) \
+ ( (!(sa) || ((struct sockaddr *)(sa))->sa_len == 0) ? \
+ sizeof(uint32_t) : \
+ 1 + ( (((struct sockaddr *)(sa))->sa_len - 1) | (sizeof(uint32_t) - 1) ) )
+#endif
+
+static char *getMac(struct sockaddr_dl *sdl, char *buf, size_t bufsize)
+{
+ char *cp;
+ int n, p = 0;
+
+ buf[0] = 0;
+ cp = (char *)LLADDR(sdl);
+ n = sdl->sdl_alen;
+ if (n > 0) {
+ while (--n >= 0) {
+ p += snprintf(&buf[p], bufsize - p, "%02x%s",
+ *cp++ & 0xff, n > 0 ? ":" : "");
+ }
+ }
+ return buf;
+}
+
+/* If the IP matches, get the MAC and return true */
+static bool matchIp(struct sockaddr_dl *sdl, struct sockaddr_inarp *addr,
+ char *ip, char *buf, size_t bufsize)
+{
+ if (sdl->sdl_alen) {
+ if (!strcmp(inet_ntoa(addr->sin_addr), ip)) {
+ getMac(sdl, buf, bufsize);
+ return true; /* done! */
+ }
+ }
+ return false; /* continue */
+}
+
+/*
+ * Scan for the 'IP' address in the ARP table and store the corresponding MAC
+ * address in 'mac'. The output buffer is 'maclen' bytes big.
+ *
+ * Returns 'true' if it found the IP and returns a MAC.
+ */
+static bool scanArp(char *ip, char *mac, size_t maclen)
+{
+ int mib[6];
+ char *lim, *next;
+ int st;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET;
+ mib[4] = NET_RT_FLAGS;
+ mib[5] = RTF_LLINFO;
+
+ size_t needed;
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
+ return false;
+ }
+ if (needed == 0) {
+ // empty table
+ return false;
+ }
+
+ UniquePtr <char[]>buf(new char[needed]);
+
+ for (;;) {
+ st = sysctl(mib, 6, &buf[0], &needed, NULL, 0);
+ if (st == 0 || errno != ENOMEM) {
+ break;
+ }
+ needed += needed / 8;
+
+ auto tmp = MakeUnique<char[]>(needed);
+ memcpy(&tmp[0], &buf[0], needed);
+ buf = Move(tmp);
+ }
+ if (st == -1) {
+ return false;
+ }
+ lim = &buf[needed];
+
+ struct rt_msghdr *rtm;
+ for (next = &buf[0]; next < lim; next += rtm->rtm_msglen) {
+ rtm = reinterpret_cast<struct rt_msghdr *>(next);
+ struct sockaddr_inarp *sin2 =
+ reinterpret_cast<struct sockaddr_inarp *>(rtm + 1);
+ struct sockaddr_dl *sdl =
+ reinterpret_cast<struct sockaddr_dl *>
+ ((char *)sin2 + SA_SIZE(sin2));
+ if (matchIp(sdl, sin2, ip, mac, maclen)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int routingTable(char *gw, size_t aGwLen)
+{
+ size_t needed;
+ int mib[6];
+ struct rt_msghdr *rtm;
+ struct sockaddr *sa;
+ struct sockaddr_in *sockin;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = 0;
+ mib[4] = NET_RT_DUMP;
+ mib[5] = 0;
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
+ return 1;
+ }
+
+ UniquePtr <char[]>buf(new char[needed]);
+
+ if (sysctl(mib, 6, &buf[0], &needed, NULL, 0) < 0) {
+ return 3;
+ }
+
+ rtm = reinterpret_cast<struct rt_msghdr *>(&buf[0]);
+ sa = reinterpret_cast<struct sockaddr *>(rtm + 1);
+ sa = reinterpret_cast<struct sockaddr *>(SA_SIZE(sa) + (char *)sa);
+ sockin = reinterpret_cast<struct sockaddr_in *>(sa);
+ inet_ntop(AF_INET, &sockin->sin_addr.s_addr, gw, aGwLen-1);
+
+ return 0;
+}
+
+//
+// Figure out the current "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).
+//
+void nsNetworkLinkService::calculateNetworkId(void)
+{
+ bool found = false;
+ char hw[MAXHOSTNAMELEN];
+ if (!routingTable(hw, sizeof(hw))) {
+ char mac[256]; // big enough for a printable MAC address
+ if (scanArp(hw, mac, sizeof(mac))) {
+ LOG(("networkid: MAC %s\n", hw));
+ nsAutoCString mac(hw);
+ // This 'addition' could potentially be a
+ // fixed number from the profile or something.
+ nsAutoCString addition("local-rubbish");
+ nsAutoCString output;
+ SHA1Sum sha1;
+ nsCString combined(mac + addition);
+ sha1.update(combined.get(), combined.Length());
+ uint8_t digest[SHA1Sum::kHashSize];
+ sha1.finish(digest);
+ nsCString 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()));
+ if (mNetworkId != output) {
+ // new id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 1);
+ mNetworkId = output;
+ }
+ else {
+ // same id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 2);
+ }
+ found = true;
+ }
+ }
+ if (!found) {
+ // no id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 0);
+ }
+}
+
+
+NS_IMETHODIMP
+nsNetworkLinkService::Observe(nsISupports *subject,
+ const char *topic,
+ const char16_t *data)
+{
+ if (!strcmp(topic, "xpcom-shutdown")) {
+ Shutdown();
+ }
+
+ return NS_OK;
+}
+
+/* static */
+void
+nsNetworkLinkService::IPConfigChanged(SCDynamicStoreRef aStoreREf,
+ CFArrayRef aChangedKeys,
+ void *aInfo)
+{
+ nsNetworkLinkService *service =
+ static_cast<nsNetworkLinkService*>(aInfo);
+ service->SendEvent(true);
+}
+
+nsresult
+nsNetworkLinkService::Init(void)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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;
+ bzero(&addr, sizeof(addr));
+ addr.sin_len = sizeof(addr);
+ addr.sin_family = AF_INET;
+ mReachability =
+ ::SCNetworkReachabilityCreateWithAddress(NULL,
+ (struct sockaddr *)&addr);
+ if (!mReachability) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ SCNetworkReachabilityContext context = {0, this, NULL, NULL, NULL};
+ if (!::SCNetworkReachabilitySetCallback(mReachability,
+ ReachabilityChanged,
+ &context)) {
+ NS_WARNING("SCNetworkReachabilitySetCallback failed.");
+ ::CFRelease(mReachability);
+ mReachability = NULL;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ SCDynamicStoreContext storeContext = {0, this, NULL, NULL, NULL};
+ mStoreRef =
+ ::SCDynamicStoreCreate(NULL,
+ CFSTR("AddIPAddressListChangeCallbackSCF"),
+ IPConfigChanged, &storeContext);
+
+ CFStringRef patterns[2] = {NULL, NULL};
+ OSStatus err = getErrorCodePtr(mStoreRef);
+ if (err == noErr) {
+ // This pattern is "State:/Network/Service/[^/]+/IPv4".
+ patterns[0] =
+ ::SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+ kSCDynamicStoreDomainState,
+ kSCCompAnyRegex,
+ kSCEntNetIPv4);
+ err = getErrorCodePtr(patterns[0]);
+ if (err == noErr) {
+ // This pattern is "State:/Network/Service/[^/]+/IPv6".
+ patterns[1] =
+ ::SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+ kSCDynamicStoreDomainState,
+ kSCCompAnyRegex,
+ kSCEntNetIPv6);
+ err = getErrorCodePtr(patterns[1]);
+ }
+ }
+
+ CFArrayRef patternList = NULL;
+ // Create a pattern list containing just one pattern,
+ // then tell SCF that we want to watch changes in keys
+ // that match that pattern list, then create our run loop
+ // source.
+ if (err == noErr) {
+ patternList = ::CFArrayCreate(NULL, (const void **) patterns,
+ 2, &kCFTypeArrayCallBacks);
+ if (!patternList) {
+ err = -1;
+ }
+ }
+ if (err == noErr) {
+ err =
+ getErrorCodeBool(::SCDynamicStoreSetNotificationKeys(mStoreRef,
+ NULL,
+ patternList));
+ }
+
+ if (err == noErr) {
+ mRunLoopSource =
+ ::SCDynamicStoreCreateRunLoopSource(NULL, mStoreRef, 0);
+ err = getErrorCodePtr(mRunLoopSource);
+ }
+
+ CFReleaseSafe(patterns[0]);
+ CFReleaseSafe(patterns[1]);
+ CFReleaseSafe(patternList);
+
+ if (err != noErr) {
+ CFReleaseSafe(mStoreRef);
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Get the current run loop. This service is initialized at startup,
+ // so we shouldn't run in to any problems with modal dialog run loops.
+ mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
+ if (!mCFRunLoop) {
+ NS_WARNING("Could not get current run loop.");
+ ::CFRelease(mReachability);
+ mReachability = NULL;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ ::CFRetain(mCFRunLoop);
+
+ ::CFRunLoopAddSource(mCFRunLoop, mRunLoopSource, kCFRunLoopDefaultMode);
+
+ if (!::SCNetworkReachabilityScheduleWithRunLoop(mReachability, mCFRunLoop,
+ kCFRunLoopDefaultMode)) {
+ NS_WARNING("SCNetworkReachabilityScheduleWIthRunLoop failed.");
+ ::CFRelease(mReachability);
+ mReachability = NULL;
+ ::CFRelease(mCFRunLoop);
+ mCFRunLoop = NULL;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ UpdateReachability();
+
+ return NS_OK;
+}
+
+nsresult
+nsNetworkLinkService::Shutdown()
+{
+ if (!::SCNetworkReachabilityUnscheduleFromRunLoop(mReachability,
+ mCFRunLoop,
+ kCFRunLoopDefaultMode)) {
+ NS_WARNING("SCNetworkReachabilityUnscheduleFromRunLoop failed.");
+ }
+
+ CFRunLoopRemoveSource(mCFRunLoop, mRunLoopSource, kCFRunLoopDefaultMode);
+
+ ::CFRelease(mReachability);
+ mReachability = nullptr;
+
+ ::CFRelease(mCFRunLoop);
+ mCFRunLoop = nullptr;
+
+ ::CFRelease(mStoreRef);
+ mStoreRef = nullptr;
+
+ ::CFRelease(mRunLoopSource);
+ mRunLoopSource = nullptr;
+
+ return NS_OK;
+}
+
+void
+nsNetworkLinkService::UpdateReachability()
+{
+ if (!mReachability) {
+ return;
+ }
+
+ SCNetworkConnectionFlags flags;
+ if (!::SCNetworkReachabilityGetFlags(mReachability, &flags)) {
+ mStatusKnown = false;
+ return;
+ }
+
+ bool reachable = (flags & kSCNetworkFlagsReachable) != 0;
+ bool needsConnection = (flags & kSCNetworkFlagsConnectionRequired) != 0;
+
+ mLinkUp = (reachable && !needsConnection);
+ mStatusKnown = true;
+}
+
+void
+nsNetworkLinkService::SendEvent(bool aNetworkChanged)
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (!observerService) {
+ return;
+ }
+
+ const char *event;
+ if (aNetworkChanged) {
+ if (!mAllowChangedEvent) {
+ return;
+ }
+ event = NS_NETWORK_LINK_DATA_CHANGED;
+ } else if (!mStatusKnown) {
+ event = NS_NETWORK_LINK_DATA_UNKNOWN;
+ } else {
+ event = mLinkUp ? NS_NETWORK_LINK_DATA_UP
+ : NS_NETWORK_LINK_DATA_DOWN;
+ }
+ LOG(("SendEvent: network is '%s'\n", event));
+
+ observerService->NotifyObservers(static_cast<nsINetworkLinkService*>(this),
+ NS_NETWORK_LINK_TOPIC,
+ NS_ConvertASCIItoUTF16(event).get());
+}
+
+/* static */
+void
+nsNetworkLinkService::ReachabilityChanged(SCNetworkReachabilityRef target,
+ SCNetworkConnectionFlags flags,
+ void *info)
+{
+ nsNetworkLinkService *service =
+ static_cast<nsNetworkLinkService*>(info);
+
+ service->UpdateReachability();
+ service->SendEvent(false);
+ service->calculateNetworkId();
+}
diff --git a/netwerk/system/moz.build b/netwerk/system/moz.build
new file mode 100644
index 000000000..08c41342b
--- /dev/null
+++ b/netwerk/system/moz.build
@@ -0,0 +1,17 @@
+# -*- 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'] == 'WINNT':
+ DIRS += ['win32']
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ DIRS += ['mac']
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+ DIRS += ['android']
+
+elif CONFIG['OS_ARCH'] == 'Linux':
+ DIRS += ['linux']
diff --git a/netwerk/system/win32/moz.build b/netwerk/system/win32/moz.build
new file mode 100644
index 000000000..31b46b6b5
--- /dev/null
+++ b/netwerk/system/win32/moz.build
@@ -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'] == 'WINNT':
+ SOURCES += [
+ 'nsNotifyAddrListener.cpp',
+ ]
+
+FINAL_LIBRARY = 'xul'
diff --git a/netwerk/system/win32/nsNotifyAddrListener.cpp b/netwerk/system/win32/nsNotifyAddrListener.cpp
new file mode 100644
index 000000000..5d1ec3a61
--- /dev/null
+++ b/netwerk/system/win32/nsNotifyAddrListener.cpp
@@ -0,0 +1,743 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set et sw=4 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/. */
+
+// We define this to make our use of inet_ntoa() pass. The "proper" function
+// inet_ntop() doesn't exist on Windows XP.
+#define _WINSOCK_DEPRECATED_NO_WARNINGS
+
+#include <stdarg.h>
+#include <windef.h>
+#include <winbase.h>
+#include <wingdi.h>
+#include <winuser.h>
+#include <ole2.h>
+#include <netcon.h>
+#include <objbase.h>
+#include <winsock2.h>
+#include <ws2ipdef.h>
+#include <tcpmib.h>
+#include <iphlpapi.h>
+#include <netioapi.h>
+#include <iprtrmib.h>
+#include "plstr.h"
+#include "mozilla/Logging.h"
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNotifyAddrListener.h"
+#include "nsString.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Services.h"
+#include "nsCRT.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/Base64.h"
+#include "mozilla/Telemetry.h"
+
+#include <iptypes.h>
+#include <iphlpapi.h>
+
+using namespace mozilla;
+
+static LazyLogModule gNotifyAddrLog("nsNotifyAddr");
+#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
+
+static HMODULE sNetshell;
+static decltype(NcFreeNetconProperties)* sNcFreeNetconProperties;
+
+static HMODULE sIphlpapi;
+static decltype(NotifyIpInterfaceChange)* sNotifyIpInterfaceChange;
+static decltype(CancelMibChangeNotify2)* sCancelMibChangeNotify2;
+
+#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
+#define NETWORK_NOTIFY_IPV6_PREF "network.notify.IPv6"
+
+// period during which to absorb subsequent network change events, in
+// milliseconds
+static const unsigned int kNetworkChangeCoalescingPeriod = 1000;
+
+static void InitIphlpapi(void)
+{
+ if (!sIphlpapi) {
+ sIphlpapi = LoadLibraryW(L"Iphlpapi.dll");
+ if (sIphlpapi) {
+ sNotifyIpInterfaceChange = (decltype(NotifyIpInterfaceChange)*)
+ GetProcAddress(sIphlpapi, "NotifyIpInterfaceChange");
+ sCancelMibChangeNotify2 = (decltype(CancelMibChangeNotify2)*)
+ GetProcAddress(sIphlpapi, "CancelMibChangeNotify2");
+ } else {
+ NS_WARNING("Failed to load Iphlpapi.dll - cannot detect network"
+ " changes!");
+ }
+ }
+}
+
+static void InitNetshellLibrary(void)
+{
+ if (!sNetshell) {
+ sNetshell = LoadLibraryW(L"Netshell.dll");
+ if (sNetshell) {
+ sNcFreeNetconProperties = (decltype(NcFreeNetconProperties)*)
+ GetProcAddress(sNetshell, "NcFreeNetconProperties");
+ }
+ }
+}
+
+static void FreeDynamicLibraries(void)
+{
+ if (sNetshell) {
+ sNcFreeNetconProperties = nullptr;
+ FreeLibrary(sNetshell);
+ sNetshell = nullptr;
+ }
+ if (sIphlpapi) {
+ sNotifyIpInterfaceChange = nullptr;
+ sCancelMibChangeNotify2 = nullptr;
+ FreeLibrary(sIphlpapi);
+ sIphlpapi = nullptr;
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsNotifyAddrListener,
+ nsINetworkLinkService,
+ nsIRunnable,
+ nsIObserver)
+
+nsNotifyAddrListener::nsNotifyAddrListener()
+ : mLinkUp(true) // assume true by default
+ , mStatusKnown(false)
+ , mCheckAttempted(false)
+ , mCheckEvent(nullptr)
+ , mShutdown(false)
+ , mIPInterfaceChecksum(0)
+ , mAllowChangedEvent(true)
+ , mIPv6Changes(false)
+ , mCoalescingActive(false)
+{
+ InitIphlpapi();
+}
+
+nsNotifyAddrListener::~nsNotifyAddrListener()
+{
+ NS_ASSERTION(!mThread, "nsNotifyAddrListener thread shutdown failed");
+ FreeDynamicLibraries();
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::GetIsLinkUp(bool *aIsUp)
+{
+ if (!mCheckAttempted && !mStatusKnown) {
+ mCheckAttempted = true;
+ CheckLinkStatus();
+ }
+
+ *aIsUp = mLinkUp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::GetLinkStatusKnown(bool *aIsUp)
+{
+ *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;
+}
+
+static bool macAddr(BYTE addr[], DWORD len, char *buf, size_t buflen)
+{
+ buf[0] = '\0';
+ if (!addr || !len || (len * 3 > buflen)) {
+ return false;
+ }
+
+ for (DWORD i = 0; i < len; ++i) {
+ sprintf_s(buf + (i * 3), sizeof(buf + (i * 3)),
+ "%02x%s", addr[i], (i == len-1) ? "" : ":");
+ }
+ return true;
+}
+
+bool nsNotifyAddrListener::findMac(char *gateway)
+{
+ // query for buffer size needed
+ DWORD dwActualSize = 0;
+ bool found = FALSE;
+
+ // GetIpNetTable gets the IPv4 to physical address mapping table
+ DWORD status = GetIpNetTable(NULL, &dwActualSize, FALSE);
+ if (status == ERROR_INSUFFICIENT_BUFFER) {
+ // the expected route, now with a known buffer size
+ UniquePtr <char[]>buf(new char[dwActualSize]);
+ PMIB_IPNETTABLE pIpNetTable =
+ reinterpret_cast<PMIB_IPNETTABLE>(&buf[0]);
+
+ status = GetIpNetTable(pIpNetTable, &dwActualSize, FALSE);
+
+ if (status == NO_ERROR) {
+ for (DWORD i = 0; i < pIpNetTable->dwNumEntries; ++i) {
+ DWORD dwCurrIndex = pIpNetTable->table[i].dwIndex;
+ char hw[256];
+
+ if (!macAddr(pIpNetTable->table[i].bPhysAddr,
+ pIpNetTable->table[i].dwPhysAddrLen,
+ hw, sizeof(hw))) {
+ // failed to get the MAC
+ continue;
+ }
+
+ struct in_addr addr;
+ addr.s_addr = pIpNetTable->table[i].dwAddr;
+
+ if (!strcmp(gateway, inet_ntoa(addr))) {
+ LOG(("networkid: MAC %s\n", hw));
+ nsAutoCString mac(hw);
+ // This 'addition' could potentially be a
+ // fixed number from the profile or something.
+ nsAutoCString addition("local-rubbish");
+ nsAutoCString output;
+ SHA1Sum sha1;
+ nsCString combined(mac + addition);
+ sha1.update(combined.get(), combined.Length());
+ uint8_t digest[SHA1Sum::kHashSize];
+ sha1.finish(digest);
+ nsCString newString(reinterpret_cast<char*>(digest),
+ SHA1Sum::kHashSize);
+ Base64Encode(newString, output);
+ LOG(("networkid: id %s\n", output.get()));
+ if (mNetworkId != output) {
+ // new id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 1);
+ mNetworkId = output;
+ }
+ else {
+ // same id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 2);
+ }
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+ return found;
+}
+
+// returns 'true' when the gw is found and stored
+static bool defaultgw(char *aGateway, size_t aGatewayLen)
+{
+ PMIB_IPFORWARDTABLE pIpForwardTable = NULL;
+
+ DWORD dwSize = 0;
+ if (GetIpForwardTable(NULL, &dwSize, 0) != ERROR_INSUFFICIENT_BUFFER) {
+ return false;
+ }
+
+ UniquePtr <char[]>buf(new char[dwSize]);
+ pIpForwardTable = reinterpret_cast<PMIB_IPFORWARDTABLE>(&buf[0]);
+
+ // Note that the IPv4 addresses returned in GetIpForwardTable entries are
+ // in network byte order
+
+ DWORD retVal = GetIpForwardTable(pIpForwardTable, &dwSize, 0);
+ if (retVal == NO_ERROR) {
+ for (unsigned int i = 0; i < pIpForwardTable->dwNumEntries; ++i) {
+ // Convert IPv4 addresses to strings
+ struct in_addr IpAddr;
+ IpAddr.S_un.S_addr = static_cast<u_long>
+ (pIpForwardTable->table[i].dwForwardDest);
+ char *ipStr = inet_ntoa(IpAddr);
+ if (ipStr && !strcmp("0.0.0.0", ipStr)) {
+ // Default gateway!
+ IpAddr.S_un.S_addr = static_cast<u_long>
+ (pIpForwardTable->table[i].dwForwardNextHop);
+ ipStr = inet_ntoa(IpAddr);
+ if (ipStr) {
+ strcpy_s(aGateway, aGatewayLen, ipStr);
+ return true;
+ }
+ }
+ } // for loop
+ }
+
+ return false;
+}
+
+//
+// Figure out the current "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).
+//
+void nsNotifyAddrListener::calculateNetworkId(void)
+{
+ bool found = FALSE;
+ char gateway[128];
+ if (defaultgw(gateway, sizeof(gateway) )) {
+ found = findMac(gateway);
+ }
+ if (!found) {
+ // no id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 0);
+ }
+}
+
+// Static Callback function for NotifyIpInterfaceChange API.
+static void WINAPI OnInterfaceChange(PVOID callerContext,
+ PMIB_IPINTERFACE_ROW row,
+ MIB_NOTIFICATION_TYPE notificationType)
+{
+ nsNotifyAddrListener *notify = static_cast<nsNotifyAddrListener*>(callerContext);
+ notify->CheckLinkStatus();
+}
+
+DWORD
+nsNotifyAddrListener::nextCoalesceWaitTime()
+{
+ // check if coalescing period should continue
+ double period = (TimeStamp::Now() - mChangeTime).ToMilliseconds();
+ if (period >= kNetworkChangeCoalescingPeriod) {
+ calculateNetworkId();
+ SendEvent(NS_NETWORK_LINK_DATA_CHANGED);
+ mCoalescingActive = false;
+ return INFINITE; // return default
+ } else {
+ // wait no longer than to the end of the period
+ return static_cast<DWORD>
+ (kNetworkChangeCoalescingPeriod - period);
+ }
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::Run()
+{
+ PR_SetCurrentThreadName("Link Monitor");
+
+ mStartTime = TimeStamp::Now();
+
+ calculateNetworkId();
+
+ DWORD waitTime = INFINITE;
+
+ if (!sNotifyIpInterfaceChange || !sCancelMibChangeNotify2 || !mIPv6Changes) {
+ // For Windows versions which are older than Vista which lack
+ // NotifyIpInterfaceChange. Note this means no IPv6 support.
+ HANDLE ev = CreateEvent(nullptr, FALSE, FALSE, nullptr);
+ NS_ENSURE_TRUE(ev, NS_ERROR_OUT_OF_MEMORY);
+
+ HANDLE handles[2] = { ev, mCheckEvent };
+ OVERLAPPED overlapped = { 0 };
+ bool shuttingDown = false;
+
+ overlapped.hEvent = ev;
+ while (!shuttingDown) {
+ HANDLE h;
+ DWORD ret = NotifyAddrChange(&h, &overlapped);
+
+ if (ret == ERROR_IO_PENDING) {
+ ret = WaitForMultipleObjects(2, handles, FALSE, waitTime);
+ if (ret == WAIT_OBJECT_0) {
+ CheckLinkStatus();
+ } else if (!mShutdown) {
+ waitTime = nextCoalesceWaitTime();
+ } else {
+ shuttingDown = true;
+ }
+ } else {
+ shuttingDown = true;
+ }
+ }
+ CloseHandle(ev);
+ } else {
+ // Windows Vista and newer versions.
+ HANDLE interfacechange;
+ // The callback will simply invoke CheckLinkStatus()
+ DWORD ret = sNotifyIpInterfaceChange(
+ AF_UNSPEC, // IPv4 and IPv6
+ (PIPINTERFACE_CHANGE_CALLBACK)OnInterfaceChange,
+ this, // pass to callback
+ false, // no initial notification
+ &interfacechange);
+
+ if (ret == NO_ERROR) {
+ do {
+ ret = WaitForSingleObject(mCheckEvent, waitTime);
+ if (!mShutdown) {
+ waitTime = nextCoalesceWaitTime();
+ }
+ else {
+ break;
+ }
+ } while (ret != WAIT_FAILED);
+ sCancelMibChangeNotify2(interfacechange);
+ } else {
+ LOG(("Link Monitor: sNotifyIpInterfaceChange returned %d\n",
+ (int)ret));
+ }
+ }
+ return NS_OK;
+}
+
+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);
+ Preferences::AddBoolVarCache(&mIPv6Changes,
+ NETWORK_NOTIFY_IPV6_PREF, false);
+
+ mCheckEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
+ NS_ENSURE_TRUE(mCheckEvent, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = NS_NewThread(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");
+
+ if (!mCheckEvent)
+ return NS_OK;
+
+ mShutdown = true;
+ SetEvent(mCheckEvent);
+
+ nsresult rv = mThread ? mThread->Shutdown() : NS_OK;
+
+ // Have to break the cycle here, otherwise nsNotifyAddrListener holds
+ // onto the thread and the thread holds onto the nsNotifyAddrListener
+ // via its mRunnable
+ mThread = nullptr;
+
+ CloseHandle(mCheckEvent);
+ mCheckEvent = 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;
+ SetEvent(mCheckEvent);
+ 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: network is '%s'\n", aEventID));
+
+ nsresult rv;
+ 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;
+}
+
+
+// Bug 465158 features an explanation for this check. ICS being "Internet
+// Connection Sharing). The description says it is always IP address
+// 192.168.0.1 for this case.
+bool
+nsNotifyAddrListener::CheckICSGateway(PIP_ADAPTER_ADDRESSES aAdapter)
+{
+ if (!aAdapter->FirstUnicastAddress)
+ return false;
+
+ LPSOCKADDR aAddress = aAdapter->FirstUnicastAddress->Address.lpSockaddr;
+ if (!aAddress)
+ return false;
+
+ PSOCKADDR_IN in_addr = (PSOCKADDR_IN)aAddress;
+ bool isGateway = (aAddress->sa_family == AF_INET &&
+ in_addr->sin_addr.S_un.S_un_b.s_b1 == 192 &&
+ in_addr->sin_addr.S_un.S_un_b.s_b2 == 168 &&
+ in_addr->sin_addr.S_un.S_un_b.s_b3 == 0 &&
+ in_addr->sin_addr.S_un.S_un_b.s_b4 == 1);
+
+ if (isGateway)
+ isGateway = CheckICSStatus(aAdapter->FriendlyName);
+
+ return isGateway;
+}
+
+bool
+nsNotifyAddrListener::CheckICSStatus(PWCHAR aAdapterName)
+{
+ InitNetshellLibrary();
+
+ // This method enumerates all privately shared connections and checks if some
+ // of them has the same name as the one provided in aAdapterName. If such
+ // connection is found in the collection the adapter is used as ICS gateway
+ bool isICSGatewayAdapter = false;
+
+ HRESULT hr;
+ RefPtr<INetSharingManager> netSharingManager;
+ hr = CoCreateInstance(
+ CLSID_NetSharingManager,
+ nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_INetSharingManager,
+ getter_AddRefs(netSharingManager));
+
+ RefPtr<INetSharingPrivateConnectionCollection> privateCollection;
+ if (SUCCEEDED(hr)) {
+ hr = netSharingManager->get_EnumPrivateConnections(
+ ICSSC_DEFAULT,
+ getter_AddRefs(privateCollection));
+ }
+
+ RefPtr<IEnumNetSharingPrivateConnection> privateEnum;
+ if (SUCCEEDED(hr)) {
+ RefPtr<IUnknown> privateEnumUnknown;
+ hr = privateCollection->get__NewEnum(getter_AddRefs(privateEnumUnknown));
+ if (SUCCEEDED(hr)) {
+ hr = privateEnumUnknown->QueryInterface(
+ IID_IEnumNetSharingPrivateConnection,
+ getter_AddRefs(privateEnum));
+ }
+ }
+
+ if (SUCCEEDED(hr)) {
+ ULONG fetched;
+ VARIANT connectionVariant;
+ while (!isICSGatewayAdapter &&
+ SUCCEEDED(hr = privateEnum->Next(1, &connectionVariant,
+ &fetched)) &&
+ fetched) {
+ if (connectionVariant.vt != VT_UNKNOWN) {
+ // We should call VariantClear here but it needs to link
+ // with oleaut32.lib that produces a Ts incrase about 10ms
+ // that is undesired. As it is quit unlikely the result would
+ // be of a different type anyway, let's pass the variant
+ // unfreed here.
+ NS_ERROR("Variant of unexpected type, expecting VT_UNKNOWN, we probably leak it!");
+ continue;
+ }
+
+ RefPtr<INetConnection> connection;
+ if (SUCCEEDED(connectionVariant.punkVal->QueryInterface(
+ IID_INetConnection,
+ getter_AddRefs(connection)))) {
+ connectionVariant.punkVal->Release();
+
+ NETCON_PROPERTIES *properties;
+ if (SUCCEEDED(connection->GetProperties(&properties))) {
+ if (!wcscmp(properties->pszwName, aAdapterName))
+ isICSGatewayAdapter = true;
+
+ if (sNcFreeNetconProperties)
+ sNcFreeNetconProperties(properties);
+ }
+ }
+ }
+ }
+
+ return isICSGatewayAdapter;
+}
+
+DWORD
+nsNotifyAddrListener::CheckAdaptersAddresses(void)
+{
+ ULONG len = 16384;
+
+ PIP_ADAPTER_ADDRESSES adapterList = (PIP_ADAPTER_ADDRESSES) moz_xmalloc(len);
+
+ ULONG flags = GAA_FLAG_SKIP_DNS_SERVER|GAA_FLAG_SKIP_MULTICAST|
+ GAA_FLAG_SKIP_ANYCAST;
+
+ DWORD ret = GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, adapterList, &len);
+ if (ret == ERROR_BUFFER_OVERFLOW) {
+ free(adapterList);
+ adapterList = static_cast<PIP_ADAPTER_ADDRESSES> (moz_xmalloc(len));
+
+ ret = GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, adapterList, &len);
+ }
+
+ if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) {
+ free(adapterList);
+ return ERROR_NOT_SUPPORTED;
+ }
+
+ //
+ // Since NotifyIpInterfaceChange() signals a change more often than we
+ // think is a worthy change, we checksum the entire state of all interfaces
+ // that are UP. If the checksum is the same as previous check, nothing
+ // of interest changed!
+ //
+ ULONG sum = 0;
+
+ if (ret == ERROR_SUCCESS) {
+ bool linkUp = false;
+
+ for (PIP_ADAPTER_ADDRESSES adapter = adapterList; adapter;
+ adapter = adapter->Next) {
+ if (adapter->OperStatus != IfOperStatusUp ||
+ !adapter->FirstUnicastAddress ||
+ adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK ||
+ CheckICSGateway(adapter) ) {
+ continue;
+ }
+
+ // Add chars from AdapterName to the checksum.
+ for (int i = 0; adapter->AdapterName[i]; ++i) {
+ sum <<= 2;
+ sum += adapter->AdapterName[i];
+ }
+
+ // Add bytes from each socket address to the checksum.
+ for (PIP_ADAPTER_UNICAST_ADDRESS pip = adapter->FirstUnicastAddress;
+ pip; pip = pip->Next) {
+ SOCKET_ADDRESS *sockAddr = &pip->Address;
+ for (int i = 0; i < sockAddr->iSockaddrLength; ++i) {
+ sum += (reinterpret_cast<unsigned char *>
+ (sockAddr->lpSockaddr))[i];
+ }
+ }
+ linkUp = true;
+ }
+ mLinkUp = linkUp;
+ mStatusKnown = true;
+ }
+ free(adapterList);
+
+ if (mLinkUp) {
+ /* Store the checksum only if one or more interfaces are up */
+ mIPInterfaceChecksum = sum;
+ }
+
+ CoUninitialize();
+
+ return ret;
+}
+
+/**
+ * Checks the status of all network adapters. If one is up and has a valid IP
+ * address, sets mLinkUp to true. Sets mStatusKnown to true if the link status
+ * is definitive.
+ */
+void
+nsNotifyAddrListener::CheckLinkStatus(void)
+{
+ DWORD ret;
+ const char *event;
+ bool prevLinkUp = mLinkUp;
+ ULONG prevCsum = mIPInterfaceChecksum;
+
+ LOG(("check status of all network adapters\n"));
+
+ // The CheckAdaptersAddresses call is very expensive (~650 milliseconds),
+ // so we don't want to call it synchronously. Instead, we just start up
+ // assuming we have a network link, but we'll report that the status is
+ // unknown.
+ if (NS_IsMainThread()) {
+ NS_WARNING("CheckLinkStatus called on main thread! No check "
+ "performed. Assuming link is up, status is unknown.");
+ mLinkUp = true;
+
+ if (!mStatusKnown) {
+ event = NS_NETWORK_LINK_DATA_UNKNOWN;
+ } else if (!prevLinkUp) {
+ event = NS_NETWORK_LINK_DATA_UP;
+ } else {
+ // Known status and it was already UP
+ event = nullptr;
+ }
+
+ if (event) {
+ SendEvent(event);
+ }
+ } else {
+ ret = CheckAdaptersAddresses();
+ if (ret != ERROR_SUCCESS) {
+ mLinkUp = true;
+ }
+
+ if (mLinkUp && (prevCsum != mIPInterfaceChecksum)) {
+ TimeDuration since = TimeStamp::Now() - mStartTime;
+
+ // Network is online. Topology has changed. Always send CHANGED
+ // before UP - if allowed to and having cooled down.
+ if (mAllowChangedEvent && (since.ToMilliseconds() > 2000)) {
+ NetworkChanged();
+ }
+ }
+ if (prevLinkUp != mLinkUp) {
+ // UP/DOWN status changed, send appropriate UP/DOWN event
+ SendEvent(mLinkUp ?
+ NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN);
+ }
+ }
+}
diff --git a/netwerk/system/win32/nsNotifyAddrListener.h b/netwerk/system/win32/nsNotifyAddrListener.h
new file mode 100644
index 000000000..47a9ed645
--- /dev/null
+++ b/netwerk/system/win32/nsNotifyAddrListener.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set et sw=4 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_H_
+#define NSNOTIFYADDRLISTENER_H_
+
+#include <windows.h>
+#include <winsock2.h>
+#include <iptypes.h>
+#include "nsINetworkLinkService.h"
+#include "nsIRunnable.h"
+#include "nsIObserver.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/TimeStamp.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);
+ void CheckLinkStatus(void);
+
+protected:
+ class ChangeEvent : public mozilla::Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ ChangeEvent(nsINetworkLinkService *aService, const char *aEventID)
+ : mService(aService), mEventID(aEventID) {
+ }
+ private:
+ nsCOMPtr<nsINetworkLinkService> mService;
+ const char *mEventID;
+ };
+
+ bool mLinkUp;
+ bool mStatusKnown;
+ bool mCheckAttempted;
+
+ nsresult Shutdown(void);
+ nsresult SendEvent(const char *aEventID);
+
+ DWORD CheckAdaptersAddresses(void);
+
+ // Checks for an Internet Connection Sharing (ICS) gateway.
+ bool CheckICSGateway(PIP_ADAPTER_ADDRESSES aAdapter);
+ bool CheckICSStatus(PWCHAR aAdapterName);
+
+ nsCOMPtr<nsIThread> mThread;
+
+private:
+ // Returns the new timeout period for coalescing (or INFINITE)
+ DWORD nextCoalesceWaitTime();
+
+ // Called for every detected network change
+ nsresult NetworkChanged();
+
+ // Figure out the current network identification
+ void calculateNetworkId(void);
+ bool findMac(char *gateway);
+ nsCString mNetworkId;
+
+ HANDLE mCheckEvent;
+
+ // set true when mCheckEvent means shutdown
+ bool mShutdown;
+
+ // This is a checksum of various meta data for all network interfaces
+ // considered UP at last check.
+ ULONG mIPInterfaceChecksum;
+
+ // 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;
+
+ // Time stamp for first event during coalescing
+ mozilla::TimeStamp mChangeTime;
+};
+
+#endif /* NSNOTIFYADDRLISTENER_H_ */