Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* vim:set et sw=4 ts=4: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include <stdarg.h>
8 : #include <fcntl.h>
9 : #include <poll.h>
10 : #include <errno.h>
11 : #include <ifaddrs.h>
12 : #include <net/if.h>
13 :
14 : #include "nsThreadUtils.h"
15 : #include "nsIObserverService.h"
16 : #include "nsServiceManagerUtils.h"
17 : #include "nsNotifyAddrListener_Linux.h"
18 : #include "nsString.h"
19 : #include "mozilla/Logging.h"
20 :
21 : #include "mozilla/Base64.h"
22 : #include "mozilla/FileUtils.h"
23 : #include "mozilla/Preferences.h"
24 : #include "mozilla/Services.h"
25 : #include "mozilla/SHA1.h"
26 : #include "mozilla/Sprintf.h"
27 : #include "mozilla/Telemetry.h"
28 :
29 : /* a shorter name that better explains what it does */
30 : #define EINTR_RETRY(x) MOZ_TEMP_FAILURE_RETRY(x)
31 :
32 : // period during which to absorb subsequent network change events, in
33 : // milliseconds
34 : static const unsigned int kNetworkChangeCoalescingPeriod = 1000;
35 :
36 : using namespace mozilla;
37 :
38 : static LazyLogModule gNotifyAddrLog("nsNotifyAddr");
39 : #define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
40 :
41 : #define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
42 :
43 23 : NS_IMPL_ISUPPORTS(nsNotifyAddrListener,
44 : nsINetworkLinkService,
45 : nsIRunnable,
46 : nsIObserver)
47 :
48 1 : nsNotifyAddrListener::nsNotifyAddrListener()
49 : : mLinkUp(true) // assume true by default
50 : , mStatusKnown(false)
51 : , mAllowChangedEvent(true)
52 1 : , mCoalescingActive(false)
53 : {
54 1 : mShutdownPipe[0] = -1;
55 1 : mShutdownPipe[1] = -1;
56 1 : }
57 :
58 0 : nsNotifyAddrListener::~nsNotifyAddrListener()
59 : {
60 0 : MOZ_ASSERT(!mThread, "nsNotifyAddrListener thread shutdown failed");
61 :
62 0 : if (mShutdownPipe[0] != -1) {
63 0 : EINTR_RETRY(close(mShutdownPipe[0]));
64 : }
65 0 : if (mShutdownPipe[1] != -1) {
66 0 : EINTR_RETRY(close(mShutdownPipe[1]));
67 : }
68 0 : }
69 :
70 : NS_IMETHODIMP
71 1 : nsNotifyAddrListener::GetIsLinkUp(bool *aIsUp)
72 : {
73 : // XXX This function has not yet been implemented for this platform
74 1 : *aIsUp = mLinkUp;
75 1 : return NS_OK;
76 : }
77 :
78 : NS_IMETHODIMP
79 0 : nsNotifyAddrListener::GetLinkStatusKnown(bool *aIsUp)
80 : {
81 : // XXX This function has not yet been implemented for this platform
82 0 : *aIsUp = mStatusKnown;
83 0 : return NS_OK;
84 : }
85 :
86 : NS_IMETHODIMP
87 0 : nsNotifyAddrListener::GetLinkType(uint32_t *aLinkType)
88 : {
89 0 : NS_ENSURE_ARG_POINTER(aLinkType);
90 :
91 : // XXX This function has not yet been implemented for this platform
92 0 : *aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
93 0 : return NS_OK;
94 : }
95 :
96 : //
97 : // Figure out the current "network identification" string.
98 : //
99 : // It detects the IP of the default gateway in the routing table, then the MAC
100 : // address of that IP in the ARP table before it hashes that string (to avoid
101 : // information leakage).
102 : //
103 1 : void nsNotifyAddrListener::calculateNetworkId(void)
104 : {
105 1 : const char *kProcRoute = "/proc/net/route"; /* IPv4 routes */
106 1 : const char *kProcArp = "/proc/net/arp";
107 1 : bool found = false;
108 :
109 1 : FILE *froute = fopen(kProcRoute, "r");
110 1 : if (froute) {
111 : char buffer[512];
112 1 : uint32_t gw = 0;
113 1 : char *l = fgets(buffer, sizeof(buffer), froute);
114 1 : if (l) {
115 : /* skip the title line */
116 1 : while (l) {
117 : char interf[32];
118 : uint32_t dest;
119 : uint32_t gateway;
120 1 : l = fgets(buffer, sizeof(buffer), froute);
121 1 : if (l) {
122 1 : buffer[511]=0; /* as a precaution */
123 : int val = sscanf(buffer, "%31s %x %x",
124 1 : interf, &dest, &gateway);
125 1 : if ((3 == val) && !dest) {
126 1 : gw = gateway;
127 2 : break;
128 : }
129 : }
130 : }
131 : }
132 1 : fclose(froute);
133 :
134 1 : if (gw) {
135 : /* create a string to search for in the arp table */
136 : char searchfor[16];
137 3 : SprintfLiteral(searchfor, "%d.%d.%d.%d",
138 : gw & 0xff,
139 1 : (gw >> 8) & 0xff,
140 1 : (gw >> 16) & 0xff,
141 1 : gw >> 24);
142 :
143 1 : FILE *farp = fopen(kProcArp, "r");
144 1 : if (farp) {
145 1 : l = fgets(buffer, sizeof(buffer), farp);
146 1 : while (l) {
147 : /* skip the title line */
148 1 : l = fgets(buffer, sizeof(buffer), farp);
149 1 : if (l) {
150 1 : buffer[511]=0; /* as a precaution */
151 : int p[4];
152 : char type[16];
153 : char flags[16];
154 : char hw[32];
155 1 : if (7 == sscanf(buffer, "%u.%u.%u.%u %15s %15s %31s",
156 : &p[0], &p[1], &p[2], &p[3],
157 : type, flags, hw)) {
158 2 : uint32_t searchip = p[0] | (p[1] << 8) |
159 2 : (p[2] << 16) | (p[3] << 24);
160 1 : if (gw == searchip) {
161 1 : LOG(("networkid: MAC %s\n", hw));
162 2 : nsAutoCString mac(hw);
163 : // This 'addition' could potentially be a
164 : // fixed number from the profile or something.
165 2 : nsAutoCString addition("local-rubbish");
166 2 : nsAutoCString output;
167 1 : SHA1Sum sha1;
168 2 : nsCString combined(mac + addition);
169 1 : sha1.update(combined.get(), combined.Length());
170 : uint8_t digest[SHA1Sum::kHashSize];
171 1 : sha1.finish(digest);
172 : nsCString newString(reinterpret_cast<char*>(digest),
173 2 : SHA1Sum::kHashSize);
174 1 : nsresult rv = Base64Encode(newString, output);
175 1 : MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
176 1 : LOG(("networkid: id %s\n", output.get()));
177 1 : if (mNetworkId != output) {
178 : // new id
179 1 : Telemetry::Accumulate(Telemetry::NETWORK_ID, 1);
180 1 : mNetworkId = output;
181 : }
182 : else {
183 : // same id
184 0 : Telemetry::Accumulate(Telemetry::NETWORK_ID, 2);
185 : }
186 1 : found = true;
187 1 : break;
188 : }
189 : }
190 : }
191 : }
192 1 : fclose(farp);
193 : } /* if (farp) */
194 : } /* if (gw) */
195 : } /* if (froute) */
196 1 : if (!found) {
197 : // no id
198 0 : Telemetry::Accumulate(Telemetry::NETWORK_ID, 0);
199 : }
200 1 : }
201 :
202 : //
203 : // Check if there's a network interface available to do networking on.
204 : //
205 0 : void nsNotifyAddrListener::checkLink(void)
206 : {
207 : struct ifaddrs *list;
208 : struct ifaddrs *ifa;
209 0 : bool link = false;
210 0 : bool prevLinkUp = mLinkUp;
211 :
212 0 : if (getifaddrs(&list))
213 0 : return;
214 :
215 : // Walk through the linked list, maintaining head pointer so we can free
216 : // list later
217 :
218 0 : for (ifa = list; ifa != nullptr; ifa = ifa->ifa_next) {
219 : int family;
220 0 : if (ifa->ifa_addr == nullptr)
221 0 : continue;
222 :
223 0 : family = ifa->ifa_addr->sa_family;
224 :
225 0 : if ((family == AF_INET || family == AF_INET6) &&
226 0 : (ifa->ifa_flags & IFF_RUNNING) &&
227 0 : !(ifa->ifa_flags & IFF_LOOPBACK)) {
228 : // An interface that is UP and not loopback
229 0 : link = true;
230 0 : break;
231 : }
232 : }
233 0 : mLinkUp = link;
234 0 : freeifaddrs(list);
235 :
236 0 : if (prevLinkUp != mLinkUp) {
237 : // UP/DOWN status changed, send appropriate UP/DOWN event
238 0 : SendEvent(mLinkUp ?
239 0 : NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN);
240 : }
241 : }
242 :
243 0 : void nsNotifyAddrListener::OnNetlinkMessage(int aNetlinkSocket)
244 : {
245 : struct nlmsghdr *nlh;
246 :
247 : // The buffer size below, (4095) was chosen partly based on testing and
248 : // partly on existing sample source code using this size. It needs to be
249 : // large enough to hold the netlink messages from the kernel.
250 : char buffer[4095];
251 : struct rtattr *attr;
252 : int attr_len;
253 : const struct ifaddrmsg* newifam;
254 :
255 :
256 0 : ssize_t rc = EINTR_RETRY(recv(aNetlinkSocket, buffer, sizeof(buffer), 0));
257 0 : if (rc < 0) {
258 0 : return;
259 : }
260 0 : size_t netlink_bytes = rc;
261 :
262 0 : nlh = reinterpret_cast<struct nlmsghdr *>(buffer);
263 :
264 0 : bool networkChange = false;
265 :
266 0 : for (; NLMSG_OK(nlh, netlink_bytes);
267 0 : nlh = NLMSG_NEXT(nlh, netlink_bytes)) {
268 : char prefixaddr[INET6_ADDRSTRLEN];
269 : char localaddr[INET6_ADDRSTRLEN];
270 0 : char* addr = nullptr;
271 0 : prefixaddr[0] = localaddr[0] = '\0';
272 :
273 0 : if (NLMSG_DONE == nlh->nlmsg_type) {
274 0 : break;
275 : }
276 :
277 0 : LOG(("nsNotifyAddrListener::OnNetlinkMessage: new/deleted address\n"));
278 0 : newifam = reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(nlh));
279 :
280 0 : if ((newifam->ifa_family != AF_INET) &&
281 0 : (newifam->ifa_family != AF_INET6)) {
282 0 : continue;
283 : }
284 :
285 0 : attr = IFA_RTA (newifam);
286 0 : attr_len = IFA_PAYLOAD (nlh);
287 0 : for (;attr_len && RTA_OK (attr, attr_len);
288 0 : attr = RTA_NEXT (attr, attr_len)) {
289 0 : if (attr->rta_type == IFA_ADDRESS) {
290 0 : if (newifam->ifa_family == AF_INET) {
291 0 : struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
292 0 : inet_ntop(AF_INET, in, prefixaddr, INET_ADDRSTRLEN);
293 : } else {
294 0 : struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
295 0 : inet_ntop(AF_INET6, in, prefixaddr, INET6_ADDRSTRLEN);
296 : }
297 0 : } else if (attr->rta_type == IFA_LOCAL) {
298 0 : if (newifam->ifa_family == AF_INET) {
299 0 : struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
300 0 : inet_ntop(AF_INET, in, localaddr, INET_ADDRSTRLEN);
301 : } else {
302 0 : struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
303 0 : inet_ntop(AF_INET6, in, localaddr, INET6_ADDRSTRLEN);
304 : }
305 : }
306 : }
307 0 : if (localaddr[0]) {
308 0 : addr = localaddr;
309 0 : } else if (prefixaddr[0]) {
310 0 : addr = prefixaddr;
311 : } else {
312 0 : continue;
313 : }
314 0 : if (nlh->nlmsg_type == RTM_NEWADDR) {
315 0 : LOG(("nsNotifyAddrListener::OnNetlinkMessage: a new address "
316 : "- %s.", addr));
317 : struct ifaddrmsg* ifam;
318 0 : nsCString addrStr;
319 0 : addrStr.Assign(addr);
320 0 : if (auto entry = mAddressInfo.LookupForAdd(addrStr)) {
321 0 : ifam = entry.Data();
322 0 : LOG(("nsNotifyAddrListener::OnNetlinkMessage: the address "
323 : "already known."));
324 0 : if (memcmp(ifam, newifam, sizeof(struct ifaddrmsg))) {
325 0 : LOG(("nsNotifyAddrListener::OnNetlinkMessage: but "
326 : "the address info has been changed."));
327 0 : networkChange = true;
328 0 : memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
329 : }
330 : } else {
331 0 : networkChange = true;
332 0 : ifam = (struct ifaddrmsg*)malloc(sizeof(struct ifaddrmsg));
333 0 : memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
334 0 : entry.OrInsert([ifam] () { return ifam; });
335 : }
336 : } else {
337 0 : LOG(("nsNotifyAddrListener::OnNetlinkMessage: an address "
338 : "has been deleted - %s.", addr));
339 0 : networkChange = true;
340 0 : nsCString addrStr;
341 0 : addrStr.Assign(addr);
342 0 : mAddressInfo.Remove(addrStr);
343 : }
344 : }
345 :
346 0 : if (networkChange && mAllowChangedEvent) {
347 0 : NetworkChanged();
348 : }
349 :
350 0 : if (networkChange) {
351 0 : checkLink();
352 : }
353 : }
354 :
355 : NS_IMETHODIMP
356 1 : nsNotifyAddrListener::Run()
357 : {
358 1 : int netlinkSocket = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
359 1 : if (netlinkSocket < 0) {
360 0 : return NS_ERROR_FAILURE;
361 : }
362 :
363 : struct sockaddr_nl addr;
364 1 : memset(&addr, 0, sizeof(addr)); // clear addr
365 :
366 1 : addr.nl_family = AF_NETLINK;
367 1 : addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
368 :
369 1 : if (bind(netlinkSocket, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
370 : // failure!
371 0 : EINTR_RETRY(close(netlinkSocket));
372 0 : return NS_ERROR_FAILURE;
373 : }
374 :
375 : // switch the socket into non-blocking
376 1 : int flags = fcntl(netlinkSocket, F_GETFL, 0);
377 1 : (void)fcntl(netlinkSocket, F_SETFL, flags | O_NONBLOCK);
378 :
379 : struct pollfd fds[2];
380 1 : fds[0].fd = mShutdownPipe[0];
381 1 : fds[0].events = POLLIN;
382 1 : fds[0].revents = 0;
383 :
384 1 : fds[1].fd = netlinkSocket;
385 1 : fds[1].events = POLLIN;
386 1 : fds[1].revents = 0;
387 :
388 1 : calculateNetworkId();
389 :
390 1 : nsresult rv = NS_OK;
391 1 : bool shutdown = false;
392 1 : int pollWait = -1;
393 1 : while (!shutdown) {
394 1 : int rc = EINTR_RETRY(poll(fds, 2, pollWait));
395 :
396 0 : if (rc > 0) {
397 0 : if (fds[0].revents & POLLIN) {
398 : // shutdown, abort the loop!
399 0 : LOG(("thread shutdown received, dying...\n"));
400 0 : shutdown = true;
401 0 : } else if (fds[1].revents & POLLIN) {
402 0 : LOG(("netlink message received, handling it...\n"));
403 0 : OnNetlinkMessage(netlinkSocket);
404 : }
405 0 : } else if (rc < 0) {
406 0 : rv = NS_ERROR_FAILURE;
407 0 : break;
408 : }
409 0 : if (mCoalescingActive) {
410 : // check if coalescing period should continue
411 0 : double period = (TimeStamp::Now() - mChangeTime).ToMilliseconds();
412 0 : if (period >= kNetworkChangeCoalescingPeriod) {
413 0 : SendEvent(NS_NETWORK_LINK_DATA_CHANGED);
414 0 : calculateNetworkId();
415 0 : mCoalescingActive = false;
416 0 : pollWait = -1; // restore to default
417 : } else {
418 : // wait no longer than to the end of the period
419 0 : pollWait = static_cast<int>
420 0 : (kNetworkChangeCoalescingPeriod - period);
421 : }
422 : }
423 : }
424 :
425 0 : EINTR_RETRY(close(netlinkSocket));
426 :
427 0 : return rv;
428 : }
429 :
430 : NS_IMETHODIMP
431 0 : nsNotifyAddrListener::Observe(nsISupports *subject,
432 : const char *topic,
433 : const char16_t *data)
434 : {
435 0 : if (!strcmp("xpcom-shutdown-threads", topic)) {
436 0 : Shutdown();
437 : }
438 :
439 0 : return NS_OK;
440 : }
441 :
442 : nsresult
443 1 : nsNotifyAddrListener::Init(void)
444 : {
445 : nsCOMPtr<nsIObserverService> observerService =
446 2 : mozilla::services::GetObserverService();
447 1 : if (!observerService)
448 0 : return NS_ERROR_FAILURE;
449 :
450 2 : nsresult rv = observerService->AddObserver(this, "xpcom-shutdown-threads",
451 2 : false);
452 1 : NS_ENSURE_SUCCESS(rv, rv);
453 :
454 1 : Preferences::AddBoolVarCache(&mAllowChangedEvent,
455 1 : NETWORK_NOTIFY_CHANGED_PREF, true);
456 :
457 1 : if (-1 == pipe(mShutdownPipe)) {
458 0 : return NS_ERROR_FAILURE;
459 : }
460 :
461 1 : rv = NS_NewNamedThread("Link Monitor", getter_AddRefs(mThread), this);
462 1 : NS_ENSURE_SUCCESS(rv, rv);
463 :
464 1 : return NS_OK;
465 : }
466 :
467 : nsresult
468 0 : nsNotifyAddrListener::Shutdown(void)
469 : {
470 : // remove xpcom shutdown observer
471 : nsCOMPtr<nsIObserverService> observerService =
472 0 : mozilla::services::GetObserverService();
473 0 : if (observerService)
474 0 : observerService->RemoveObserver(this, "xpcom-shutdown-threads");
475 :
476 0 : LOG(("write() to signal thread shutdown\n"));
477 :
478 : // awake the thread to make it terminate
479 0 : ssize_t rc = EINTR_RETRY(write(mShutdownPipe[1], "1", 1));
480 0 : LOG(("write() returned %d, errno == %d\n", (int)rc, errno));
481 :
482 0 : nsresult rv = mThread->Shutdown();
483 :
484 : // Have to break the cycle here, otherwise nsNotifyAddrListener holds
485 : // onto the thread and the thread holds onto the nsNotifyAddrListener
486 : // via its mRunnable
487 0 : mThread = nullptr;
488 :
489 0 : return rv;
490 : }
491 :
492 :
493 : /*
494 : * A network event has been registered. Delay the actual sending of the event
495 : * for a while and absorb subsequent events in the mean time in an effort to
496 : * squash potentially many triggers into a single event.
497 : * Only ever called from the same thread.
498 : */
499 : nsresult
500 0 : nsNotifyAddrListener::NetworkChanged()
501 : {
502 0 : if (mCoalescingActive) {
503 0 : LOG(("NetworkChanged: absorbed an event (coalescing active)\n"));
504 : } else {
505 : // A fresh trigger!
506 0 : mChangeTime = TimeStamp::Now();
507 0 : mCoalescingActive = true;
508 0 : LOG(("NetworkChanged: coalescing period started\n"));
509 : }
510 0 : return NS_OK;
511 : }
512 :
513 : /* Sends the given event. Assumes aEventID never goes out of scope (static
514 : * strings are ideal).
515 : */
516 : nsresult
517 0 : nsNotifyAddrListener::SendEvent(const char *aEventID)
518 : {
519 0 : if (!aEventID)
520 0 : return NS_ERROR_NULL_POINTER;
521 :
522 0 : LOG(("SendEvent: %s\n", aEventID));
523 0 : nsresult rv = NS_OK;
524 0 : nsCOMPtr<nsIRunnable> event = new ChangeEvent(this, aEventID);
525 0 : if (NS_FAILED(rv = NS_DispatchToMainThread(event)))
526 0 : NS_WARNING("Failed to dispatch ChangeEvent");
527 0 : return rv;
528 : }
529 :
530 : NS_IMETHODIMP
531 0 : nsNotifyAddrListener::ChangeEvent::Run()
532 : {
533 : nsCOMPtr<nsIObserverService> observerService =
534 0 : mozilla::services::GetObserverService();
535 0 : if (observerService)
536 0 : observerService->NotifyObservers(
537 : mService, NS_NETWORK_LINK_TOPIC,
538 0 : NS_ConvertASCIItoUTF16(mEventID).get());
539 0 : return NS_OK;
540 : }
|