summaryrefslogtreecommitdiffstats
path: root/netwerk/system/linux
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/system/linux')
-rw-r--r--netwerk/system/linux/moz.build12
-rw-r--r--netwerk/system/linux/nsNotifyAddrListener_Linux.cpp549
-rw-r--r--netwerk/system/linux/nsNotifyAddrListener_Linux.h100
3 files changed, 661 insertions, 0 deletions
diff --git a/netwerk/system/linux/moz.build b/netwerk/system/linux/moz.build
new file mode 100644
index 000000000..21fc5e2d2
--- /dev/null
+++ b/netwerk/system/linux/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; 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',
+ ]
+
+FINAL_LIBRARY = 'xul'
diff --git a/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp b/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp
new file mode 100644
index 000000000..c0ec9d90e
--- /dev/null
+++ b/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp
@@ -0,0 +1,549 @@
+/* -*- 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 <errno.h>
+#ifndef MOZ_WIDGET_GONK
+#include <ifaddrs.h>
+#include <net/if.h>
+#endif
+
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNotifyAddrListener_Linux.h"
+#include "nsString.h"
+#include "mozilla/Logging.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Telemetry.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include <cutils/properties.h>
+#endif
+
+/* a shorter name that better explains what it does */
+#define EINTR_RETRY(x) MOZ_TEMP_FAILURE_RETRY(x)
+
+// period during which to absorb subsequent network change events, in
+// milliseconds
+static const unsigned int kNetworkChangeCoalescingPeriod = 1000;
+
+using namespace mozilla;
+
+static LazyLogModule gNotifyAddrLog("nsNotifyAddr");
+#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
+
+#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)
+ , mCoalescingActive(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;
+}
+
+//
+// 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 nsNotifyAddrListener::calculateNetworkId(void)
+{
+ const char *kProcRoute = "/proc/net/route"; /* IPv4 routes */
+ const char *kProcArp = "/proc/net/arp";
+ bool found = false;
+
+ FILE *froute = fopen(kProcRoute, "r");
+ if (froute) {
+ char buffer[512];
+ uint32_t gw = 0;
+ char *l = fgets(buffer, sizeof(buffer), froute);
+ if (l) {
+ /* skip the title line */
+ while (l) {
+ char interf[32];
+ uint32_t dest;
+ uint32_t gateway;
+ l = fgets(buffer, sizeof(buffer), froute);
+ if (l) {
+ buffer[511]=0; /* as a precaution */
+ int val = sscanf(buffer, "%31s %x %x",
+ interf, &dest, &gateway);
+ if ((3 == val) && !dest) {
+ gw = gateway;
+ break;
+ }
+ }
+ }
+ }
+ fclose(froute);
+
+ if (gw) {
+ /* create a string to search for in the arp table */
+ char searchfor[16];
+ SprintfLiteral(searchfor, "%d.%d.%d.%d",
+ gw & 0xff,
+ (gw >> 8) & 0xff,
+ (gw >> 16) & 0xff,
+ gw >> 24);
+
+ FILE *farp = fopen(kProcArp, "r");
+ if (farp) {
+ l = fgets(buffer, sizeof(buffer), farp);
+ while (l) {
+ /* skip the title line */
+ l = fgets(buffer, sizeof(buffer), farp);
+ if (l) {
+ buffer[511]=0; /* as a precaution */
+ int p[4];
+ char type[16];
+ char flags[16];
+ char hw[32];
+ if (7 == sscanf(buffer, "%u.%u.%u.%u %15s %15s %31s",
+ &p[0], &p[1], &p[2], &p[3],
+ type, flags, hw)) {
+ uint32_t searchip = p[0] | (p[1] << 8) |
+ (p[2] << 16) | (p[3] << 24);
+ if (gw == searchip) {
+ 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
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 1);
+ mNetworkId = output;
+ }
+ else {
+ // same id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 2);
+ }
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+ fclose(farp);
+ } /* if (farp) */
+ } /* if (gw) */
+ } /* if (froute) */
+ if (!found) {
+ // no id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 0);
+ }
+}
+
+//
+// Check if there's a network interface available to do networking on.
+//
+void nsNotifyAddrListener::checkLink(void)
+{
+#ifdef MOZ_WIDGET_GONK
+ // b2g instead has NetworkManager.js which handles UP/DOWN
+#else
+ struct ifaddrs *list;
+ struct ifaddrs *ifa;
+ bool link = false;
+ bool prevLinkUp = mLinkUp;
+
+ if (getifaddrs(&list))
+ return;
+
+ // Walk through the linked list, maintaining head pointer so we can free
+ // list later
+
+ for (ifa = list; ifa != NULL; ifa = ifa->ifa_next) {
+ int family;
+ if (ifa->ifa_addr == NULL)
+ continue;
+
+ family = ifa->ifa_addr->sa_family;
+
+ if ((family == AF_INET || family == AF_INET6) &&
+ (ifa->ifa_flags & IFF_RUNNING) &&
+ !(ifa->ifa_flags & IFF_LOOPBACK)) {
+ // An interface that is UP and not loopback
+ link = true;
+ break;
+ }
+ }
+ mLinkUp = link;
+ freeifaddrs(list);
+
+ if (prevLinkUp != mLinkUp) {
+ // UP/DOWN status changed, send appropriate UP/DOWN event
+ SendEvent(mLinkUp ?
+ NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN);
+ }
+#endif
+}
+
+void nsNotifyAddrListener::OnNetlinkMessage(int aNetlinkSocket)
+{
+ struct nlmsghdr *nlh;
+
+ // 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];
+ struct rtattr *attr;
+ int attr_len;
+ const struct ifaddrmsg* newifam;
+
+
+ ssize_t rc = EINTR_RETRY(recv(aNetlinkSocket, buffer, sizeof(buffer), 0));
+ if (rc < 0) {
+ 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)) {
+ char prefixaddr[INET6_ADDRSTRLEN];
+ char localaddr[INET6_ADDRSTRLEN];
+ char* addr = nullptr;
+ prefixaddr[0] = localaddr[0] = '\0';
+
+ if (NLMSG_DONE == nlh->nlmsg_type) {
+ break;
+ }
+
+ LOG(("nsNotifyAddrListener::OnNetlinkMessage: new/deleted address\n"));
+ newifam = reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(nlh));
+
+ if ((newifam->ifa_family != AF_INET) &&
+ (newifam->ifa_family != AF_INET6)) {
+ continue;
+ }
+
+ attr = IFA_RTA (newifam);
+ attr_len = IFA_PAYLOAD (nlh);
+ for (;attr_len && RTA_OK (attr, attr_len);
+ attr = RTA_NEXT (attr, attr_len)) {
+ if (attr->rta_type == IFA_ADDRESS) {
+ if (newifam->ifa_family == AF_INET) {
+ struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
+ inet_ntop(AF_INET, in, prefixaddr, INET_ADDRSTRLEN);
+ } else {
+ struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
+ inet_ntop(AF_INET6, in, prefixaddr, INET6_ADDRSTRLEN);
+ }
+ } else if (attr->rta_type == IFA_LOCAL) {
+ if (newifam->ifa_family == AF_INET) {
+ struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
+ inet_ntop(AF_INET, in, localaddr, INET_ADDRSTRLEN);
+ } else {
+ struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
+ inet_ntop(AF_INET6, in, localaddr, INET6_ADDRSTRLEN);
+ }
+ }
+ }
+ if (localaddr[0]) {
+ addr = localaddr;
+ } else if (prefixaddr[0]) {
+ addr = prefixaddr;
+ } else {
+ continue;
+ }
+ if (nlh->nlmsg_type == RTM_NEWADDR) {
+ LOG(("nsNotifyAddrListener::OnNetlinkMessage: a new address "
+ "- %s.", addr));
+ struct ifaddrmsg* ifam;
+ nsCString addrStr;
+ addrStr.Assign(addr);
+ if (mAddressInfo.Get(addrStr, &ifam)) {
+ LOG(("nsNotifyAddrListener::OnNetlinkMessage: the address "
+ "already known."));
+ if (memcmp(ifam, newifam, sizeof(struct ifaddrmsg))) {
+ LOG(("nsNotifyAddrListener::OnNetlinkMessage: but "
+ "the address info has been changed."));
+ networkChange = true;
+ memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
+ }
+ } else {
+ networkChange = true;
+ ifam = (struct ifaddrmsg*)malloc(sizeof(struct ifaddrmsg));
+ memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
+ mAddressInfo.Put(addrStr,ifam);
+ }
+ } else {
+ LOG(("nsNotifyAddrListener::OnNetlinkMessage: an address "
+ "has been deleted - %s.", addr));
+ networkChange = true;
+ nsCString addrStr;
+ addrStr.Assign(addr);
+ mAddressInfo.Remove(addrStr);
+ }
+ }
+
+ if (networkChange && mAllowChangedEvent) {
+ NetworkChanged();
+ }
+
+ if (networkChange) {
+ checkLink();
+ }
+}
+
+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_IFADDR | RTMGRP_IPV6_IFADDR;
+
+ 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;
+
+ calculateNetworkId();
+
+ nsresult rv = NS_OK;
+ bool shutdown = false;
+ int pollWait = -1;
+ while (!shutdown) {
+ int rc = EINTR_RETRY(poll(fds, 2, pollWait));
+
+ 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 (mCoalescingActive) {
+ // check if coalescing period should continue
+ double period = (TimeStamp::Now() - mChangeTime).ToMilliseconds();
+ if (period >= kNetworkChangeCoalescingPeriod) {
+ SendEvent(NS_NETWORK_LINK_DATA_CHANGED);
+ calculateNetworkId();
+ mCoalescingActive = false;
+ pollWait = -1; // restore to default
+ } else {
+ // wait no longer than to the end of the period
+ pollWait = static_cast<int>
+ (kNetworkChangeCoalescingPeriod - period);
+ }
+ }
+ }
+
+ 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;
+}
+
+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);
+
+ if (-1 == pipe(mShutdownPipe)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = NS_NewNamedThread("Link Monitor", getter_AddRefs(mThread), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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");
+
+ 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));
+
+ 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;
+}
+
+
+/*
+ * A network event has been registered. Delay the actual sending of the event
+ * for a while and absorb subsequent events in the mean time in an effort to
+ * squash potentially many triggers into a single event.
+ * Only ever called from the same thread.
+ */
+nsresult
+nsNotifyAddrListener::NetworkChanged()
+{
+ if (mCoalescingActive) {
+ LOG(("NetworkChanged: absorbed an event (coalescing active)\n"));
+ } else {
+ // A fresh trigger!
+ mChangeTime = TimeStamp::Now();
+ mCoalescingActive = true;
+ LOG(("NetworkChanged: coalescing period started\n"));
+ }
+ return NS_OK;
+}
+
+/* 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;
+
+ LOG(("SendEvent: %s\n", aEventID));
+ nsresult rv = NS_OK;
+ 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;
+}
diff --git a/netwerk/system/linux/nsNotifyAddrListener_Linux.h b/netwerk/system/linux/nsNotifyAddrListener_Linux.h
new file mode 100644
index 000000000..a2b8f2277
--- /dev/null
+++ b/netwerk/system/linux/nsNotifyAddrListener_Linux.h
@@ -0,0 +1,100 @@
+/* -*- 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/Atomics.h"
+#include "mozilla/TimeStamp.h"
+#include "nsITimer.h"
+#include "nsClassHashtable.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 mozilla::Runnable {
+ 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);
+
+ // Called when a network change was detected
+ nsresult NetworkChanged();
+
+ // Sends the network event.
+ nsresult SendEvent(const char *aEventID);
+
+ // Figure out the current "network identification"
+ void calculateNetworkId(void);
+ nsCString mNetworkId;
+
+ // Checks if there's a network "link"
+ void checkLink(void);
+
+ // 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;
+
+ // Flag set while coalescing change events
+ bool mCoalescingActive;
+
+ // Time stamp for first event during coalescing
+ mozilla::TimeStamp mChangeTime;
+
+ // Seen Ip addresses. For Ipv6 addresses some time router renews their
+ // lifetime and we should not detect this as a network link change, so we
+ // keep info about all seen addresses.
+ nsClassHashtable<nsCStringHashKey, struct ifaddrmsg> mAddressInfo;
+ };
+
+#endif /* NSNOTIFYADDRLISTENER_LINUX_H_ */