Bug 1008091 - Send network change events on FxOS and Linux. r=sworkman
--- 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']