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 id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerssworkman
bugs1008091
milestone36.0a1
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']