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/. */
5
6 #include "ODoHService.h"
7
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 "nsIDNSService.h"
14 #include "nsIDNSByTypeRecord.h"
15 #include "nsIOService.h"
16 #include "nsIObserverService.h"
17 #include "nsNetUtil.h"
18 #include "ODoH.h"
19 #include "TRRService.h"
20 #include "nsURLHelper.h"
21 // Put DNSLogging.h at the end to avoid LOG being overwritten by other headers.
22 #include "DNSLogging.h"
23
24 static const char kODoHProxyURIPref[] = "network.trr.odoh.proxy_uri";
25 static const char kODoHTargetHostPref[] = "network.trr.odoh.target_host";
26 static const char kODoHTargetPathPref[] = "network.trr.odoh.target_path";
27 static const char kODoHConfigsUriPref[] = "network.trr.odoh.configs_uri";
28
29 namespace mozilla {
30 namespace net {
31
32 ODoHService* gODoHService = nullptr;
33
NS_IMPL_ISUPPORTS(ODoHService,nsIDNSListener,nsIObserver,nsISupportsWeakReference,nsITimerCallback,nsIStreamLoaderObserver)34 NS_IMPL_ISUPPORTS(ODoHService, nsIDNSListener, nsIObserver,
35 nsISupportsWeakReference, nsITimerCallback,
36 nsIStreamLoaderObserver)
37
38 ODoHService::ODoHService()
39 : mLock("net::ODoHService"), mQueryODoHConfigInProgress(false) {
40 gODoHService = this;
41 }
42
~ODoHService()43 ODoHService::~ODoHService() { gODoHService = nullptr; }
44
Init()45 bool ODoHService::Init() {
46 MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
47
48 nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
49 if (!prefBranch) {
50 return false;
51 }
52
53 prefBranch->AddObserver(kODoHProxyURIPref, this, true);
54 prefBranch->AddObserver(kODoHTargetHostPref, this, true);
55 prefBranch->AddObserver(kODoHTargetPathPref, this, true);
56 prefBranch->AddObserver(kODoHConfigsUriPref, this, true);
57
58 ReadPrefs(nullptr);
59
60 nsCOMPtr<nsIObserverService> observerService =
61 mozilla::services::GetObserverService();
62 if (observerService) {
63 observerService->AddObserver(this, "xpcom-shutdown-threads", true);
64 }
65
66 return true;
67 }
68
Enabled() const69 bool ODoHService::Enabled() const {
70 return StaticPrefs::network_trr_odoh_enabled();
71 }
72
73 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)74 ODoHService::Observe(nsISupports* aSubject, const char* aTopic,
75 const char16_t* aData) {
76 MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
77 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
78 ReadPrefs(NS_ConvertUTF16toUTF8(aData).get());
79 } else if (!strcmp(aTopic, "xpcom-shutdown-threads")) {
80 if (mTTLTimer) {
81 mTTLTimer->Cancel();
82 mTTLTimer = nullptr;
83 }
84 }
85
86 return NS_OK;
87 }
88
ReadPrefs(const char * aName)89 nsresult ODoHService::ReadPrefs(const char* aName) {
90 if (!aName || !strcmp(aName, kODoHConfigsUriPref)) {
91 OnODohConfigsURIChanged();
92 }
93 if (!aName || !strcmp(aName, kODoHProxyURIPref) ||
94 !strcmp(aName, kODoHTargetHostPref) ||
95 !strcmp(aName, kODoHTargetPathPref)) {
96 OnODoHPrefsChange(aName == nullptr);
97 }
98
99 return NS_OK;
100 }
101
OnODohConfigsURIChanged()102 void ODoHService::OnODohConfigsURIChanged() {
103 nsAutoCString uri;
104 Preferences::GetCString(kODoHConfigsUriPref, uri);
105
106 bool updateConfig = false;
107 {
108 MutexAutoLock lock(mLock);
109 if (!mODoHConfigsUri.Equals(uri)) {
110 mODoHConfigsUri = uri;
111 updateConfig = true;
112 }
113 }
114
115 if (updateConfig) {
116 UpdateODoHConfigFromURI();
117 }
118 }
119
OnODoHPrefsChange(bool aInit)120 void ODoHService::OnODoHPrefsChange(bool aInit) {
121 nsAutoCString proxyURI;
122 Preferences::GetCString(kODoHProxyURIPref, proxyURI);
123 nsAutoCString targetHost;
124 Preferences::GetCString(kODoHTargetHostPref, targetHost);
125 nsAutoCString targetPath;
126 Preferences::GetCString(kODoHTargetPathPref, targetPath);
127
128 bool updateODoHConfig = false;
129 {
130 MutexAutoLock lock(mLock);
131 mODoHProxyURI = proxyURI;
132 // Only update ODoHConfig when the host is really changed.
133 if (!mODoHTargetHost.Equals(targetHost) && mODoHConfigsUri.IsEmpty()) {
134 updateODoHConfig = true;
135 }
136 mODoHTargetHost = targetHost;
137 mODoHTargetPath = targetPath;
138
139 BuildODoHRequestURI();
140 }
141
142 if (updateODoHConfig) {
143 // When this function is called from ODoHService::Init(), it's on the same
144 // call stack as nsDNSService is inited. In this case, we need to dispatch
145 // UpdateODoHConfigFromHTTPSRR(), since recursively getting DNS service is
146 // not allowed.
147 auto task = []() { gODoHService->UpdateODoHConfigFromHTTPSRR(); };
148 if (aInit) {
149 NS_DispatchToMainThread(NS_NewRunnableFunction(
150 "ODoHService::UpdateODoHConfigFromHTTPSRR", std::move(task)));
151 } else {
152 task();
153 }
154 }
155 }
156
ExtractHost(const nsACString & aURI,nsCString & aResult)157 static nsresult ExtractHost(const nsACString& aURI, nsCString& aResult) {
158 nsCOMPtr<nsIURI> uri;
159 nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI);
160 if (NS_FAILED(rv)) {
161 return rv;
162 }
163
164 if (!uri->SchemeIs("https")) {
165 LOG(("ODoHService host uri is not https"));
166 return NS_ERROR_FAILURE;
167 }
168
169 return uri->GetAsciiHost(aResult);
170 }
171
BuildODoHRequestURI()172 void ODoHService::BuildODoHRequestURI() {
173 mLock.AssertCurrentThreadOwns();
174
175 mODoHRequestURI.Truncate();
176 if (mODoHTargetHost.IsEmpty() || mODoHTargetPath.IsEmpty()) {
177 return;
178 }
179
180 if (mODoHProxyURI.IsEmpty()) {
181 mODoHRequestURI.Append(mODoHTargetHost);
182 mODoHRequestURI.AppendLiteral("/");
183 mODoHRequestURI.Append(mODoHTargetPath);
184 } else {
185 nsAutoCString hostStr;
186 if (NS_FAILED(ExtractHost(mODoHTargetHost, hostStr))) {
187 return;
188 }
189
190 mODoHRequestURI.Append(mODoHProxyURI);
191 mODoHRequestURI.AppendLiteral("?targethost=");
192 mODoHRequestURI.Append(hostStr);
193 mODoHRequestURI.AppendLiteral("&targetpath=/");
194 mODoHRequestURI.Append(mODoHTargetPath);
195 }
196 }
197
GetRequestURI(nsACString & aResult)198 void ODoHService::GetRequestURI(nsACString& aResult) {
199 MutexAutoLock lock(mLock);
200 aResult = mODoHRequestURI;
201 }
202
UpdateODoHConfig()203 nsresult ODoHService::UpdateODoHConfig() {
204 LOG(("ODoHService::UpdateODoHConfig"));
205 if (mQueryODoHConfigInProgress) {
206 return NS_OK;
207 }
208
209 if (NS_SUCCEEDED(UpdateODoHConfigFromURI())) {
210 return NS_OK;
211 }
212
213 return UpdateODoHConfigFromHTTPSRR();
214 }
215
UpdateODoHConfigFromURI()216 nsresult ODoHService::UpdateODoHConfigFromURI() {
217 LOG(("ODoHService::UpdateODoHConfigFromURI"));
218
219 nsAutoCString configUri;
220 {
221 MutexAutoLock lock(mLock);
222 configUri = mODoHConfigsUri;
223 }
224
225 if (configUri.IsEmpty() || !StringBeginsWith(configUri, "https://"_ns)) {
226 LOG(("ODoHService::UpdateODoHConfigFromURI: uri is invalid"));
227 return UpdateODoHConfigFromHTTPSRR();
228 }
229
230 nsCOMPtr<nsIEventTarget> target = gTRRService->MainThreadOrTRRThread();
231 if (!target) {
232 return NS_ERROR_UNEXPECTED;
233 }
234
235 if (!target->IsOnCurrentThread()) {
236 nsresult rv = target->Dispatch(NS_NewRunnableFunction(
237 "ODoHService::UpdateODoHConfigFromURI",
238 []() { gODoHService->UpdateODoHConfigFromURI(); }));
239 if (NS_SUCCEEDED(rv)) {
240 // Set mQueryODoHConfigInProgress to true to avoid updating ODoHConfigs
241 // when waiting the runnable to be executed.
242 mQueryODoHConfigInProgress = true;
243 }
244 return rv;
245 }
246
247 // In case any error happens, we should reset mQueryODoHConfigInProgress.
248 auto guard = MakeScopeExit([&]() { mQueryODoHConfigInProgress = false; });
249
250 nsCOMPtr<nsIURI> uri;
251 nsresult rv = NS_NewURI(getter_AddRefs(uri), configUri);
252 if (NS_FAILED(rv)) {
253 return rv;
254 }
255
256 nsCOMPtr<nsIChannel> channel;
257 rv = DNSUtils::CreateChannelHelper(uri, getter_AddRefs(channel));
258 if (NS_FAILED(rv) || !channel) {
259 LOG(("NewChannel failed!"));
260 return rv;
261 }
262
263 channel->SetLoadFlags(
264 nsIRequest::LOAD_ANONYMOUS | nsIRequest::INHIBIT_CACHING |
265 nsIRequest::LOAD_BYPASS_CACHE | nsIChannel::LOAD_BYPASS_URL_CLASSIFIER);
266 NS_ENSURE_SUCCESS(rv, rv);
267
268 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
269 if (!httpChannel) {
270 return NS_ERROR_UNEXPECTED;
271 }
272
273 // This connection should not use TRR
274 rv = httpChannel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE);
275 NS_ENSURE_SUCCESS(rv, rv);
276
277 nsCOMPtr<nsIStreamLoader> loader;
278 rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
279 if (NS_FAILED(rv)) {
280 return rv;
281 }
282
283 rv = httpChannel->AsyncOpen(loader);
284 if (NS_FAILED(rv)) {
285 return rv;
286 }
287
288 // AsyncOpen succeeded, dismiss the guard.
289 guard.release();
290 mLoader.swap(loader);
291 return rv;
292 }
293
UpdateODoHConfigFromHTTPSRR()294 nsresult ODoHService::UpdateODoHConfigFromHTTPSRR() {
295 LOG(("ODoHService::UpdateODoHConfigFromHTTPSRR"));
296
297 nsAutoCString uri;
298 {
299 MutexAutoLock lock(mLock);
300 uri = mODoHTargetHost;
301 }
302
303 nsCOMPtr<nsIDNSService> dns(
304 do_GetService("@mozilla.org/network/dns-service;1"));
305 if (!dns) {
306 return NS_ERROR_NOT_AVAILABLE;
307 }
308
309 if (!gTRRService) {
310 return NS_ERROR_NOT_AVAILABLE;
311 }
312
313 nsAutoCString hostStr;
314 nsresult rv = ExtractHost(uri, hostStr);
315 if (NS_FAILED(rv)) {
316 return rv;
317 }
318
319 nsCOMPtr<nsICancelable> tmpOutstanding;
320 nsCOMPtr<nsIEventTarget> target = gTRRService->MainThreadOrTRRThread();
321 // We'd like to bypass the DNS cache, since ODoHConfigs will be updated
322 // manually by ODoHService.
323 uint32_t flags =
324 nsIDNSService::RESOLVE_DISABLE_ODOH | nsIDNSService::RESOLVE_BYPASS_CACHE;
325 rv = dns->AsyncResolveNative(hostStr, nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
326 flags, nullptr, this, target, OriginAttributes(),
327 getter_AddRefs(tmpOutstanding));
328 LOG(("ODoHService::UpdateODoHConfig [host=%s rv=%" PRIx32 "]", hostStr.get(),
329 static_cast<uint32_t>(rv)));
330
331 if (NS_SUCCEEDED(rv)) {
332 mQueryODoHConfigInProgress = true;
333 }
334 return rv;
335 }
336
StartTTLTimer(uint32_t aTTL)337 void ODoHService::StartTTLTimer(uint32_t aTTL) {
338 if (mTTLTimer) {
339 mTTLTimer->Cancel();
340 mTTLTimer = nullptr;
341 }
342 LOG(("ODoHService::StartTTLTimer ttl=%d(s)", aTTL));
343 NS_NewTimerWithCallback(getter_AddRefs(mTTLTimer), this, aTTL * 1000,
344 nsITimer::TYPE_ONE_SHOT);
345 }
346
347 NS_IMETHODIMP
Notify(nsITimer * aTimer)348 ODoHService::Notify(nsITimer* aTimer) {
349 MOZ_ASSERT(aTimer == mTTLTimer);
350 UpdateODoHConfig();
351 return NS_OK;
352 }
353
ODoHConfigUpdateDone(uint32_t aTTL,Span<const uint8_t> aRawConfig)354 void ODoHService::ODoHConfigUpdateDone(uint32_t aTTL,
355 Span<const uint8_t> aRawConfig) {
356 MOZ_ASSERT_IF(XRE_IsParentProcess() && gTRRService,
357 NS_IsMainThread() || gTRRService->IsOnTRRThread());
358 MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
359
360 mQueryODoHConfigInProgress = false;
361 mODoHConfigs.reset();
362
363 nsTArray<ObliviousDoHConfig> configs;
364 if (ODoHDNSPacket::ParseODoHConfigs(aRawConfig, configs)) {
365 mODoHConfigs.emplace(std::move(configs));
366 }
367
368 // Let observers know whether ODoHService is activated or not.
369 bool hasODoHConfigs = mODoHConfigs && !mODoHConfigs->IsEmpty();
370 if (aTTL < StaticPrefs::network_trr_odoh_min_ttl()) {
371 aTTL = StaticPrefs::network_trr_odoh_min_ttl();
372 }
373 auto task = [hasODoHConfigs, aTTL]() {
374 MOZ_ASSERT(NS_IsMainThread());
375 if (XRE_IsSocketProcess()) {
376 SocketProcessChild::GetSingleton()->SendODoHServiceActivated(
377 hasODoHConfigs);
378 }
379
380 nsCOMPtr<nsIObserverService> observerService =
381 mozilla::services::GetObserverService();
382
383 if (observerService) {
384 observerService->NotifyObservers(nullptr, "odoh-service-activated",
385 hasODoHConfigs ? u"true" : u"false");
386 }
387
388 if (aTTL) {
389 gODoHService->StartTTLTimer(aTTL);
390 }
391 };
392
393 if (NS_IsMainThread()) {
394 task();
395 } else {
396 NS_DispatchToMainThread(
397 NS_NewRunnableFunction("ODoHService::Activated", std::move(task)));
398 }
399
400 if (!mPendingRequests.IsEmpty()) {
401 nsTArray<RefPtr<ODoH>> requests = std::move(mPendingRequests);
402 nsCOMPtr<nsIEventTarget> target = gTRRService->MainThreadOrTRRThread();
403 for (auto& query : requests) {
404 target->Dispatch(query.forget());
405 }
406 }
407 }
408
409 NS_IMETHODIMP
OnLookupComplete(nsICancelable * aRequest,nsIDNSRecord * aRec,nsresult aStatus)410 ODoHService::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRec,
411 nsresult aStatus) {
412 MOZ_ASSERT_IF(XRE_IsParentProcess() && gTRRService,
413 NS_IsMainThread() || gTRRService->IsOnTRRThread());
414 MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
415
416 nsCOMPtr<nsIDNSHTTPSSVCRecord> httpsRecord;
417 nsCString rawODoHConfig;
418 auto notifyDone = MakeScopeExit([&]() {
419 uint32_t ttl = 0;
420 if (httpsRecord) {
421 Unused << httpsRecord->GetTtl(&ttl);
422 }
423
424 ODoHConfigUpdateDone(
425 ttl,
426 Span(reinterpret_cast<const uint8_t*>(rawODoHConfig.BeginReading()),
427 rawODoHConfig.Length()));
428 });
429
430 LOG(("ODoHService::OnLookupComplete [aStatus=%" PRIx32 "]",
431 static_cast<uint32_t>(aStatus)));
432 if (NS_FAILED(aStatus)) {
433 return NS_OK;
434 }
435
436 httpsRecord = do_QueryInterface(aRec);
437 if (!httpsRecord) {
438 return NS_OK;
439 }
440
441 nsTArray<RefPtr<nsISVCBRecord>> svcbRecords;
442 httpsRecord->GetRecords(svcbRecords);
443 for (const auto& record : svcbRecords) {
444 Unused << record->GetODoHConfig(rawODoHConfig);
445 if (!rawODoHConfig.IsEmpty()) {
446 break;
447 }
448 }
449
450 return NS_OK;
451 }
452
453 NS_IMETHODIMP
OnStreamComplete(nsIStreamLoader * aLoader,nsISupports * aContext,nsresult aStatus,uint32_t aLength,const uint8_t * aContent)454 ODoHService::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
455 nsresult aStatus, uint32_t aLength,
456 const uint8_t* aContent) {
457 MOZ_ASSERT_IF(XRE_IsParentProcess() && gTRRService,
458 NS_IsMainThread() || gTRRService->IsOnTRRThread());
459 MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
460 LOG(("ODoHService::OnStreamComplete aLength=%d\n", aLength));
461
462 {
463 MutexAutoLock lock(mLock);
464 mLoader = nullptr;
465 }
466 ODoHConfigUpdateDone(0, Span(aContent, aLength));
467 return NS_OK;
468 }
469
ODoHConfigs()470 const Maybe<nsTArray<ObliviousDoHConfig>>& ODoHService::ODoHConfigs() {
471 MOZ_ASSERT_IF(XRE_IsParentProcess() && gTRRService,
472 NS_IsMainThread() || gTRRService->IsOnTRRThread());
473 MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
474
475 return mODoHConfigs;
476 }
477
AppendPendingODoHRequest(ODoH * aRequest)478 void ODoHService::AppendPendingODoHRequest(ODoH* aRequest) {
479 LOG(("ODoHService::AppendPendingODoHQuery\n"));
480 MOZ_ASSERT_IF(XRE_IsParentProcess() && gTRRService,
481 NS_IsMainThread() || gTRRService->IsOnTRRThread());
482 MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
483
484 mPendingRequests.AppendElement(aRequest);
485 }
486
RemovePendingODoHRequest(ODoH * aRequest)487 bool ODoHService::RemovePendingODoHRequest(ODoH* aRequest) {
488 MOZ_ASSERT_IF(XRE_IsParentProcess() && gTRRService,
489 NS_IsMainThread() || gTRRService->IsOnTRRThread());
490 MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
491
492 return mPendingRequests.RemoveElement(aRequest);
493 }
494
495 } // namespace net
496 } // namespace mozilla
497