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"
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"
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";
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) {
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
));
55 prefBranch
->AddObserver(kODoHProxyURIPref
, this, true);
56 prefBranch
->AddObserver(kODoHTargetHostPref
, this, true);
57 prefBranch
->AddObserver(kODoHTargetPathPref
, this, true);
58 prefBranch
->AddObserver(kODoHConfigsUriPref
, this, true);
62 nsCOMPtr
<nsIObserverService
> observerService
=
63 mozilla::services::GetObserverService();
64 if (observerService
) {
65 observerService
->AddObserver(this, "xpcom-shutdown-threads", true);
71 bool ODoHService::Enabled() const {
72 return StaticPrefs::network_trr_odoh_enabled();
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")) {
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);
104 void ODoHService::OnODohConfigsURIChanged() {
106 Preferences::GetCString(kODoHConfigsUriPref
, uri
);
108 bool updateConfig
= false;
110 MutexAutoLock
lock(mLock
);
111 if (!mODoHConfigsUri
.Equals(uri
)) {
112 mODoHConfigsUri
= uri
;
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
149 auto task
= []() { gODoHService
->UpdateODoHConfigFromHTTPSRR(); };
151 NS_DispatchToMainThread(NS_NewRunnableFunction(
152 "ODoHService::UpdateODoHConfigFromHTTPSRR", std::move(task
)));
159 static nsresult
ExtractHostAndPort(const nsACString
& aURI
, nsCString
& aResult
,
161 nsCOMPtr
<nsIURI
> uri
;
162 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), aURI
);
167 if (!uri
->SchemeIs("https")) {
168 LOG(("ODoHService host uri is not https"));
169 return NS_ERROR_FAILURE
;
172 rv
= uri
->GetPort(&aOutPort
);
177 return uri
->GetAsciiHost(aResult
);
180 void ODoHService::BuildODoHRequestURI() {
181 mLock
.AssertCurrentThreadOwns();
183 mODoHRequestURI
.Truncate();
184 if (mODoHTargetHost
.IsEmpty() || mODoHTargetPath
.IsEmpty()) {
188 if (mODoHProxyURI
.IsEmpty()) {
189 mODoHRequestURI
.Append(mODoHTargetHost
);
190 mODoHRequestURI
.AppendLiteral("/");
191 mODoHRequestURI
.Append(mODoHTargetPath
);
193 nsAutoCString hostStr
;
195 if (NS_FAILED(ExtractHostAndPort(mODoHTargetHost
, hostStr
, port
))) {
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
) {
218 if (NS_SUCCEEDED(UpdateODoHConfigFromURI())) {
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();
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;
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
);
265 nsCOMPtr
<nsIChannel
> channel
;
266 rv
= DNSUtils::CreateChannelHelper(uri
, getter_AddRefs(channel
));
267 if (NS_FAILED(rv
) || !channel
) {
268 LOG(("NewChannel failed!"));
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
);
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);
292 rv
= httpChannel
->AsyncOpen(loader
);
297 // AsyncOpen succeeded, dismiss the guard.
298 MutexAutoLock
lock(mLock
);
300 mLoader
.swap(loader
);
304 nsresult
ODoHService::UpdateODoHConfigFromHTTPSRR() {
305 LOG(("ODoHService::UpdateODoHConfigFromHTTPSRR"));
309 MutexAutoLock
lock(mLock
);
310 uri
= mODoHTargetHost
;
313 nsCOMPtr
<nsIDNSService
> dns(
314 do_GetService("@mozilla.org/network/dns-service;1"));
316 return NS_ERROR_NOT_AVAILABLE
;
319 if (!TRRService::Get()) {
320 return NS_ERROR_NOT_AVAILABLE
;
323 nsAutoCString hostStr
;
325 nsresult rv
= ExtractHostAndPort(uri
, hostStr
, port
);
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.
335 nsIDNSService::RESOLVE_DISABLE_ODOH
| nsIDNSService::RESOLVE_BYPASS_CACHE
;
336 nsCOMPtr
<nsIDNSAdditionalInfo
> info
;
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;
352 void ODoHService::StartTTLTimer(uint32_t aTTL
) {
357 LOG(("ODoHService::StartTTLTimer ttl=%d(s)", aTTL
));
358 NS_NewTimerWithCallback(getter_AddRefs(mTTLTimer
), this, aTTL
* 1000,
359 nsITimer::TYPE_ONE_SHOT
);
363 ODoHService::Notify(nsITimer
* aTimer
) {
364 MOZ_ASSERT(aTimer
== mTTLTimer
);
370 ODoHService::GetName(nsACString
& aName
) {
371 aName
.AssignLiteral("ODoHService");
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(
402 nsCOMPtr
<nsIObserverService
> observerService
=
403 mozilla::services::GetObserverService();
405 if (observerService
) {
406 observerService
->NotifyObservers(nullptr, "odoh-service-activated",
407 hasODoHConfigs
? u
"true" : u
"false");
411 gODoHService
->StartTTLTimer(aTTL
);
415 if (NS_IsMainThread()) {
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());
433 ODoHService::OnLookupComplete(nsICancelable
* aRequest
, nsIDNSRecord
* aRec
,
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([&]() {
444 Unused
<< httpsRecord
->GetTtl(&ttl
);
447 ODoHConfigUpdateDone(
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
)) {
459 httpsRecord
= do_QueryInterface(aRec
);
464 nsTArray
<RefPtr
<nsISVCBRecord
>> svcbRecords
;
465 httpsRecord
->GetRecords(svcbRecords
);
466 for (const auto& record
: svcbRecords
) {
467 Unused
<< record
->GetODoHConfig(rawODoHConfig
);
468 if (!rawODoHConfig
.IsEmpty()) {
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
);
489 ODoHConfigUpdateDone(0, Span(aContent
, aLength
));
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());
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
);
521 } // namespace mozilla