1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 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/. */
7 #include "GetAddrInfo.h"
9 #ifdef DNSQUERY_AVAILABLE
10 // There is a bug in windns.h where the type of parameter ppQueryResultsSet for
11 // DnsQuery_A is dependent on UNICODE being set. It should *always* be
12 // PDNS_RECORDA, but if UNICODE is set it is PDNS_RECORDW. To get around this
13 // we make sure that UNICODE is unset.
15 # include <ws2tcpip.h>
18 #endif // DNSQUERY_AVAILABLE
20 #include "mozilla/ClearOnShutdown.h"
21 #include "mozilla/net/DNS.h"
22 #include "NativeDNSResolverOverrideParent.h"
24 #include "nsIOService.h"
25 #include "nsHostResolver.h"
27 #include "mozilla/net/DNS.h"
31 #include "mozilla/Logging.h"
32 #include "mozilla/StaticPrefs_network.h"
33 #include "mozilla/net/DNSPacket.h"
34 #include "nsIDNSService.h"
36 namespace mozilla::net
{
38 static StaticRefPtr
<NativeDNSResolverOverride
> gOverrideService
;
40 LazyLogModule
gGetAddrInfoLog("GetAddrInfo");
41 #define LOG(msg, ...) \
42 MOZ_LOG(gGetAddrInfoLog, LogLevel::Debug, ("[DNS]: " msg, ##__VA_ARGS__))
43 #define LOG_WARNING(msg, ...) \
44 MOZ_LOG(gGetAddrInfoLog, LogLevel::Warning, ("[DNS]: " msg, ##__VA_ARGS__))
46 #ifdef DNSQUERY_AVAILABLE
48 # define COMPUTER_NAME_BUFFER_SIZE 100
49 static char sDNSComputerName
[COMPUTER_NAME_BUFFER_SIZE
];
50 static char sNETBIOSComputerName
[MAX_COMPUTERNAME_LENGTH
+ 1];
52 ////////////////////////////
53 // WINDOWS IMPLEMENTATION //
54 ////////////////////////////
56 // Ensure consistency of PR_* and AF_* constants to allow for legacy usage of
57 // PR_* constants with this API.
58 static_assert(PR_AF_INET
== AF_INET
&& PR_AF_INET6
== AF_INET6
&&
59 PR_AF_UNSPEC
== AF_UNSPEC
,
60 "PR_AF_* must match AF_*");
62 // If successful, returns in aResult a TTL value that is smaller or
63 // equal with the one already there. Gets the TTL value by calling
64 // to DnsQuery_A and iterating through the returned
65 // records to find the one with the smallest TTL value.
66 static MOZ_ALWAYS_INLINE nsresult
_CallDnsQuery_A_Windows(
67 const nsACString
& aHost
, uint16_t aAddressFamily
, DWORD aFlags
,
68 std::function
<void(PDNS_RECORDA
)> aCallback
) {
69 NS_ConvertASCIItoUTF16
name(aHost
);
71 auto callDnsQuery_A
= [&](uint16_t reqFamily
) {
72 PDNS_RECORDA dnsData
= nullptr;
73 DNS_STATUS status
= DnsQuery_A(aHost
.BeginReading(), reqFamily
, aFlags
,
74 nullptr, &dnsData
, nullptr);
75 if (status
== DNS_INFO_NO_RECORDS
|| status
== DNS_ERROR_RCODE_NAME_ERROR
||
77 LOG("No DNS records found for %s. status=%lX. reqFamily = %X\n",
78 aHost
.BeginReading(), status
, reqFamily
);
79 return NS_ERROR_FAILURE
;
80 } else if (status
!= NOERROR
) {
81 LOG_WARNING("DnsQuery_A failed with status %lX.\n", status
);
82 return NS_ERROR_UNEXPECTED
;
85 for (PDNS_RECORDA curRecord
= dnsData
; curRecord
;
86 curRecord
= curRecord
->pNext
) {
87 // Only records in the answer section are important
88 if (curRecord
->Flags
.S
.Section
!= DnsSectionAnswer
) {
91 if (curRecord
->wType
!= reqFamily
) {
98 DnsFree(dnsData
, DNS_FREE_TYPE::DnsFreeRecordList
);
102 if (aAddressFamily
== PR_AF_UNSPEC
|| aAddressFamily
== PR_AF_INET
) {
103 callDnsQuery_A(DNS_TYPE_A
);
106 if (aAddressFamily
== PR_AF_UNSPEC
|| aAddressFamily
== PR_AF_INET6
) {
107 callDnsQuery_A(DNS_TYPE_AAAA
);
112 bool recordTypeMatchesRequest(uint16_t wType
, uint16_t aAddressFamily
) {
113 if (aAddressFamily
== PR_AF_UNSPEC
) {
114 return wType
== DNS_TYPE_A
|| wType
== DNS_TYPE_AAAA
;
116 if (aAddressFamily
== PR_AF_INET
) {
117 return wType
== DNS_TYPE_A
;
119 if (aAddressFamily
== PR_AF_INET6
) {
120 return wType
== DNS_TYPE_AAAA
;
125 static MOZ_ALWAYS_INLINE nsresult
_GetTTLData_Windows(const nsACString
& aHost
,
127 uint16_t aAddressFamily
) {
128 MOZ_ASSERT(!aHost
.IsEmpty());
130 if (aAddressFamily
!= PR_AF_UNSPEC
&& aAddressFamily
!= PR_AF_INET
&&
131 aAddressFamily
!= PR_AF_INET6
) {
132 return NS_ERROR_UNEXPECTED
;
135 // In order to avoid using ANY records which are not always implemented as a
136 // "Gimme what you have" request in hostname resolvers, we should send A
137 // and/or AAAA requests, based on the address family requested.
138 const DWORD ttlFlags
=
139 (DNS_QUERY_STANDARD
| DNS_QUERY_NO_NETBT
| DNS_QUERY_NO_HOSTS_FILE
|
140 DNS_QUERY_NO_MULTICAST
| DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE
|
141 DNS_QUERY_DONT_RESET_TTL_VALUES
);
142 unsigned int ttl
= (unsigned int)-1;
143 _CallDnsQuery_A_Windows(
144 aHost
, aAddressFamily
, ttlFlags
,
145 [&ttl
, &aHost
, aAddressFamily
](PDNS_RECORDA curRecord
) {
146 if (recordTypeMatchesRequest(curRecord
->wType
, aAddressFamily
)) {
147 ttl
= std::min
<unsigned int>(ttl
, curRecord
->dwTtl
);
149 LOG("Received unexpected record type %u in response for %s.\n",
150 curRecord
->wType
, aHost
.BeginReading());
154 if (ttl
== (unsigned int)-1) {
155 LOG("No useable TTL found.");
156 return NS_ERROR_FAILURE
;
163 static MOZ_ALWAYS_INLINE nsresult
164 _DNSQuery_A_SingleLabel(const nsACString
& aCanonHost
, uint16_t aAddressFamily
,
165 uint16_t aFlags
, AddrInfo
** aAddrInfo
) {
166 bool setCanonName
= aFlags
& nsHostResolver::RES_CANON_NAME
;
167 nsAutoCString canonName
;
168 const DWORD flags
= (DNS_QUERY_STANDARD
| DNS_QUERY_NO_MULTICAST
|
169 DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE
);
170 nsTArray
<NetAddr
> addresses
;
172 _CallDnsQuery_A_Windows(
173 aCanonHost
, aAddressFamily
, flags
, [&](PDNS_RECORDA curRecord
) {
174 MOZ_DIAGNOSTIC_ASSERT(curRecord
->wType
== DNS_TYPE_A
||
175 curRecord
->wType
== DNS_TYPE_AAAA
);
177 canonName
.Assign(curRecord
->pName
);
180 addr
.inet
.family
= AF_INET
;
181 addr
.inet
.ip
= curRecord
->Data
.A
.IpAddress
;
182 addresses
.AppendElement(addr
);
185 LOG("Query for: %s has %zu results", aCanonHost
.BeginReading(),
187 if (addresses
.IsEmpty()) {
188 return NS_ERROR_UNKNOWN_HOST
;
190 RefPtr
<AddrInfo
> ai(new AddrInfo(
191 aCanonHost
, canonName
, DNSResolverType::Native
, 0, std::move(addresses
)));
192 ai
.forget(aAddrInfo
);
199 ////////////////////////////////////
200 // PORTABLE RUNTIME IMPLEMENTATION//
201 ////////////////////////////////////
203 static MOZ_ALWAYS_INLINE nsresult
204 _GetAddrInfo_Portable(const nsACString
& aCanonHost
, uint16_t aAddressFamily
,
205 uint16_t aFlags
, AddrInfo
** aAddrInfo
) {
206 MOZ_ASSERT(!aCanonHost
.IsEmpty());
207 MOZ_ASSERT(aAddrInfo
);
209 // We accept the same aFlags that nsHostResolver::ResolveHost accepts, but we
210 // need to translate the aFlags into a form that PR_GetAddrInfoByName
212 int prFlags
= PR_AI_ADDRCONFIG
;
213 if (!(aFlags
& nsHostResolver::RES_CANON_NAME
)) {
214 prFlags
|= PR_AI_NOCANONNAME
;
217 // We need to remove IPv4 records manually because PR_GetAddrInfoByName
218 // doesn't support PR_AF_INET6.
219 bool disableIPv4
= aAddressFamily
== PR_AF_INET6
;
221 aAddressFamily
= PR_AF_UNSPEC
;
224 #if defined(DNSQUERY_AVAILABLE)
225 if (StaticPrefs::network_dns_dns_query_single_label() &&
226 !aCanonHost
.Contains('.') && aCanonHost
!= "localhost"_ns
) {
227 // For some reason we can't use DnsQuery_A to get the computer's IP.
228 if (!aCanonHost
.Equals(nsDependentCString(sDNSComputerName
),
229 nsCaseInsensitiveCStringComparator
) &&
230 !aCanonHost
.Equals(nsDependentCString(sNETBIOSComputerName
),
231 nsCaseInsensitiveCStringComparator
)) {
232 // This is a single label name resolve without a dot.
233 // We use DNSQuery_A for these.
234 LOG("Resolving %s using DnsQuery_A (computername: %s)\n",
235 aCanonHost
.BeginReading(), sDNSComputerName
);
236 return _DNSQuery_A_SingleLabel(aCanonHost
, aAddressFamily
, aFlags
,
242 LOG("Resolving %s using PR_GetAddrInfoByName", aCanonHost
.BeginReading());
244 PR_GetAddrInfoByName(aCanonHost
.BeginReading(), aAddressFamily
, prFlags
);
247 LOG("PR_GetAddrInfoByName returned null PR_GetError:%d PR_GetOSErrpr:%d",
248 PR_GetError(), PR_GetOSError());
249 return NS_ERROR_UNKNOWN_HOST
;
252 nsAutoCString canonName
;
253 if (aFlags
& nsHostResolver::RES_CANON_NAME
) {
254 canonName
.Assign(PR_GetCanonNameFromAddrInfo(prai
));
257 bool filterNameCollision
=
258 !(aFlags
& nsHostResolver::RES_ALLOW_NAME_COLLISION
);
259 RefPtr
<AddrInfo
> ai(new AddrInfo(aCanonHost
, prai
, disableIPv4
,
260 filterNameCollision
, canonName
));
261 PR_FreeAddrInfo(prai
);
262 if (ai
->Addresses().IsEmpty()) {
263 LOG("PR_GetAddrInfoByName returned empty address list");
264 return NS_ERROR_UNKNOWN_HOST
;
267 ai
.forget(aAddrInfo
);
269 LOG("PR_GetAddrInfoByName resolved successfully");
273 //////////////////////////////////////
274 // COMMON/PLATFORM INDEPENDENT CODE //
275 //////////////////////////////////////
276 nsresult
GetAddrInfoInit() {
277 LOG("Initializing GetAddrInfo.\n");
279 #ifdef DNSQUERY_AVAILABLE
280 DWORD namesize
= COMPUTER_NAME_BUFFER_SIZE
;
281 if (!GetComputerNameExA(ComputerNameDnsHostname
, sDNSComputerName
,
283 sDNSComputerName
[0] = 0;
285 namesize
= MAX_COMPUTERNAME_LENGTH
+ 1;
286 if (!GetComputerNameExA(ComputerNameNetBIOS
, sNETBIOSComputerName
,
288 sNETBIOSComputerName
[0] = 0;
294 nsresult
GetAddrInfoShutdown() {
295 LOG("Shutting down GetAddrInfo.\n");
299 bool FindAddrOverride(const nsACString
& aHost
, uint16_t aAddressFamily
,
300 uint16_t aFlags
, AddrInfo
** aAddrInfo
) {
301 RefPtr
<NativeDNSResolverOverride
> overrideService
= gOverrideService
;
302 if (!overrideService
) {
305 AutoReadLock
lock(overrideService
->mLock
);
306 auto overrides
= overrideService
->mOverrides
.Lookup(aHost
);
310 nsCString
* cname
= nullptr;
311 if (aFlags
& nsHostResolver::RES_CANON_NAME
) {
312 cname
= overrideService
->mCnames
.Lookup(aHost
).DataPtrOrNull();
317 nsTArray
<NetAddr
> addresses
;
318 for (const auto& ip
: *overrides
) {
319 if (aAddressFamily
!= AF_UNSPEC
&& ip
.raw
.family
!= aAddressFamily
) {
322 addresses
.AppendElement(ip
);
326 ai
= new AddrInfo(aHost
, DNSResolverType::Native
, 0, std::move(addresses
));
328 ai
= new AddrInfo(aHost
, *cname
, DNSResolverType::Native
, 0,
329 std::move(addresses
));
332 ai
.forget(aAddrInfo
);
336 nsresult
GetAddrInfo(const nsACString
& aHost
, uint16_t aAddressFamily
,
337 uint16_t aFlags
, AddrInfo
** aAddrInfo
, bool aGetTtl
) {
338 if (NS_WARN_IF(aHost
.IsEmpty()) || NS_WARN_IF(!aAddrInfo
)) {
339 return NS_ERROR_NULL_POINTER
;
341 *aAddrInfo
= nullptr;
343 if (StaticPrefs::network_dns_disabled()) {
344 return NS_ERROR_UNKNOWN_HOST
;
347 #ifdef DNSQUERY_AVAILABLE
348 // The GetTTLData needs the canonical name to function properly
350 aFlags
|= nsHostResolver::RES_CANON_NAME
;
354 // If there is an override for this host, then we synthetize a result.
355 if (gOverrideService
&&
356 FindAddrOverride(aHost
, aAddressFamily
, aFlags
, aAddrInfo
)) {
357 LOG("Returning IP address from NativeDNSResolverOverride");
358 return (*aAddrInfo
)->Addresses().Length() ? NS_OK
: NS_ERROR_UNKNOWN_HOST
;
362 if (StaticPrefs::network_dns_copy_string_before_call()) {
363 host
= Substring(aHost
.BeginReading(), aHost
.Length());
364 MOZ_ASSERT(aHost
.BeginReading() != host
.BeginReading());
369 if (gNativeIsLocalhost
) {
370 // pretend we use the given host but use IPv4 localhost instead!
371 host
= "localhost"_ns
;
372 aAddressFamily
= PR_AF_INET
;
375 RefPtr
<AddrInfo
> info
;
377 _GetAddrInfo_Portable(host
, aAddressFamily
, aFlags
, getter_AddRefs(info
));
379 #ifdef DNSQUERY_AVAILABLE
380 if (aGetTtl
&& NS_SUCCEEDED(rv
)) {
381 // Figure out the canonical name, or if that fails, just use the host name
384 if (info
&& !info
->CanonicalHostname().IsEmpty()) {
385 name
= info
->CanonicalHostname();
390 LOG("Getting TTL for %s (cname = %s).", host
.get(), name
.get());
392 nsresult ttlRv
= _GetTTLData_Windows(name
, &ttl
, aAddressFamily
);
393 if (NS_SUCCEEDED(ttlRv
)) {
394 auto builder
= info
->Build();
396 info
= builder
.Finish();
397 LOG("Got TTL %u for %s (name = %s).", ttl
, host
.get(), name
.get());
399 LOG_WARNING("Could not get TTL for %s (cname = %s).", host
.get(),
405 info
.forget(aAddrInfo
);
409 bool FindHTTPSRecordOverride(const nsACString
& aHost
,
410 TypeRecordResultType
& aResult
) {
411 LOG("FindHTTPSRecordOverride aHost=%s", nsCString(aHost
).get());
412 RefPtr
<NativeDNSResolverOverride
> overrideService
= gOverrideService
;
413 if (!overrideService
) {
417 AutoReadLock
lock(overrideService
->mLock
);
418 auto overrides
= overrideService
->mHTTPSRecordOverrides
.Lookup(aHost
);
424 nsAutoCString
host(aHost
);
427 LOG("resolving %s\n", host
.get());
429 nsresult rv
= packet
.FillBuffer(
430 [&](unsigned char response
[DNSPacket::MAX_SIZE
]) -> int {
431 if (overrides
->Length() > DNSPacket::MAX_SIZE
) {
434 memcpy(response
, overrides
->Elements(), overrides
->Length());
435 return overrides
->Length();
442 rv
= ParseHTTPSRecord(host
, packet
, aResult
, ttl
);
444 return NS_SUCCEEDED(rv
);
447 nsresult
ParseHTTPSRecord(nsCString
& aHost
, DNSPacket
& aDNSPacket
,
448 TypeRecordResultType
& aResult
, uint32_t& aTTL
) {
452 aDNSPacket
.SetNativePacket(true);
454 int32_t loopCount
= 64;
455 while (loopCount
> 0 && aResult
.is
<Nothing
>()) {
458 nsClassHashtable
<nsCStringHashKey
, DOHresp
> additionalRecords
;
459 rv
= aDNSPacket
.Decode(aHost
, TRRTYPE_HTTPSSVC
, cname
, true, resp
, aResult
,
460 additionalRecords
, aTTL
);
462 LOG("Decode failed %x", static_cast<uint32_t>(rv
));
465 if (!cname
.IsEmpty() && aResult
.is
<Nothing
>()) {
472 if (aResult
.is
<Nothing
>()) {
473 LOG("Result is nothing");
474 // The call succeeded, but no HTTPS records were found.
475 return NS_ERROR_UNKNOWN_HOST
;
481 nsresult
ResolveHTTPSRecord(const nsACString
& aHost
, uint16_t aFlags
,
482 TypeRecordResultType
& aResult
, uint32_t& aTTL
) {
483 if (gOverrideService
) {
484 return FindHTTPSRecordOverride(aHost
, aResult
) ? NS_OK
485 : NS_ERROR_UNKNOWN_HOST
;
488 return ResolveHTTPSRecordImpl(aHost
, aFlags
, aResult
, aTTL
);
492 already_AddRefed
<nsINativeDNSResolverOverride
>
493 NativeDNSResolverOverride::GetSingleton() {
494 if (nsIOService::UseSocketProcess() && XRE_IsParentProcess()) {
495 return NativeDNSResolverOverrideParent::GetSingleton();
498 if (gOverrideService
) {
499 return do_AddRef(gOverrideService
);
502 gOverrideService
= new NativeDNSResolverOverride();
503 ClearOnShutdown(&gOverrideService
);
504 return do_AddRef(gOverrideService
);
507 NS_IMPL_ISUPPORTS(NativeDNSResolverOverride
, nsINativeDNSResolverOverride
)
509 NS_IMETHODIMP
NativeDNSResolverOverride::AddIPOverride(
510 const nsACString
& aHost
, const nsACString
& aIPLiteral
) {
513 if (aIPLiteral
.Equals("N/A"_ns
)) {
514 AutoWriteLock
lock(mLock
);
515 auto& overrides
= mOverrides
.LookupOrInsert(aHost
);
520 if (NS_FAILED(tempAddr
.InitFromString(aIPLiteral
))) {
521 return NS_ERROR_UNEXPECTED
;
524 AutoWriteLock
lock(mLock
);
525 auto& overrides
= mOverrides
.LookupOrInsert(aHost
);
526 overrides
.AppendElement(tempAddr
);
531 NS_IMETHODIMP
NativeDNSResolverOverride::AddHTTPSRecordOverride(
532 const nsACString
& aHost
, const uint8_t* aData
, uint32_t aLength
) {
533 AutoWriteLock
lock(mLock
);
534 nsTArray
<uint8_t> data(aData
, aLength
);
535 mHTTPSRecordOverrides
.InsertOrUpdate(aHost
, std::move(data
));
540 NS_IMETHODIMP
NativeDNSResolverOverride::SetCnameOverride(
541 const nsACString
& aHost
, const nsACString
& aCNAME
) {
542 if (aCNAME
.IsEmpty()) {
543 return NS_ERROR_UNEXPECTED
;
546 AutoWriteLock
lock(mLock
);
547 mCnames
.InsertOrUpdate(aHost
, nsCString(aCNAME
));
552 NS_IMETHODIMP
NativeDNSResolverOverride::ClearHostOverride(
553 const nsACString
& aHost
) {
554 AutoWriteLock
lock(mLock
);
555 mCnames
.Remove(aHost
);
556 auto overrides
= mOverrides
.Extract(aHost
);
565 NS_IMETHODIMP
NativeDNSResolverOverride::ClearOverrides() {
566 AutoWriteLock
lock(mLock
);
572 #ifdef MOZ_NO_HTTPS_IMPL
574 // If there is no platform specific implementation of ResolveHTTPSRecordImpl
575 // we link a dummy implementation here.
576 // Otherwise this is implemented in GetAddrInfoWin/Linux/etc
577 nsresult
ResolveHTTPSRecordImpl(const nsACString
& aHost
, uint16_t aFlags
,
578 TypeRecordResultType
& aResult
, uint32_t& aTTL
) {
579 return NS_ERROR_NOT_IMPLEMENTED
;
582 #endif // MOZ_NO_HTTPS_IMPL
584 } // namespace mozilla::net