TRRServiceBase.cpp (11552B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set sw=2 ts=8 et tw=80 : */ 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 "TRRServiceBase.h" 8 9 #include "TRRService.h" 10 #include "mozilla/Preferences.h" 11 #include "nsHostResolver.h" 12 #include "nsNetUtil.h" 13 #include "nsIOService.h" 14 #include "nsIDNSService.h" 15 #include "nsIProxyInfo.h" 16 #include "nsHttpConnectionInfo.h" 17 #include "nsHttpHandler.h" 18 #include "mozilla/StaticPrefs_network.h" 19 #include "AlternateServices.h" 20 #include "ProxyConfigLookup.h" 21 // Put DNSLogging.h at the end to avoid LOG being overwritten by other headers. 22 #include "DNSLogging.h" 23 24 #if defined(XP_WIN) 25 # include <shlobj.h> // for SHGetSpecialFolderPathA 26 #endif // XP_WIN 27 28 namespace mozilla { 29 namespace net { 30 31 NS_IMPL_ISUPPORTS(TRRServiceBase, nsIProxyConfigChangedCallback) 32 33 TRRServiceBase::TRRServiceBase() 34 : mDefaultTRRConnectionInfo("DataMutex::mDefaultTRRConnectionInfo") {} 35 36 TRRServiceBase::~TRRServiceBase() { 37 if (mTRRConnectionInfoInited) { 38 UnregisterProxyChangeListener(); 39 } 40 } 41 42 void TRRServiceBase::ProcessURITemplate(nsACString& aURI) { 43 // URI Template, RFC 6570. 44 if (aURI.IsEmpty()) { 45 return; 46 } 47 nsAutoCString scheme; 48 nsCOMPtr<nsIIOService> ios(do_GetIOService()); 49 if (ios) { 50 ios->ExtractScheme(aURI, scheme); 51 } 52 if (!scheme.Equals("https")) { 53 LOG(("TRRService TRR URI %s is not https. Not used.\n", 54 PromiseFlatCString(aURI).get())); 55 aURI.Truncate(); 56 return; 57 } 58 59 // cut off everything from "{" to "}" sequences (potentially multiple), 60 // as a crude conversion from template into URI. 61 nsAutoCString uri(aURI); 62 63 do { 64 nsCCharSeparatedTokenizer openBrace(uri, '{'); 65 if (openBrace.hasMoreTokens()) { 66 // the 'nextToken' is the left side of the open brace (or full uri) 67 nsAutoCString prefix(openBrace.nextToken()); 68 69 // if there is an open brace, there's another token 70 const nsACString& endBrace = openBrace.nextToken(); 71 nsCCharSeparatedTokenizer closeBrace(endBrace, '}'); 72 if (closeBrace.hasMoreTokens()) { 73 // there is a close brace as well, make a URI out of the prefix 74 // and the suffix 75 closeBrace.nextToken(); 76 nsAutoCString suffix(closeBrace.nextToken()); 77 uri = prefix + suffix; 78 } else { 79 // no (more) close brace 80 break; 81 } 82 } else { 83 // no (more) open brace 84 break; 85 } 86 } while (true); 87 88 aURI = uri; 89 } 90 91 void TRRServiceBase::CheckURIPrefs() { 92 mURISetByDetection = false; 93 94 if (StaticPrefs::network_trr_use_ohttp() && !mOHTTPURIPref.IsEmpty()) { 95 MaybeSetPrivateURI(mOHTTPURIPref); 96 return; 97 } 98 99 // The user has set a custom URI so it takes precedence. 100 if (!mURIPref.IsEmpty()) { 101 MaybeSetPrivateURI(mURIPref); 102 return; 103 } 104 105 // Check if the rollout addon has set a pref. 106 if (!mRolloutURIPref.IsEmpty()) { 107 MaybeSetPrivateURI(mRolloutURIPref); 108 return; 109 } 110 111 // Otherwise just use the default value. 112 MaybeSetPrivateURI(mDefaultURIPref); 113 } 114 115 // static 116 nsIDNSService::ResolverMode ModeFromPrefs( 117 nsIDNSService::ResolverMode& aTRRModePrefValue) { 118 // 0 - off, 1 - reserved, 2 - TRR first, 3 - TRR only, 4 - reserved, 119 // 5 - explicit off 120 121 auto processPrefValue = [](uint32_t value) -> nsIDNSService::ResolverMode { 122 if (value == nsIDNSService::MODE_RESERVED1 || 123 value == nsIDNSService::MODE_RESERVED4 || 124 value > nsIDNSService::MODE_TRROFF) { 125 return nsIDNSService::MODE_TRROFF; 126 } 127 return static_cast<nsIDNSService::ResolverMode>(value); 128 }; 129 130 uint32_t tmp; 131 if (NS_FAILED(Preferences::GetUint("network.trr.mode", &tmp))) { 132 tmp = 0; 133 } 134 nsIDNSService::ResolverMode modeFromPref = processPrefValue(tmp); 135 aTRRModePrefValue = modeFromPref; 136 137 if (modeFromPref != nsIDNSService::MODE_NATIVEONLY) { 138 return modeFromPref; 139 } 140 141 if (NS_FAILED(Preferences::GetUint(kRolloutModePref, &tmp))) { 142 tmp = 0; 143 } 144 modeFromPref = processPrefValue(tmp); 145 146 return modeFromPref; 147 } 148 149 void TRRServiceBase::OnTRRModeChange() { 150 uint32_t oldMode = mMode; 151 // This is the value of the pref "network.trr.mode" 152 nsIDNSService::ResolverMode trrModePrefValue; 153 mMode = ModeFromPrefs(trrModePrefValue); 154 if (mMode != oldMode) { 155 LOG(("TRR Mode changed from %d to %d", oldMode, int(mMode))); 156 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 157 if (obs) { 158 obs->NotifyObservers(nullptr, NS_NETWORK_TRR_MODE_CHANGED_TOPIC, nullptr); 159 } 160 161 TRRService::SetCurrentTRRMode(trrModePrefValue); 162 } 163 164 static bool readHosts = false; 165 // When native HTTPS query is enabled, we need to read etc/hosts. 166 if ((mMode == nsIDNSService::MODE_TRRFIRST || 167 mMode == nsIDNSService::MODE_TRRONLY || mNativeHTTPSQueryEnabled) && 168 !readHosts) { 169 readHosts = true; 170 ReadEtcHostsFile(); 171 } 172 } 173 174 void TRRServiceBase::OnTRRURIChange() { 175 Preferences::GetCString("network.trr.uri", mURIPref); 176 Preferences::GetCString(kRolloutURIPref, mRolloutURIPref); 177 Preferences::GetCString("network.trr.default_provider_uri", mDefaultURIPref); 178 Preferences::GetCString("network.trr.ohttp.uri", mOHTTPURIPref); 179 180 CheckURIPrefs(); 181 } 182 183 static already_AddRefed<nsHttpConnectionInfo> CreateConnInfoHelper( 184 nsIURI* aURI, nsIProxyInfo* aProxyInfo) { 185 MOZ_ASSERT(NS_IsMainThread()); 186 187 nsAutoCString host; 188 nsAutoCString scheme; 189 nsAutoCString username; 190 int32_t port = -1; 191 bool isHttps = aURI->SchemeIs("https"); 192 193 nsresult rv = aURI->GetScheme(scheme); 194 if (NS_FAILED(rv)) { 195 return nullptr; 196 } 197 198 rv = aURI->GetAsciiHost(host); 199 if (NS_FAILED(rv)) { 200 return nullptr; 201 } 202 203 rv = aURI->GetPort(&port); 204 if (NS_FAILED(rv)) { 205 return nullptr; 206 } 207 208 // Just a warning here because some nsIURIs do not implement this method. 209 if (NS_WARN_IF(NS_FAILED(aURI->GetUsername(username)))) { 210 LOG(("Failed to get username for aURI(%s)", 211 aURI->GetSpecOrDefault().get())); 212 } 213 214 gHttpHandler->MaybeAddAltSvcForTesting(aURI, username, false, nullptr, 215 OriginAttributes()); 216 217 nsCOMPtr<nsProxyInfo> proxyInfo = do_QueryInterface(aProxyInfo); 218 RefPtr<nsHttpConnectionInfo> connInfo = new nsHttpConnectionInfo( 219 host, port, ""_ns, username, proxyInfo, OriginAttributes(), isHttps); 220 bool http2Allowed = !gHttpHandler->IsHttp2Excluded(connInfo); 221 bool http3Allowed = proxyInfo ? proxyInfo->IsDirect() : true; 222 223 RefPtr<AltSvcMapping> mapping; 224 if ((http2Allowed || http3Allowed) && 225 AltSvcMapping::AcceptableProxy(proxyInfo) && 226 (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https")) && 227 (mapping = gHttpHandler->GetAltServiceMapping( 228 scheme, host, port, false, OriginAttributes(), http2Allowed, 229 http3Allowed))) { 230 mapping->GetConnectionInfo(getter_AddRefs(connInfo), proxyInfo, 231 OriginAttributes()); 232 } 233 234 return connInfo.forget(); 235 } 236 237 void TRRServiceBase::InitTRRConnectionInfo(bool aForceReinit) { 238 if (!XRE_IsParentProcess()) { 239 return; 240 } 241 242 if (mTRRConnectionInfoInited && !aForceReinit) { 243 return; 244 } 245 246 if (!NS_IsMainThread()) { 247 NS_DispatchToMainThread(NS_NewRunnableFunction( 248 "TRRServiceBase::InitTRRConnectionInfo", 249 [self = RefPtr{this}]() { self->InitTRRConnectionInfo(); })); 250 return; 251 } 252 253 LOG(("TRRServiceBase::InitTRRConnectionInfo")); 254 nsAutoCString uri; 255 GetURI(uri); 256 AsyncCreateTRRConnectionInfoInternal(uri); 257 } 258 259 void TRRServiceBase::AsyncCreateTRRConnectionInfo(const nsACString& aURI) { 260 LOG( 261 ("TRRServiceBase::AsyncCreateTRRConnectionInfo " 262 "mTRRConnectionInfoInited=%d", 263 bool(mTRRConnectionInfoInited))); 264 if (!mTRRConnectionInfoInited) { 265 return; 266 } 267 268 AsyncCreateTRRConnectionInfoInternal(aURI); 269 } 270 271 void TRRServiceBase::AsyncCreateTRRConnectionInfoInternal( 272 const nsACString& aURI) { 273 if (!XRE_IsParentProcess()) { 274 return; 275 } 276 277 SetDefaultTRRConnectionInfo(nullptr); 278 279 MOZ_ASSERT(NS_IsMainThread()); 280 281 nsCOMPtr<nsIURI> dnsURI; 282 nsresult rv = NS_NewURI(getter_AddRefs(dnsURI), aURI); 283 if (NS_FAILED(rv)) { 284 return; 285 } 286 287 rv = ProxyConfigLookup::Create( 288 [self = RefPtr{this}, uri(dnsURI)](nsIProxyInfo* aProxyInfo, 289 nsresult aStatus) mutable { 290 if (NS_FAILED(aStatus)) { 291 self->SetDefaultTRRConnectionInfo(nullptr); 292 return; 293 } 294 295 RefPtr<nsHttpConnectionInfo> connInfo = 296 CreateConnInfoHelper(uri, aProxyInfo); 297 self->SetDefaultTRRConnectionInfo(connInfo); 298 if (!self->mTRRConnectionInfoInited) { 299 self->mTRRConnectionInfoInited = true; 300 self->RegisterProxyChangeListener(); 301 } 302 }, 303 dnsURI, 0, nullptr); 304 305 // mDefaultTRRConnectionInfo is set to nullptr at the beginning of this 306 // method, so we don't really care aobut the |rv| here. If it's failed, 307 // mDefaultTRRConnectionInfo stays as nullptr and we'll create a new 308 // connection info in TRRServiceChannel again. 309 (void)NS_WARN_IF(NS_FAILED(rv)); 310 } 311 312 already_AddRefed<nsHttpConnectionInfo> TRRServiceBase::TRRConnectionInfo() { 313 RefPtr<nsHttpConnectionInfo> connInfo; 314 { 315 auto lock = mDefaultTRRConnectionInfo.Lock(); 316 connInfo = *lock; 317 } 318 return connInfo.forget(); 319 } 320 321 void TRRServiceBase::SetDefaultTRRConnectionInfo( 322 nsHttpConnectionInfo* aConnInfo) { 323 LOG(("TRRService::SetDefaultTRRConnectionInfo aConnInfo=%s", 324 aConnInfo ? aConnInfo->HashKey().get() : "none")); 325 { 326 auto lock = mDefaultTRRConnectionInfo.Lock(); 327 lock.ref() = aConnInfo; 328 } 329 } 330 331 void TRRServiceBase::RegisterProxyChangeListener() { 332 if (!XRE_IsParentProcess()) { 333 return; 334 } 335 336 nsCOMPtr<nsIProtocolProxyService> pps = 337 do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID); 338 if (!pps) { 339 return; 340 } 341 342 pps->AddProxyConfigCallback(this); 343 } 344 345 void TRRServiceBase::UnregisterProxyChangeListener() { 346 if (!XRE_IsParentProcess()) { 347 return; 348 } 349 350 nsCOMPtr<nsIProtocolProxyService> pps = 351 do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID); 352 if (!pps) { 353 return; 354 } 355 356 pps->RemoveProxyConfigCallback(this); 357 } 358 359 void TRRServiceBase::DoReadEtcHostsFile(ParsingCallback aCallback) { 360 MOZ_ASSERT(XRE_IsParentProcess()); 361 362 if (!StaticPrefs::network_trr_exclude_etc_hosts()) { 363 return; 364 } 365 366 auto readHostsTask = [aCallback]() { 367 MOZ_ASSERT(!NS_IsMainThread(), "Must not run on the main thread"); 368 #if defined(XP_WIN) 369 // Inspired by libevent/evdns.c 370 // Windows is a little coy about where it puts its configuration 371 // files. Sure, they're _usually_ in C:\windows\system32, but 372 // there's no reason in principle they couldn't be in 373 // W:\hoboken chicken emergency 374 375 nsCString path; 376 path.SetLength(MAX_PATH + 1); 377 if (!SHGetSpecialFolderPathA(NULL, path.BeginWriting(), CSIDL_SYSTEM, 378 false)) { 379 LOG(("Calling SHGetSpecialFolderPathA failed")); 380 return; 381 } 382 383 path.SetLength(strlen(path.get())); 384 path.Append("\\drivers\\etc\\hosts"); 385 #else 386 nsAutoCString path("/etc/hosts"_ns); 387 #endif 388 389 LOG(("Reading hosts file at %s", path.get())); 390 rust_parse_etc_hosts(&path, aCallback); 391 }; 392 393 (void)NS_DispatchBackgroundTask( 394 NS_NewRunnableFunction("Read /etc/hosts file", readHostsTask), 395 NS_DISPATCH_EVENT_MAY_BLOCK); 396 } 397 398 } // namespace net 399 } // namespace mozilla