diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /netwerk/system | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-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.build | 14 | ||||
-rw-r--r-- | netwerk/system/android/nsAndroidNetworkLinkService.cpp | 63 | ||||
-rw-r--r-- | netwerk/system/android/nsAndroidNetworkLinkService.h | 24 | ||||
-rw-r--r-- | netwerk/system/linux/moz.build | 12 | ||||
-rw-r--r-- | netwerk/system/linux/nsNotifyAddrListener_Linux.cpp | 549 | ||||
-rw-r--r-- | netwerk/system/linux/nsNotifyAddrListener_Linux.h | 100 | ||||
-rw-r--r-- | netwerk/system/mac/moz.build | 14 | ||||
-rw-r--r-- | netwerk/system/mac/nsNetworkLinkService.h | 54 | ||||
-rw-r--r-- | netwerk/system/mac/nsNetworkLinkService.mm | 530 | ||||
-rw-r--r-- | netwerk/system/moz.build | 17 | ||||
-rw-r--r-- | netwerk/system/win32/moz.build | 12 | ||||
-rw-r--r-- | netwerk/system/win32/nsNotifyAddrListener.cpp | 743 | ||||
-rw-r--r-- | netwerk/system/win32/nsNotifyAddrListener.h | 100 |
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_ */ |