summaryrefslogtreecommitdiff
path: root/netwerk/system/mac/nsNetworkLinkService.mm
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/system/mac/nsNetworkLinkService.mm')
-rw-r--r--netwerk/system/mac/nsNetworkLinkService.mm526
1 files changed, 526 insertions, 0 deletions
diff --git a/netwerk/system/mac/nsNetworkLinkService.mm b/netwerk/system/mac/nsNetworkLinkService.mm
new file mode 100644
index 0000000000..5b2d7575ac
--- /dev/null
+++ b/netwerk/system/mac/nsNetworkLinkService.mm
@@ -0,0 +1,526 @@
+/* -*- 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 "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
+ mNetworkId = output;
+ }
+ else {
+ // same id
+ }
+ found = true;
+ }
+ }
+ if (!found) {
+ // no id
+ }
+}
+
+
+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();
+}