Bug 1008091 - Send network change events on FxOS and Linux. r=sworkman
☠☠ backed out by 980d33b2ca6c ☠ ☠
authorDaniel Stenberg <daniel@haxx.se>
Tue, 14 Oct 2014 04:44:00 -0400
changeset 210350 8754d79ca14c1b58d13408533637066b3ddce47b
parent 210349 f8bb9368beb1f0e7d4d50efccf69beee0af5d372
child 210351 1ac8890ee0a354c51824bc0597ea00c5b72110dc
push id27651
push userkwierso@gmail.com
push dateWed, 15 Oct 2014 00:18:02 +0000
treeherdermozilla-central@62f0b771583c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssworkman
bugs1008091
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1008091 - Send network change events on FxOS and Linux. r=sworkman
netwerk/build/moz.build
netwerk/build/nsNetModule.cpp
netwerk/system/linux/moz.build
netwerk/system/linux/nsNotifyAddrListener_Linux.cpp
netwerk/system/linux/nsNotifyAddrListener_Linux.h
netwerk/system/moz.build
--- a/netwerk/build/moz.build
+++ b/netwerk/build/moz.build
@@ -53,16 +53,21 @@ if CONFIG['MOZ_ENABLE_QTNETWORK']:
         '../system/qt',
     ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     LOCAL_INCLUDES += [
         '../system/android',
     ]
 
+elif CONFIG['OS_ARCH'] == 'Linux':
+    LOCAL_INCLUDES += [
+        '../system/linux',
+    ]
+
 if CONFIG['NECKO_COOKIES']:
     LOCAL_INCLUDES += [
         '../cookie',
     ]
 
 if CONFIG['NECKO_WIFI']:
     LOCAL_INCLUDES += [
         '../wifi',
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -372,16 +372,19 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsNo
 #include "nsNetworkLinkService.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsNetworkLinkService, Init)
 #elif defined(MOZ_ENABLE_QTNETWORK)
 #include "nsQtNetworkLinkService.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsQtNetworkLinkService, Init)
 #elif defined(MOZ_WIDGET_ANDROID)
 #include "nsAndroidNetworkLinkService.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAndroidNetworkLinkService)
+#elif defined(XP_LINUX)
+#include "nsNotifyAddrListener_Linux.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsNotifyAddrListener, Init)
 #endif
 
 ///////////////////////////////////////////////////////////////////////////////
 
 #ifdef NECKO_PROTOCOL_ftp
 #include "nsFTPDirListingConv.h"
 nsresult NS_NewFTPDirListingConv(nsFTPDirListingConv** result);
 #endif
@@ -793,16 +796,18 @@ NS_DEFINE_NAMED_CID(NS_RTSPPROTOCOLHANDL
 #if defined(XP_WIN)
 NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
 #elif defined(MOZ_WIDGET_COCOA)
 NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
 #elif defined(MOZ_ENABLE_QTNETWORK)
 NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
 #elif defined(MOZ_WIDGET_ANDROID)
 NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
+#elif defined(XP_LINUX)
+NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_SERIALIZATION_HELPER_CID);
 NS_DEFINE_NAMED_CID(NS_REDIRECTCHANNELREGISTRAR_CID);
 NS_DEFINE_NAMED_CID(NS_CACHE_STORAGE_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_NETWORKPREDICTOR_CID);
 
 static const mozilla::Module::CIDEntry kNeckoCIDs[] = {
     { &kNS_IOSERVICE_CID, false, nullptr, nsIOServiceConstructor },
@@ -937,16 +942,18 @@ static const mozilla::Module::CIDEntry k
 #if defined(XP_WIN)
     { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsNotifyAddrListenerConstructor },
 #elif defined(MOZ_WIDGET_COCOA)
     { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsNetworkLinkServiceConstructor },
 #elif defined(MOZ_ENABLE_QTNETWORK)
     { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsQtNetworkLinkServiceConstructor },
 #elif defined(MOZ_WIDGET_ANDROID)
     { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsAndroidNetworkLinkServiceConstructor },
+#elif defined(XP_LINUX)
+    { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsNotifyAddrListenerConstructor },
 #endif
     { &kNS_SERIALIZATION_HELPER_CID, false, nullptr, nsSerializationHelperConstructor },
     { &kNS_REDIRECTCHANNELREGISTRAR_CID, false, nullptr, RedirectChannelRegistrarConstructor },
     { &kNS_CACHE_STORAGE_SERVICE_CID, false, nullptr, CacheStorageServiceConstructor },
     { &kNS_NETWORKPREDICTOR_CID, false, nullptr, mozilla::net::Predictor::Create },
     { nullptr }
 };
 
@@ -1084,16 +1091,18 @@ static const mozilla::Module::ContractID
 #if defined(XP_WIN)
     { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
 #elif defined(MOZ_WIDGET_COCOA)
     { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
 #elif defined(MOZ_ENABLE_QTNETWORK)
     { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
 #elif defined(MOZ_WIDGET_ANDROID)
     { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
+#elif defined(XP_LINUX)
+    { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
 #endif
     { NS_SERIALIZATION_HELPER_CONTRACTID, &kNS_SERIALIZATION_HELPER_CID },
     { NS_REDIRECTCHANNELREGISTRAR_CONTRACTID, &kNS_REDIRECTCHANNELREGISTRAR_CID },
     { NS_CACHE_STORAGE_SERVICE_CONTRACTID, &kNS_CACHE_STORAGE_SERVICE_CID },
     { NS_CACHE_STORAGE_SERVICE_CONTRACTID2, &kNS_CACHE_STORAGE_SERVICE_CID },
     { NS_NETWORKPREDICTOR_CONTRACTID, &kNS_NETWORKPREDICTOR_CID },
     { nullptr }
 };
new file mode 100644
--- /dev/null
+++ b/netwerk/system/linux/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; c-basic-offset: 4; 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',
+    ]
+
+FAIL_ON_WARNINGS = True
+
+FINAL_LIBRARY = 'xul'
new file mode 100644
--- /dev/null
+++ b/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp
@@ -0,0 +1,269 @@
+/* -*- 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 "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNotifyAddrListener_Linux.h"
+#include "nsString.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+
+#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)
+{
+}
+
+nsNotifyAddrListener::~nsNotifyAddrListener()
+{
+    NS_ASSERTION(!mThread, "nsNotifyAddrListener thread shutdown failed");
+
+    close(mShutdownPipe[0]);
+    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;
+}
+
+void nsNotifyAddrListener::OnNetlinkMessage(int aNetlinkSocket)
+{
+    struct  nlmsghdr *nlh;
+    struct  rtmsg *route_entry;
+    char buffer[4095];
+
+    NS_ASSERTION(aNetlinkSocket >= 0, "bad aNetlinkSocket");
+
+    // Receiving netlink socket data
+    ssize_t rc = recv(aNetlinkSocket, buffer, sizeof(buffer), 0);
+    if (rc < 0) {
+        // LOG this?
+        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)) {
+
+        if (NLMSG_DONE == nlh->nlmsg_type) {
+            break;
+        }
+
+        switch(nlh->nlmsg_type) {
+        case RTM_DELROUTE:
+        case RTM_NEWROUTE:
+            // Get the route data
+            route_entry = static_cast<struct rtmsg *>(NLMSG_DATA(nlh));
+
+            // We are just intrested in main routing table
+            if (route_entry->rtm_table != RT_TABLE_MAIN)
+                continue;
+
+            networkChange = true;
+            break;
+
+        case RTM_NEWADDR:
+            networkChange = true;
+            break;
+
+        default:
+            continue;
+        }
+    }
+
+    if (networkChange && mAllowChangedEvent) {
+        SendEvent(NS_NETWORK_LINK_DATA_CHANGED);
+    }
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::Run()
+{
+    PR_SetCurrentThreadName("Link Monitor");
+
+    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_ROUTE | RTMGRP_IPV4_IFADDR |
+        RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE;
+
+    if (bind(netlinkSocket, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+        // failure!
+        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;
+
+    nsresult rv = NS_OK;
+    bool shutdown = false;
+    while (!shutdown) {
+        int rc = poll(fds, 2, -1);
+        if (rc > 0) {
+            if (fds[0].revents & POLLIN) {
+                // shutdown, abort the loop!
+                shutdown = true;
+            } else if (fds[1].revents & POLLIN) {
+                OnNetlinkMessage(netlinkSocket);
+            }
+        } else if (rc < 0) {
+            rv = NS_ERROR_FAILURE;
+            break;
+        }
+    }
+
+    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);
+
+    rv = NS_NewThread(getter_AddRefs(mThread), this);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (-1 == pipe(mShutdownPipe)) {
+        return NS_ERROR_FAILURE;
+    }
+
+    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");
+
+    // awake the thread to make it terminate
+    write(mShutdownPipe[1], "1", 1);
+
+    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;
+}
+
+/* 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;
+
+    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;
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/system/linux/nsNotifyAddrListener_Linux.h
@@ -0,0 +1,76 @@
+/* -*- 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/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);
+
+private:
+    class ChangeEvent : public nsRunnable {
+    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);
+
+    // Sends the network event.
+    nsresult SendEvent(const char *aEventID);
+
+    // 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;
+};
+
+#endif /* NSNOTIFYADDRLISTENER_LINUX_H_ */
--- a/netwerk/system/moz.build
+++ b/netwerk/system/moz.build
@@ -11,8 +11,10 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'coco
     DIRS += ['mac']
 
 if CONFIG['MOZ_ENABLE_QTNETWORK']:
     DIRS += ['qt']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     DIRS += ['android']
 
+elif CONFIG['OS_ARCH'] == 'Linux':
+    DIRS += ['linux']