1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 #include "NetworkConnectivityService.h"
6 #include "mozilla/Preferences.h"
7 #include "mozilla/Services.h"
8 #include "xpcpublic.h"
9 #include "nsSocketTransport2.h"
10 #include "nsIHttpChannelInternal.h"
11 #include "nsINetworkLinkService.h"
12 #include "mozilla/StaticPrefs_network.h"
13
14 static LazyLogModule gNCSLog("NetworkConnectivityService");
15 #undef LOG
16 #define LOG(args) MOZ_LOG(gNCSLog, mozilla::LogLevel::Debug, args)
17
18 namespace mozilla {
19 namespace net {
20
21 NS_IMPL_ISUPPORTS(NetworkConnectivityService, nsIDNSListener, nsIObserver,
22 nsINetworkConnectivityService, nsIStreamListener)
23
24 static StaticRefPtr<NetworkConnectivityService> gConnService;
25
NetworkConnectivityService()26 NetworkConnectivityService::NetworkConnectivityService()
27 : mDNSv4(UNKNOWN),
28 mDNSv6(UNKNOWN),
29 mIPv4(UNKNOWN),
30 mIPv6(UNKNOWN),
31 mNAT64(UNKNOWN),
32 mLock("nat64prefixes") {}
33
34 // static
35 already_AddRefed<NetworkConnectivityService>
GetSingleton()36 NetworkConnectivityService::GetSingleton() {
37 if (!XRE_IsParentProcess()) {
38 return nullptr;
39 }
40
41 if (gConnService) {
42 return do_AddRef(gConnService);
43 }
44
45 RefPtr<NetworkConnectivityService> service = new NetworkConnectivityService();
46 service->Init();
47
48 gConnService = std::move(service);
49 ClearOnShutdown(&gConnService);
50 return do_AddRef(gConnService);
51 }
52
Init()53 nsresult NetworkConnectivityService::Init() {
54 nsCOMPtr<nsIObserverService> observerService =
55 mozilla::services::GetObserverService();
56 observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
57 observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
58 observerService->AddObserver(this, "network:captive-portal-connectivity",
59 false);
60
61 return NS_OK;
62 }
63
64 NS_IMETHODIMP
GetDNSv4(ConnectivityState * aState)65 NetworkConnectivityService::GetDNSv4(ConnectivityState* aState) {
66 NS_ENSURE_ARG(aState);
67 *aState = mDNSv4;
68 return NS_OK;
69 }
70
71 NS_IMETHODIMP
GetDNSv6(ConnectivityState * aState)72 NetworkConnectivityService::GetDNSv6(ConnectivityState* aState) {
73 NS_ENSURE_ARG(aState);
74 *aState = mDNSv6;
75 return NS_OK;
76 }
77
78 NS_IMETHODIMP
GetIPv4(ConnectivityState * aState)79 NetworkConnectivityService::GetIPv4(ConnectivityState* aState) {
80 NS_ENSURE_ARG(aState);
81 *aState = mIPv4;
82 return NS_OK;
83 }
84
85 NS_IMETHODIMP
GetIPv6(ConnectivityState * aState)86 NetworkConnectivityService::GetIPv6(ConnectivityState* aState) {
87 NS_ENSURE_ARG(aState);
88 *aState = mIPv6;
89 return NS_OK;
90 }
91
92 NS_IMETHODIMP
GetNAT64(ConnectivityState * aState)93 NetworkConnectivityService::GetNAT64(ConnectivityState* aState) {
94 NS_ENSURE_ARG(aState);
95 *aState = mNAT64;
96 return NS_OK;
97 }
98
MapNAT64IPs(AddrInfo * aNewRRSet)99 already_AddRefed<AddrInfo> NetworkConnectivityService::MapNAT64IPs(
100 AddrInfo* aNewRRSet) {
101 // Add prefixes only if there are no IPv6 addresses.
102 // Expect that if aNewRRSet has IPv6 addresses, they must come
103 // before IPv4 addresses.
104 if (aNewRRSet->Addresses().IsEmpty() ||
105 aNewRRSet->Addresses()[0].raw.family == PR_AF_INET6) {
106 return do_AddRef(aNewRRSet);
107 }
108
109 // Currently we only add prefixes to the first IP's clones.
110 uint32_t ip = aNewRRSet->Addresses()[0].inet.ip;
111 nsTArray<NetAddr> addresses = aNewRRSet->Addresses().Clone();
112
113 {
114 MutexAutoLock lock(mLock);
115 for (const auto& prefix : mNAT64Prefixes) {
116 NetAddr addr = NetAddr(prefix);
117
118 // Copy the IPv4 address to the end
119 addr.inet6.ip.u32[3] = ip;
120
121 // If we have both IPv4 and NAT64, we be could insourcing NAT64
122 // to avoid double NAT and improve performance. However, this
123 // breaks WebRTC, so we push it to the back.
124 addresses.AppendElement(addr);
125 }
126 }
127
128 auto builder = aNewRRSet->Build();
129 builder.SetAddresses(std::move(addresses));
130 return builder.Finish();
131 }
132
133 // Returns true if a prefix was read and saved to the argument
NAT64PrefixFromPref(NetAddr * prefix)134 static inline bool NAT64PrefixFromPref(NetAddr* prefix) {
135 nsAutoCString nat64PrefixPref;
136
137 nsresult rv = Preferences::GetCString(
138 "network.connectivity-service.nat64-prefix", nat64PrefixPref);
139 return !(NS_FAILED(rv) || nat64PrefixPref.IsEmpty() ||
140 NS_FAILED(prefix->InitFromString(nat64PrefixPref)) ||
141 prefix->raw.family != PR_AF_INET6);
142 }
143
NAT64PrefixCompare(const NetAddr & prefix1,const NetAddr & prefix2)144 static inline bool NAT64PrefixCompare(const NetAddr& prefix1,
145 const NetAddr& prefix2) {
146 // Compare the first 96 bits as 64 + 32
147 return prefix1.inet6.ip.u64[0] == prefix2.inet6.ip.u64[0] &&
148 prefix1.inet6.ip.u32[2] == prefix2.inet6.ip.u32[2];
149 }
150
PerformChecks()151 void NetworkConnectivityService::PerformChecks() {
152 mDNSv4 = UNKNOWN;
153 mDNSv6 = UNKNOWN;
154
155 mIPv4 = UNKNOWN;
156 mIPv6 = UNKNOWN;
157
158 mNAT64 = UNKNOWN;
159
160 {
161 MutexAutoLock lock(mLock);
162 mNAT64Prefixes.Clear();
163
164 // NAT64 checks might be disabled.
165 // Since We can't guarantee a DNS response, we should set up
166 // NAT64 manually now if needed.
167
168 NetAddr priorityPrefix{};
169 bool havePrefix = NAT64PrefixFromPref(&priorityPrefix);
170 if (havePrefix) {
171 mNAT64Prefixes.AppendElement(priorityPrefix);
172 mNAT64 = OK;
173 }
174 }
175
176 RecheckDNS();
177 RecheckIPConnectivity();
178 }
179
NotifyObservers(const char * aTopic)180 static inline void NotifyObservers(const char* aTopic) {
181 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
182 obs->NotifyObservers(nullptr, aTopic, nullptr);
183 }
184
SaveNAT64Prefixes(nsIDNSRecord * aRecord)185 void NetworkConnectivityService::SaveNAT64Prefixes(nsIDNSRecord* aRecord) {
186 nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(aRecord);
187 MutexAutoLock lock(mLock);
188 mNAT64Prefixes.Clear();
189
190 NetAddr priorityPrefix{};
191 bool havePrefix = NAT64PrefixFromPref(&priorityPrefix);
192 if (havePrefix) {
193 mNAT64 = OK;
194 mNAT64Prefixes.AppendElement(priorityPrefix);
195 }
196
197 if (!rec) {
198 if (!havePrefix) {
199 mNAT64 = NOT_AVAILABLE;
200 }
201 return;
202 }
203
204 mNAT64 = UNKNOWN;
205 NetAddr addr{};
206
207 // use port 80 as dummy value for NetAddr
208 while (NS_SUCCEEDED(rec->GetNextAddr(80, &addr))) {
209 if (addr.raw.family != AF_INET6 || addr.IsIPAddrV4Mapped()) {
210 // These are not the kind of addresses we are looking for.
211 continue;
212 }
213
214 // RFC 7050 does not require the embedded IPv4 to be
215 // at the end of IPv6. In practice, and as we assume,
216 // it is always at the end.
217 // The embedded IP must be 192.0.0.170 or 192.0.0.171
218
219 // Clear the last bit to compare with the next one.
220 addr.inet6.ip.u8[15] &= ~(uint32_t)1;
221 if ((addr.inet6.ip.u8[12] != 192) || (addr.inet6.ip.u8[13] != 0) ||
222 (addr.inet6.ip.u8[14] != 0) || (addr.inet6.ip.u8[15] != 170)) {
223 continue;
224 }
225
226 mNAT64Prefixes.AppendElement(addr);
227 }
228
229 size_t length = mNAT64Prefixes.Length();
230 if (length == 0) {
231 mNAT64 = NOT_AVAILABLE;
232 return;
233 }
234
235 // Remove duplicates. Typically a DNS64 resolver sends every
236 // prefix twice with address with different last bits. We want
237 // a list of unique prefixes while reordering is not allowed.
238 // We must not handle the case with an element in-between
239 // two identical ones, which is never the case for a properly
240 // configured DNS64 resolver.
241
242 NetAddr prev = mNAT64Prefixes[0];
243
244 for (size_t i = 1; i < length; i++) {
245 if (NAT64PrefixCompare(prev, mNAT64Prefixes[i])) {
246 mNAT64Prefixes.RemoveElementAt(i);
247 i--;
248 length--;
249 } else {
250 prev = mNAT64Prefixes[i];
251 }
252 }
253
254 // The prioritized address might also appear in the record we received.
255
256 if (havePrefix) {
257 for (size_t i = 1; i < length; i++) {
258 if (NAT64PrefixCompare(priorityPrefix, mNAT64Prefixes[i])) {
259 mNAT64Prefixes.RemoveElementAt(i);
260 // It wouldn't appear more than once.
261 break;
262 }
263 }
264 }
265
266 mNAT64 = OK;
267 }
268
269 NS_IMETHODIMP
OnLookupComplete(nsICancelable * aRequest,nsIDNSRecord * aRecord,nsresult aStatus)270 NetworkConnectivityService::OnLookupComplete(nsICancelable* aRequest,
271 nsIDNSRecord* aRecord,
272 nsresult aStatus) {
273 ConnectivityState state = aRecord ? OK : NOT_AVAILABLE;
274
275 if (aRequest == mDNSv4Request) {
276 mDNSv4 = state;
277 mDNSv4Request = nullptr;
278 } else if (aRequest == mDNSv6Request) {
279 mDNSv6 = state;
280 mDNSv6Request = nullptr;
281 } else if (aRequest == mNAT64Request) {
282 mNAT64Request = nullptr;
283 SaveNAT64Prefixes(aRecord);
284 }
285
286 if (!mDNSv4Request && !mDNSv6Request && !mNAT64Request) {
287 NotifyObservers("network:connectivity-service:dns-checks-complete");
288 }
289 return NS_OK;
290 }
291
292 NS_IMETHODIMP
RecheckDNS()293 NetworkConnectivityService::RecheckDNS() {
294 bool enabled =
295 Preferences::GetBool("network.connectivity-service.enabled", false);
296 if (!enabled) {
297 return NS_OK;
298 }
299
300 nsresult rv;
301 nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
302 OriginAttributes attrs;
303 nsAutoCString host;
304 Preferences::GetCString("network.connectivity-service.DNSv4.domain", host);
305
306 rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT,
307 nsIDNSService::RESOLVE_DISABLE_IPV6 |
308 nsIDNSService::RESOLVE_TRR_DISABLED_MODE,
309 nullptr, this, NS_GetCurrentThread(), attrs,
310 getter_AddRefs(mDNSv4Request));
311 NS_ENSURE_SUCCESS(rv, rv);
312
313 Preferences::GetCString("network.connectivity-service.DNSv6.domain", host);
314 rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT,
315 nsIDNSService::RESOLVE_DISABLE_IPV4 |
316 nsIDNSService::RESOLVE_TRR_DISABLED_MODE,
317 nullptr, this, NS_GetCurrentThread(), attrs,
318 getter_AddRefs(mDNSv6Request));
319 NS_ENSURE_SUCCESS(rv, rv);
320
321 if (StaticPrefs::network_connectivity_service_nat64_check()) {
322 rv = dns->AsyncResolveNative("ipv4only.arpa"_ns,
323 nsIDNSService::RESOLVE_TYPE_DEFAULT,
324 nsIDNSService::RESOLVE_DISABLE_IPV4 |
325 nsIDNSService::RESOLVE_TRR_DISABLED_MODE,
326 nullptr, this, NS_GetCurrentThread(), attrs,
327 getter_AddRefs(mNAT64Request));
328 NS_ENSURE_SUCCESS(rv, rv);
329 }
330 return rv;
331 }
332
333 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)334 NetworkConnectivityService::Observe(nsISupports* aSubject, const char* aTopic,
335 const char16_t* aData) {
336 if (!strcmp(aTopic, "network:captive-portal-connectivity")) {
337 // Captive portal is cleared, so we redo the checks.
338 PerformChecks();
339 } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
340 if (mDNSv4Request) {
341 mDNSv4Request->Cancel(NS_ERROR_ABORT);
342 mDNSv4Request = nullptr;
343 }
344 if (mDNSv6Request) {
345 mDNSv6Request->Cancel(NS_ERROR_ABORT);
346 mDNSv6Request = nullptr;
347 }
348 if (mNAT64Request) {
349 mNAT64Request->Cancel(NS_ERROR_ABORT);
350 mNAT64Request = nullptr;
351 }
352
353 nsCOMPtr<nsIObserverService> observerService =
354 mozilla::services::GetObserverService();
355 observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
356 observerService->RemoveObserver(this,
357 "network:captive-portal-connectivity");
358 observerService->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
359 } else if (!strcmp(aTopic, NS_NETWORK_LINK_TOPIC) &&
360 !NS_LITERAL_STRING_FROM_CSTRING(NS_NETWORK_LINK_DATA_UNKNOWN)
361 .Equals(aData)) {
362 PerformChecks();
363 }
364
365 return NS_OK;
366 }
367
SetupIPCheckChannel(bool ipv4)368 static inline already_AddRefed<nsIChannel> SetupIPCheckChannel(bool ipv4) {
369 nsresult rv;
370 nsAutoCString url;
371
372 if (ipv4) {
373 rv = Preferences::GetCString("network.connectivity-service.IPv4.url", url);
374 } else {
375 rv = Preferences::GetCString("network.connectivity-service.IPv6.url", url);
376 }
377 NS_ENSURE_SUCCESS(rv, nullptr);
378
379 nsCOMPtr<nsIURI> uri;
380 rv = NS_NewURI(getter_AddRefs(uri), url);
381 NS_ENSURE_SUCCESS(rv, nullptr);
382
383 nsCOMPtr<nsIChannel> channel;
384 rv = NS_NewChannel(
385 getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(),
386 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
387 nsIContentPolicy::TYPE_OTHER,
388 nullptr, // nsICookieJarSettings
389 nullptr, // aPerformanceStorage
390 nullptr, // aLoadGroup
391 nullptr,
392 nsIRequest::LOAD_BYPASS_CACHE | // don't read from the cache
393 nsIRequest::INHIBIT_CACHING | // don't write the response to cache
394 nsIRequest::LOAD_ANONYMOUS); // prevent privacy leaks
395 NS_ENSURE_SUCCESS(rv, nullptr);
396
397 rv = channel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE);
398 NS_ENSURE_SUCCESS(rv, nullptr);
399
400 {
401 // Prevent HTTPS-Only Mode from upgrading the OCSP request.
402 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
403 uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
404 httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_EXEMPT;
405 loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
406
407 // allow deprecated HTTP request from SystemPrincipal
408 loadInfo->SetAllowDeprecatedSystemRequests(true);
409 }
410
411 nsCOMPtr<nsIHttpChannelInternal> internalChan = do_QueryInterface(channel);
412 NS_ENSURE_TRUE(internalChan, nullptr);
413
414 if (ipv4) {
415 internalChan->SetIPv6Disabled();
416 } else {
417 internalChan->SetIPv4Disabled();
418 }
419
420 return channel.forget();
421 }
422
423 NS_IMETHODIMP
RecheckIPConnectivity()424 NetworkConnectivityService::RecheckIPConnectivity() {
425 bool enabled =
426 Preferences::GetBool("network.connectivity-service.enabled", false);
427 if (!enabled) {
428 return NS_OK;
429 }
430
431 if (xpc::AreNonLocalConnectionsDisabled() &&
432 !Preferences::GetBool("network.captive-portal-service.testMode", false)) {
433 return NS_OK;
434 }
435
436 if (mIPv4Channel) {
437 mIPv4Channel->Cancel(NS_ERROR_ABORT);
438 mIPv4Channel = nullptr;
439 }
440 if (mIPv6Channel) {
441 mIPv6Channel->Cancel(NS_ERROR_ABORT);
442 mIPv6Channel = nullptr;
443 }
444
445 nsresult rv;
446 mHasNetworkId = false;
447 mCheckedNetworkId = false;
448 mIPv4Channel = SetupIPCheckChannel(/* ipv4 = */ true);
449 if (mIPv4Channel) {
450 rv = mIPv4Channel->AsyncOpen(this);
451 NS_ENSURE_SUCCESS(rv, rv);
452 }
453
454 mIPv6Channel = SetupIPCheckChannel(/* ipv4 = */ false);
455 if (mIPv6Channel) {
456 rv = mIPv6Channel->AsyncOpen(this);
457 NS_ENSURE_SUCCESS(rv, rv);
458 }
459
460 return NS_OK;
461 }
462
463 NS_IMETHODIMP
OnStartRequest(nsIRequest * aRequest)464 NetworkConnectivityService::OnStartRequest(nsIRequest* aRequest) {
465 return NS_OK;
466 }
467
468 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsresult aStatusCode)469 NetworkConnectivityService::OnStopRequest(nsIRequest* aRequest,
470 nsresult aStatusCode) {
471 if (aStatusCode == NS_ERROR_ABORT) {
472 return NS_OK;
473 }
474
475 ConnectivityState status = NS_FAILED(aStatusCode) ? NOT_AVAILABLE : OK;
476
477 if (aRequest == mIPv4Channel) {
478 mIPv4 = status;
479 mIPv4Channel = nullptr;
480
481 if (mIPv4 == nsINetworkConnectivityService::OK) {
482 Telemetry::AccumulateCategorical(
483 mHasNetworkId ? Telemetry::LABELS_NETWORK_ID_ONLINE::present
484 : Telemetry::LABELS_NETWORK_ID_ONLINE::absent);
485 LOG(("mHasNetworkId : %d\n", mHasNetworkId));
486 }
487 } else if (aRequest == mIPv6Channel) {
488 mIPv6 = status;
489 mIPv6Channel = nullptr;
490 }
491
492 if (!mIPv6Channel && !mIPv4Channel) {
493 NotifyObservers("network:connectivity-service:ip-checks-complete");
494 }
495
496 return NS_OK;
497 }
498
499 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsIInputStream * aInputStream,uint64_t aOffset,uint32_t aCount)500 NetworkConnectivityService::OnDataAvailable(nsIRequest* aRequest,
501 nsIInputStream* aInputStream,
502 uint64_t aOffset, uint32_t aCount) {
503 nsAutoCString data;
504
505 // We perform this check here, instead of doing it in OnStopRequest in case
506 // a network down event occurs after the data has arrived but before we fire
507 // OnStopRequest. That would cause us to report a missing networkID, even
508 // though it was not empty while receiving data.
509 if (aRequest == mIPv4Channel && !mCheckedNetworkId) {
510 nsCOMPtr<nsINetworkLinkService> nls =
511 do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID);
512 nsAutoCString networkId;
513 if (nls) {
514 nls->GetNetworkID(networkId);
515 }
516 mHasNetworkId = !networkId.IsEmpty();
517 mCheckedNetworkId = true;
518 }
519
520 Unused << NS_ReadInputStreamToString(aInputStream, data, aCount);
521 return NS_OK;
522 }
523
524 } // namespace net
525 } // namespace mozilla
526