diff options
Diffstat (limited to 'netwerk/system/linux')
-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 |
3 files changed, 661 insertions, 0 deletions
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_ */ |