From 1515334cca8462a1f119fe156ada9ae808d18c95 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 15 Jan 2015 00:22:00 +0100 Subject: [PATCH] Bug 1008091 - send network change events on FxOS and Linux, r=sworkman --- netwerk/build/moz.build | 5 + netwerk/build/nsNetModule.cpp | 9 + netwerk/system/linux/moz.build | 14 + .../linux/nsNotifyAddrListener_Linux.cpp | 338 ++++++++++++++++++ .../system/linux/nsNotifyAddrListener_Linux.h | 79 ++++ netwerk/system/moz.build | 2 + xpcom/glue/FileUtils.h | 18 +- 7 files changed, 457 insertions(+), 8 deletions(-) create mode 100644 netwerk/system/linux/moz.build create mode 100644 netwerk/system/linux/nsNotifyAddrListener_Linux.cpp create mode 100644 netwerk/system/linux/nsNotifyAddrListener_Linux.h diff --git a/netwerk/build/moz.build b/netwerk/build/moz.build index 54d75851012..9611d8af945 100644 --- a/netwerk/build/moz.build +++ b/netwerk/build/moz.build @@ -58,6 +58,11 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': '../system/android', ] +elif CONFIG['OS_ARCH'] == 'Linux': + LOCAL_INCLUDES += [ + '../system/linux', + ] + if CONFIG['NECKO_COOKIES']: LOCAL_INCLUDES += [ '../cookie', diff --git a/netwerk/build/nsNetModule.cpp b/netwerk/build/nsNetModule.cpp index 070762956e7..af6a3b827fa 100644 --- a/netwerk/build/nsNetModule.cpp +++ b/netwerk/build/nsNetModule.cpp @@ -376,6 +376,9 @@ 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 /////////////////////////////////////////////////////////////////////////////// @@ -791,6 +794,8 @@ NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID); 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); @@ -935,6 +940,8 @@ static const mozilla::Module::CIDEntry kNeckoCIDs[] = { { &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 }, @@ -1082,6 +1089,8 @@ static const mozilla::Module::ContractIDEntry kNeckoContracts[] = { { 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 }, diff --git a/netwerk/system/linux/moz.build b/netwerk/system/linux/moz.build new file mode 100644 index 00000000000..4785a2fb7c1 --- /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' diff --git a/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp b/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp new file mode 100644 index 00000000000..2fbf8d9340e --- /dev/null +++ b/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp @@ -0,0 +1,338 @@ +/* -*- 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 +#include +#include +#include + +#include "nsThreadUtils.h" +#include "nsIObserverService.h" +#include "nsServiceManagerUtils.h" +#include "nsNotifyAddrListener_Linux.h" +#include "nsString.h" +#include "nsAutoPtr.h" +#include "prlog.h" + +#include "mozilla/Services.h" +#include "mozilla/Preferences.h" +#include "mozilla/FileUtils.h" + +#ifdef MOZ_NUWA_PROCESS +#include "ipc/Nuwa.h" +#endif + +#ifdef MOZ_WIDGET_GONK +#include +#endif + +/* a shorter name that better explains what it does */ +#define EINTR_RETRY(x) MOZ_TEMP_FAILURE_RETRY(x) + +using namespace mozilla; + +#if defined(PR_LOGGING) +static PRLogModuleInfo *gNotifyAddrLog = nullptr; +#define LOG(args) PR_LOG(gNotifyAddrLog, PR_LOG_DEBUG, args) +#else +#define LOG(args) +#endif + +#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) + , mChildThreadShutdown(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; +} + +void nsNotifyAddrListener::OnNetlinkMessage(int aNetlinkSocket) +{ + struct nlmsghdr *nlh; + struct rtmsg *route_entry; + + // 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]; + + // Receiving netlink socket data + ssize_t rc = EINTR_RETRY(recv(aNetlinkSocket, buffer, sizeof(buffer), 0)); + if (rc < 0) { + return; + } + size_t netlink_bytes = rc; + + nlh = reinterpret_cast(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(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() +{ + 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! + 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; + + // when in b2g emulator, work around bug 1112499 + int pollTimeout = -1; +#ifdef MOZ_WIDGET_GONK + char propQemu[PROPERTY_VALUE_MAX]; + property_get("ro.kernel.qemu", propQemu, ""); + pollTimeout = !strncmp(propQemu, "1", 1) ? 100 : -1; +#endif + + nsresult rv = NS_OK; + bool shutdown = false; + while (!shutdown) { + int rc = EINTR_RETRY(poll(fds, 2, pollTimeout)); + + 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 (mChildThreadShutdown) { + LOG(("thread shutdown via variable, dying...\n")); + shutdown = true; + } + } + + 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; +} + +#ifdef MOZ_NUWA_PROCESS +class NuwaMarkLinkMonitorThreadRunner : public nsRunnable +{ + NS_IMETHODIMP Run() MOZ_OVERRIDE + { + if (IsNuwaProcess()) { + NuwaMarkCurrentThread(nullptr, nullptr); + } + return NS_OK; + } +}; +#endif + +nsresult +nsNotifyAddrListener::Init(void) +{ +#if defined(PR_LOGGING) + if (!gNotifyAddrLog) + gNotifyAddrLog = PR_NewLogModule("nsNotifyAddr"); +#endif + + nsCOMPtr 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_NewNamedThread("Link Monitor", getter_AddRefs(mThread)); + NS_ENSURE_SUCCESS(rv, rv); + +#ifdef MOZ_NUWA_PROCESS + nsCOMPtr runner = new NuwaMarkLinkMonitorThreadRunner(); + mThread->Dispatch(runner, NS_DISPATCH_NORMAL); +#endif + + if (-1 == pipe(mShutdownPipe)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +nsNotifyAddrListener::Shutdown(void) +{ + // remove xpcom shutdown observer + nsCOMPtr 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)); + + mChildThreadShutdown = true; + + 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 = NS_OK; + nsCOMPtr 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 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 00000000000..b303f127ab0 --- /dev/null +++ b/netwerk/system/linux/nsNotifyAddrListener_Linux.h @@ -0,0 +1,79 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include + +#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 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 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 to signal child thread kill with + bool mChildThreadShutdown; +}; + +#endif /* NSNOTIFYADDRLISTENER_LINUX_H_ */ diff --git a/netwerk/system/moz.build b/netwerk/system/moz.build index db9e7df7e4c..18e923b3939 100644 --- a/netwerk/system/moz.build +++ b/netwerk/system/moz.build @@ -16,3 +16,5 @@ if CONFIG['MOZ_ENABLE_QTNETWORK']: if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': DIRS += ['android'] +elif CONFIG['OS_ARCH'] == 'Linux': + DIRS += ['linux'] diff --git a/xpcom/glue/FileUtils.h b/xpcom/glue/FileUtils.h index c5a93a5d678..510432473bb 100644 --- a/xpcom/glue/FileUtils.h +++ b/xpcom/glue/FileUtils.h @@ -161,6 +161,16 @@ void ReadAhead(filedesc_t aFd, const size_t aOffset = 0, const size_t aCount = SIZE_MAX); +#if defined(MOZ_WIDGET_GONK) || defined(XP_UNIX) +#define MOZ_TEMP_FAILURE_RETRY(exp) (__extension__({ \ + typeof (exp) _rc; \ + do { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; \ +})) +#endif + /* Define ReadSysFile() only on GONK to avoid unnecessary lubxul bloat. Also define it in debug builds, so that unit tests for it can be written and run in non-GONK builds. */ @@ -170,14 +180,6 @@ and run in non-GONK builds. */ #define ReadSysFile_PRESENT #endif /* ReadSysFile_PRESENT */ -#define MOZ_TEMP_FAILURE_RETRY(exp) (__extension__({ \ - typeof (exp) _rc; \ - do { \ - _rc = (exp); \ - } while (_rc == -1 && errno == EINTR); \ - _rc; \ -})) - /** * Read the contents of a file. * This function is intended for reading a single-lined text files from