Bug 1794427 - Origin Controls reflect implicit access through content scripts r=willd...
[gecko.git] / netwerk / dns / ODoHService.cpp
blobeda9d1f8542a7a467644afb721f9df295604aaeb
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "ODoHService.h"
8 #include "DNSUtils.h"
9 #include "mozilla/net/SocketProcessChild.h"
10 #include "mozilla/Preferences.h"
11 #include "mozilla/ScopeExit.h"
12 #include "mozilla/StaticPrefs_network.h"
13 #include "nsICancelable.h"
14 #include "nsIDNSAdditionalInfo.h"
15 #include "nsIDNSService.h"
16 #include "nsIDNSByTypeRecord.h"
17 #include "nsIOService.h"
18 #include "nsIObserverService.h"
19 #include "nsNetUtil.h"
20 #include "ODoH.h"
21 #include "TRRService.h"
22 #include "nsURLHelper.h"
23 // Put DNSLogging.h at the end to avoid LOG being overwritten by other headers.
24 #include "DNSLogging.h"
26 static const char kODoHProxyURIPref[] = "network.trr.odoh.proxy_uri";
27 static const char kODoHTargetHostPref[] = "network.trr.odoh.target_host";
28 static const char kODoHTargetPathPref[] = "network.trr.odoh.target_path";
29 static const char kODoHConfigsUriPref[] = "network.trr.odoh.configs_uri";
31 namespace mozilla {
32 namespace net {
34 ODoHService* gODoHService = nullptr;
36 NS_IMPL_ISUPPORTS(ODoHService, nsIDNSListener, nsIObserver,
37 nsISupportsWeakReference, nsITimerCallback, nsINamed,
38 nsIStreamLoaderObserver)
40 ODoHService::ODoHService()
41 : mLock("net::ODoHService"), mQueryODoHConfigInProgress(false) {
42 gODoHService = this;
45 ODoHService::~ODoHService() { gODoHService = nullptr; }
47 bool ODoHService::Init() {
48 MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
50 nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
51 if (!prefBranch) {
52 return false;
55 prefBranch->AddObserver(kODoHProxyURIPref, this, true);
56 prefBranch->AddObserver(kODoHTargetHostPref, this, true);
57 prefBranch->AddObserver(kODoHTargetPathPref, this, true);
58 prefBranch->AddObserver(kODoHConfigsUriPref, this, true);
60 ReadPrefs(nullptr);
62 nsCOMPtr<nsIObserverService> observerService =
63 mozilla::services::GetObserverService();
64 if (observerService) {
65 observerService->AddObserver(this, "xpcom-shutdown-threads", true);
68 return true;
71 bool ODoHService::Enabled() const {
72 return StaticPrefs::network_trr_odoh_enabled();
75 NS_IMETHODIMP
76 ODoHService::Observe(nsISupports* aSubject, const char* aTopic,
77 const char16_t* aData) {
78 MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
79 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
80 ReadPrefs(NS_ConvertUTF16toUTF8(aData).get());
81 } else if (!strcmp(aTopic, "xpcom-shutdown-threads")) {
82 if (mTTLTimer) {
83 mTTLTimer->Cancel();
84 mTTLTimer = nullptr;
88 return NS_OK;
91 nsresult ODoHService::ReadPrefs(const char* aName) {
92 if (!aName || !strcmp(aName, kODoHConfigsUriPref)) {
93 OnODohConfigsURIChanged();
95 if (!aName || !strcmp(aName, kODoHProxyURIPref) ||
96 !strcmp(aName, kODoHTargetHostPref) ||
97 !strcmp(aName, kODoHTargetPathPref)) {
98 OnODoHPrefsChange(aName == nullptr);
101 return NS_OK;
104 void ODoHService::OnODohConfigsURIChanged() {
105 nsAutoCString uri;
106 Preferences::GetCString(kODoHConfigsUriPref, uri);
108 bool updateConfig = false;
110 MutexAutoLock lock(mLock);
111 if (!mODoHConfigsUri.Equals(uri)) {
112 mODoHConfigsUri = uri;
113 updateConfig = true;
117 if (updateConfig) {
118 UpdateODoHConfigFromURI();
122 void ODoHService::OnODoHPrefsChange(bool aInit) {
123 nsAutoCString proxyURI;
124 Preferences::GetCString(kODoHProxyURIPref, proxyURI);
125 nsAutoCString targetHost;
126 Preferences::GetCString(kODoHTargetHostPref, targetHost);
127 nsAutoCString targetPath;
128 Preferences::GetCString(kODoHTargetPathPref, targetPath);
130 bool updateODoHConfig = false;
132 MutexAutoLock lock(mLock);
133 mODoHProxyURI = proxyURI;
134 // Only update ODoHConfig when the host is really changed.
135 if (!mODoHTargetHost.Equals(targetHost) && mODoHConfigsUri.IsEmpty()) {
136 updateODoHConfig = true;
138 mODoHTargetHost = targetHost;
139 mODoHTargetPath = targetPath;
141 BuildODoHRequestURI();
144 if (updateODoHConfig) {
145 // When this function is called from ODoHService::Init(), it's on the same
146 // call stack as nsDNSService is inited. In this case, we need to dispatch
147 // UpdateODoHConfigFromHTTPSRR(), since recursively getting DNS service is
148 // not allowed.
149 auto task = []() { gODoHService->UpdateODoHConfigFromHTTPSRR(); };
150 if (aInit) {
151 NS_DispatchToMainThread(NS_NewRunnableFunction(
152 "ODoHService::UpdateODoHConfigFromHTTPSRR", std::move(task)));
153 } else {
154 task();
159 static nsresult ExtractHostAndPort(const nsACString& aURI, nsCString& aResult,
160 int32_t& aOutPort) {
161 nsCOMPtr<nsIURI> uri;
162 nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI);
163 if (NS_FAILED(rv)) {
164 return rv;
167 if (!uri->SchemeIs("https")) {
168 LOG(("ODoHService host uri is not https"));
169 return NS_ERROR_FAILURE;
172 rv = uri->GetPort(&aOutPort);
173 if (NS_FAILED(rv)) {
174 return rv;
177 return uri->GetAsciiHost(aResult);
180 void ODoHService::BuildODoHRequestURI() {
181 mLock.AssertCurrentThreadOwns();
183 mODoHRequestURI.Truncate();
184 if (mODoHTargetHost.IsEmpty() || mODoHTargetPath.IsEmpty()) {
185 return;
188 if (mODoHProxyURI.IsEmpty()) {
189 mODoHRequestURI.Append(mODoHTargetHost);
190 mODoHRequestURI.AppendLiteral("/");
191 mODoHRequestURI.Append(mODoHTargetPath);
192 } else {
193 nsAutoCString hostStr;
194 int32_t port = -1;
195 if (NS_FAILED(ExtractHostAndPort(mODoHTargetHost, hostStr, port))) {
196 return;
199 mODoHRequestURI.Append(mODoHProxyURI);
200 mODoHRequestURI.AppendLiteral("?targethost=");
201 mODoHRequestURI.Append(hostStr);
202 mODoHRequestURI.AppendLiteral("&targetpath=/");
203 mODoHRequestURI.Append(mODoHTargetPath);
207 void ODoHService::GetRequestURI(nsACString& aResult) {
208 MutexAutoLock lock(mLock);
209 aResult = mODoHRequestURI;
212 nsresult ODoHService::UpdateODoHConfig() {
213 LOG(("ODoHService::UpdateODoHConfig"));
214 if (mQueryODoHConfigInProgress) {
215 return NS_OK;
218 if (NS_SUCCEEDED(UpdateODoHConfigFromURI())) {
219 return NS_OK;
222 return UpdateODoHConfigFromHTTPSRR();
225 nsresult ODoHService::UpdateODoHConfigFromURI() {
226 LOG(("ODoHService::UpdateODoHConfigFromURI"));
228 nsAutoCString configUri;
230 MutexAutoLock lock(mLock);
231 configUri = mODoHConfigsUri;
234 if (configUri.IsEmpty() || !StringBeginsWith(configUri, "https://"_ns)) {
235 LOG(("ODoHService::UpdateODoHConfigFromURI: uri is invalid"));
236 return UpdateODoHConfigFromHTTPSRR();
239 nsCOMPtr<nsIEventTarget> target = TRRService::Get()->MainThreadOrTRRThread();
240 if (!target) {
241 return NS_ERROR_UNEXPECTED;
244 if (!target->IsOnCurrentThread()) {
245 nsresult rv = target->Dispatch(NS_NewRunnableFunction(
246 "ODoHService::UpdateODoHConfigFromURI",
247 []() { gODoHService->UpdateODoHConfigFromURI(); }));
248 if (NS_SUCCEEDED(rv)) {
249 // Set mQueryODoHConfigInProgress to true to avoid updating ODoHConfigs
250 // when waiting the runnable to be executed.
251 mQueryODoHConfigInProgress = true;
253 return rv;
256 // In case any error happens, we should reset mQueryODoHConfigInProgress.
257 auto guard = MakeScopeExit([&]() { mQueryODoHConfigInProgress = false; });
259 nsCOMPtr<nsIURI> uri;
260 nsresult rv = NS_NewURI(getter_AddRefs(uri), configUri);
261 if (NS_FAILED(rv)) {
262 return rv;
265 nsCOMPtr<nsIChannel> channel;
266 rv = DNSUtils::CreateChannelHelper(uri, getter_AddRefs(channel));
267 if (NS_FAILED(rv) || !channel) {
268 LOG(("NewChannel failed!"));
269 return rv;
272 channel->SetLoadFlags(
273 nsIRequest::LOAD_ANONYMOUS | nsIRequest::INHIBIT_CACHING |
274 nsIRequest::LOAD_BYPASS_CACHE | nsIChannel::LOAD_BYPASS_URL_CLASSIFIER);
275 NS_ENSURE_SUCCESS(rv, rv);
277 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
278 if (!httpChannel) {
279 return NS_ERROR_UNEXPECTED;
282 // This connection should not use TRR
283 rv = httpChannel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE);
284 NS_ENSURE_SUCCESS(rv, rv);
286 nsCOMPtr<nsIStreamLoader> loader;
287 rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
288 if (NS_FAILED(rv)) {
289 return rv;
292 rv = httpChannel->AsyncOpen(loader);
293 if (NS_FAILED(rv)) {
294 return rv;
297 // AsyncOpen succeeded, dismiss the guard.
298 MutexAutoLock lock(mLock);
299 guard.release();
300 mLoader.swap(loader);
301 return rv;
304 nsresult ODoHService::UpdateODoHConfigFromHTTPSRR() {
305 LOG(("ODoHService::UpdateODoHConfigFromHTTPSRR"));
307 nsAutoCString uri;
309 MutexAutoLock lock(mLock);
310 uri = mODoHTargetHost;
313 nsCOMPtr<nsIDNSService> dns(
314 do_GetService("@mozilla.org/network/dns-service;1"));
315 if (!dns) {
316 return NS_ERROR_NOT_AVAILABLE;
319 if (!TRRService::Get()) {
320 return NS_ERROR_NOT_AVAILABLE;
323 nsAutoCString hostStr;
324 int32_t port = -1;
325 nsresult rv = ExtractHostAndPort(uri, hostStr, port);
326 if (NS_FAILED(rv)) {
327 return rv;
330 nsCOMPtr<nsICancelable> tmpOutstanding;
331 nsCOMPtr<nsIEventTarget> target = TRRService::Get()->MainThreadOrTRRThread();
332 // We'd like to bypass the DNS cache, since ODoHConfigs will be updated
333 // manually by ODoHService.
334 uint32_t flags =
335 nsIDNSService::RESOLVE_DISABLE_ODOH | nsIDNSService::RESOLVE_BYPASS_CACHE;
336 nsCOMPtr<nsIDNSAdditionalInfo> info;
337 if (port != -1) {
338 Unused << dns->NewAdditionalInfo(""_ns, port, getter_AddRefs(info));
340 rv = dns->AsyncResolveNative(hostStr, nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
341 flags, info, this, target, OriginAttributes(),
342 getter_AddRefs(tmpOutstanding));
343 LOG(("ODoHService::UpdateODoHConfig [host=%s rv=%" PRIx32 "]", hostStr.get(),
344 static_cast<uint32_t>(rv)));
346 if (NS_SUCCEEDED(rv)) {
347 mQueryODoHConfigInProgress = true;
349 return rv;
352 void ODoHService::StartTTLTimer(uint32_t aTTL) {
353 if (mTTLTimer) {
354 mTTLTimer->Cancel();
355 mTTLTimer = nullptr;
357 LOG(("ODoHService::StartTTLTimer ttl=%d(s)", aTTL));
358 NS_NewTimerWithCallback(getter_AddRefs(mTTLTimer), this, aTTL * 1000,
359 nsITimer::TYPE_ONE_SHOT);
362 NS_IMETHODIMP
363 ODoHService::Notify(nsITimer* aTimer) {
364 MOZ_ASSERT(aTimer == mTTLTimer);
365 UpdateODoHConfig();
366 return NS_OK;
369 NS_IMETHODIMP
370 ODoHService::GetName(nsACString& aName) {
371 aName.AssignLiteral("ODoHService");
372 return NS_OK;
375 void ODoHService::ODoHConfigUpdateDone(uint32_t aTTL,
376 Span<const uint8_t> aRawConfig) {
377 MOZ_ASSERT_IF(XRE_IsParentProcess() && TRRService::Get(),
378 NS_IsMainThread() || TRRService::Get()->IsOnTRRThread());
379 MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
381 MutexAutoLock lock(mLock);
382 mQueryODoHConfigInProgress = false;
383 mODoHConfigs.reset();
385 nsTArray<ObliviousDoHConfig> configs;
386 if (ODoHDNSPacket::ParseODoHConfigs(aRawConfig, configs)) {
387 mODoHConfigs.emplace(std::move(configs));
390 // Let observers know whether ODoHService is activated or not.
391 bool hasODoHConfigs = mODoHConfigs && !mODoHConfigs->IsEmpty();
392 if (aTTL < StaticPrefs::network_trr_odoh_min_ttl()) {
393 aTTL = StaticPrefs::network_trr_odoh_min_ttl();
395 auto task = [hasODoHConfigs, aTTL]() {
396 MOZ_ASSERT(NS_IsMainThread());
397 if (XRE_IsSocketProcess()) {
398 SocketProcessChild::GetSingleton()->SendODoHServiceActivated(
399 hasODoHConfigs);
402 nsCOMPtr<nsIObserverService> observerService =
403 mozilla::services::GetObserverService();
405 if (observerService) {
406 observerService->NotifyObservers(nullptr, "odoh-service-activated",
407 hasODoHConfigs ? u"true" : u"false");
410 if (aTTL) {
411 gODoHService->StartTTLTimer(aTTL);
415 if (NS_IsMainThread()) {
416 task();
417 } else {
418 NS_DispatchToMainThread(
419 NS_NewRunnableFunction("ODoHService::Activated", std::move(task)));
422 if (!mPendingRequests.IsEmpty()) {
423 nsTArray<RefPtr<ODoH>> requests = std::move(mPendingRequests);
424 nsCOMPtr<nsIEventTarget> target =
425 TRRService::Get()->MainThreadOrTRRThread();
426 for (auto& query : requests) {
427 target->Dispatch(query.forget());
432 NS_IMETHODIMP
433 ODoHService::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRec,
434 nsresult aStatus) {
435 MOZ_ASSERT_IF(XRE_IsParentProcess() && TRRService::Get(),
436 NS_IsMainThread() || TRRService::Get()->IsOnTRRThread());
437 MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
439 nsCOMPtr<nsIDNSHTTPSSVCRecord> httpsRecord;
440 nsCString rawODoHConfig;
441 auto notifyDone = MakeScopeExit([&]() {
442 uint32_t ttl = 0;
443 if (httpsRecord) {
444 Unused << httpsRecord->GetTtl(&ttl);
447 ODoHConfigUpdateDone(
448 ttl,
449 Span(reinterpret_cast<const uint8_t*>(rawODoHConfig.BeginReading()),
450 rawODoHConfig.Length()));
453 LOG(("ODoHService::OnLookupComplete [aStatus=%" PRIx32 "]",
454 static_cast<uint32_t>(aStatus)));
455 if (NS_FAILED(aStatus)) {
456 return NS_OK;
459 httpsRecord = do_QueryInterface(aRec);
460 if (!httpsRecord) {
461 return NS_OK;
464 nsTArray<RefPtr<nsISVCBRecord>> svcbRecords;
465 httpsRecord->GetRecords(svcbRecords);
466 for (const auto& record : svcbRecords) {
467 Unused << record->GetODoHConfig(rawODoHConfig);
468 if (!rawODoHConfig.IsEmpty()) {
469 break;
473 return NS_OK;
476 NS_IMETHODIMP
477 ODoHService::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
478 nsresult aStatus, uint32_t aLength,
479 const uint8_t* aContent) {
480 MOZ_ASSERT_IF(XRE_IsParentProcess() && TRRService::Get(),
481 NS_IsMainThread() || TRRService::Get()->IsOnTRRThread());
482 MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
483 LOG(("ODoHService::OnStreamComplete aLength=%d\n", aLength));
486 MutexAutoLock lock(mLock);
487 mLoader = nullptr;
489 ODoHConfigUpdateDone(0, Span(aContent, aLength));
490 return NS_OK;
493 const Maybe<nsTArray<ObliviousDoHConfig>>& ODoHService::ODoHConfigs() {
494 MOZ_ASSERT_IF(XRE_IsParentProcess() && TRRService::Get(),
495 NS_IsMainThread() || TRRService::Get()->IsOnTRRThread());
496 MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
498 return mODoHConfigs;
501 void ODoHService::AppendPendingODoHRequest(ODoH* aRequest) {
502 LOG(("ODoHService::AppendPendingODoHQuery\n"));
503 MOZ_ASSERT_IF(XRE_IsParentProcess() && TRRService::Get(),
504 NS_IsMainThread() || TRRService::Get()->IsOnTRRThread());
505 MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
507 MutexAutoLock lock(mLock);
508 mPendingRequests.AppendElement(aRequest);
511 bool ODoHService::RemovePendingODoHRequest(ODoH* aRequest) {
512 MOZ_ASSERT_IF(XRE_IsParentProcess() && TRRService::Get(),
513 NS_IsMainThread() || TRRService::Get()->IsOnTRRThread());
514 MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
516 MutexAutoLock lock(mLock);
517 return mPendingRequests.RemoveElement(aRequest);
520 } // namespace net
521 } // namespace mozilla