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/linux/nsNotifyAddrListener_Linux.cpp | |
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/linux/nsNotifyAddrListener_Linux.cpp')
-rw-r--r-- | netwerk/system/linux/nsNotifyAddrListener_Linux.cpp | 549 |
1 files changed, 549 insertions, 0 deletions
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; +} |