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"
35 #include "nsINetworkLinkService.h"
37 namespace mozilla::net
{
39 static StaticRefPtr
<NativeDNSResolverOverride
> gOverrideService
;
41 LazyLogModule
gGetAddrInfoLog("GetAddrInfo");
42 #define LOG(msg, ...) \
43 MOZ_LOG(gGetAddrInfoLog, LogLevel::Debug, ("[DNS]: " msg, ##__VA_ARGS__))
44 #define LOG_WARNING(msg, ...) \
45 MOZ_LOG(gGetAddrInfoLog, LogLevel::Warning, ("[DNS]: " msg, ##__VA_ARGS__))
47 #ifdef DNSQUERY_AVAILABLE
49 # define COMPUTER_NAME_BUFFER_SIZE 100
50 static char sDNSComputerName
[COMPUTER_NAME_BUFFER_SIZE
];
51 static char sNETBIOSComputerName
[MAX_COMPUTERNAME_LENGTH
+ 1];
53 ////////////////////////////
54 // WINDOWS IMPLEMENTATION //
55 ////////////////////////////
57 // Ensure consistency of PR_* and AF_* constants to allow for legacy usage of
58 // PR_* constants with this API.
59 static_assert(PR_AF_INET
== AF_INET
&& PR_AF_INET6
== AF_INET6
&&
60 PR_AF_UNSPEC
== AF_UNSPEC
,
61 "PR_AF_* must match AF_*");
63 // If successful, returns in aResult a TTL value that is smaller or
64 // equal with the one already there. Gets the TTL value by calling
65 // to DnsQuery_A and iterating through the returned
66 // records to find the one with the smallest TTL value.
67 static MOZ_ALWAYS_INLINE nsresult
_CallDnsQuery_A_Windows(
68 const nsACString
& aHost
, uint16_t aAddressFamily
, DWORD aFlags
,
69 std::function
<void(PDNS_RECORDA
)> aCallback
) {
70 NS_ConvertASCIItoUTF16
name(aHost
);
72 auto callDnsQuery_A
= [&](uint16_t reqFamily
) {
73 PDNS_RECORDA dnsData
= nullptr;
74 DNS_STATUS status
= DnsQuery_A(aHost
.BeginReading(), reqFamily
, aFlags
,
75 nullptr, &dnsData
, nullptr);
76 if (status
== DNS_INFO_NO_RECORDS
|| status
== DNS_ERROR_RCODE_NAME_ERROR
||
78 LOG("No DNS records found for %s. status=%lX. reqFamily = %X\n",
79 aHost
.BeginReading(), status
, reqFamily
);
80 return NS_ERROR_FAILURE
;
81 } else if (status
!= NOERROR
) {
82 LOG_WARNING("DnsQuery_A failed with status %lX.\n", status
);
83 return NS_ERROR_UNEXPECTED
;
86 for (PDNS_RECORDA curRecord
= dnsData
; curRecord
;
87 curRecord
= curRecord
->pNext
) {
88 // Only records in the answer section are important
89 if (curRecord
->Flags
.S
.Section
!= DnsSectionAnswer
) {
92 if (curRecord
->wType
!= reqFamily
) {
99 DnsFree(dnsData
, DNS_FREE_TYPE::DnsFreeRecordList
);
103 if (aAddressFamily
== PR_AF_UNSPEC
|| aAddressFamily
== PR_AF_INET
) {
104 callDnsQuery_A(DNS_TYPE_A
);
107 if (aAddressFamily
== PR_AF_UNSPEC
|| aAddressFamily
== PR_AF_INET6
) {
108 callDnsQuery_A(DNS_TYPE_AAAA
);
113 bool recordTypeMatchesRequest(uint16_t wType
, uint16_t aAddressFamily
) {
114 if (aAddressFamily
== PR_AF_UNSPEC
) {
115 return wType
== DNS_TYPE_A
|| wType
== DNS_TYPE_AAAA
;
117 if (aAddressFamily
== PR_AF_INET
) {
118 return wType
== DNS_TYPE_A
;
120 if (aAddressFamily
== PR_AF_INET6
) {
121 return wType
== DNS_TYPE_AAAA
;
126 static MOZ_ALWAYS_INLINE nsresult
_GetTTLData_Windows(const nsACString
& aHost
,
128 uint16_t aAddressFamily
) {
129 MOZ_ASSERT(!aHost
.IsEmpty());
131 if (aAddressFamily
!= PR_AF_UNSPEC
&& aAddressFamily
!= PR_AF_INET
&&
132 aAddressFamily
!= PR_AF_INET6
) {
133 return NS_ERROR_UNEXPECTED
;
136 // In order to avoid using ANY records which are not always implemented as a
137 // "Gimme what you have" request in hostname resolvers, we should send A
138 // and/or AAAA requests, based on the address family requested.
139 const DWORD ttlFlags
=
140 (DNS_QUERY_STANDARD
| DNS_QUERY_NO_NETBT
| DNS_QUERY_NO_HOSTS_FILE
|
141 DNS_QUERY_NO_MULTICAST
| DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE
|
142 DNS_QUERY_DONT_RESET_TTL_VALUES
);
143 unsigned int ttl
= (unsigned int)-1;
144 _CallDnsQuery_A_Windows(
145 aHost
, aAddressFamily
, ttlFlags
,
146 [&ttl
, &aHost
, aAddressFamily
](PDNS_RECORDA curRecord
) {
147 if (recordTypeMatchesRequest(curRecord
->wType
, aAddressFamily
)) {
148 ttl
= std::min
<unsigned int>(ttl
, curRecord
->dwTtl
);
150 LOG("Received unexpected record type %u in response for %s.\n",
151 curRecord
->wType
, aHost
.BeginReading());
155 if (ttl
== (unsigned int)-1) {
156 LOG("No useable TTL found.");
157 return NS_ERROR_FAILURE
;
164 static MOZ_ALWAYS_INLINE nsresult
165 _DNSQuery_A_SingleLabel(const nsACString
& aCanonHost
, uint16_t aAddressFamily
,
166 uint16_t aFlags
, AddrInfo
** aAddrInfo
) {
167 bool setCanonName
= aFlags
& nsIDNSService::RESOLVE_CANONICAL_NAME
;
168 nsAutoCString canonName
;
169 const DWORD flags
= (DNS_QUERY_STANDARD
| DNS_QUERY_NO_MULTICAST
|
170 DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE
);
171 nsTArray
<NetAddr
> addresses
;
173 _CallDnsQuery_A_Windows(
174 aCanonHost
, aAddressFamily
, flags
, [&](PDNS_RECORDA curRecord
) {
175 MOZ_DIAGNOSTIC_ASSERT(curRecord
->wType
== DNS_TYPE_A
||
176 curRecord
->wType
== DNS_TYPE_AAAA
);
178 canonName
.Assign(curRecord
->pName
);
181 addr
.inet
.family
= AF_INET
;
182 addr
.inet
.ip
= curRecord
->Data
.A
.IpAddress
;
183 addresses
.AppendElement(addr
);
186 LOG("Query for: %s has %zu results", aCanonHost
.BeginReading(),
188 if (addresses
.IsEmpty()) {
189 return NS_ERROR_UNKNOWN_HOST
;
191 RefPtr
<AddrInfo
> ai(new AddrInfo(
192 aCanonHost
, canonName
, DNSResolverType::Native
, 0, std::move(addresses
)));
193 ai
.forget(aAddrInfo
);
200 ////////////////////////////////////
201 // PORTABLE RUNTIME IMPLEMENTATION//
202 ////////////////////////////////////
204 static bool SkipIPv6DNSLookup() {
205 #if defined(XP_WIN) || defined(XP_LINUX) || defined(XP_MACOSX)
206 return StaticPrefs::network_dns_skip_ipv6_when_no_addresses() &&
207 !nsINetworkLinkService::HasNonLocalIPv6Address();
213 static MOZ_ALWAYS_INLINE nsresult
214 _GetAddrInfo_Portable(const nsACString
& aCanonHost
, uint16_t aAddressFamily
,
215 nsIDNSService::DNSFlags aFlags
, AddrInfo
** aAddrInfo
) {
216 MOZ_ASSERT(!aCanonHost
.IsEmpty());
217 MOZ_ASSERT(aAddrInfo
);
219 // We accept the same aFlags that nsHostResolver::ResolveHost accepts, but we
220 // need to translate the aFlags into a form that PR_GetAddrInfoByName
222 int prFlags
= PR_AI_ADDRCONFIG
;
223 if (!(aFlags
& nsIDNSService::RESOLVE_CANONICAL_NAME
)) {
224 prFlags
|= PR_AI_NOCANONNAME
;
227 // We need to remove IPv4 records manually because PR_GetAddrInfoByName
228 // doesn't support PR_AF_INET6.
229 bool disableIPv4
= aAddressFamily
== PR_AF_INET6
;
231 aAddressFamily
= PR_AF_UNSPEC
;
234 if (SkipIPv6DNSLookup()) {
235 // If the family was AF_UNSPEC initially, make it AF_INET
236 // when there are no IPv6 addresses.
237 // If the DNS request specified IPv6 specifically, let it
239 if (aAddressFamily
== PR_AF_UNSPEC
&& !disableIPv4
) {
240 aAddressFamily
= PR_AF_INET
;
244 #if defined(DNSQUERY_AVAILABLE)
245 if (StaticPrefs::network_dns_dns_query_single_label() &&
246 !aCanonHost
.Contains('.') && aCanonHost
!= "localhost"_ns
) {
247 // For some reason we can't use DnsQuery_A to get the computer's IP.
248 if (!aCanonHost
.Equals(nsDependentCString(sDNSComputerName
),
249 nsCaseInsensitiveCStringComparator
) &&
250 !aCanonHost
.Equals(nsDependentCString(sNETBIOSComputerName
),
251 nsCaseInsensitiveCStringComparator
)) {
252 // This is a single label name resolve without a dot.
253 // We use DNSQuery_A for these.
254 LOG("Resolving %s using DnsQuery_A (computername: %s)\n",
255 aCanonHost
.BeginReading(), sDNSComputerName
);
256 return _DNSQuery_A_SingleLabel(aCanonHost
, aAddressFamily
, aFlags
,
262 LOG("Resolving %s using PR_GetAddrInfoByName", aCanonHost
.BeginReading());
264 PR_GetAddrInfoByName(aCanonHost
.BeginReading(), aAddressFamily
, prFlags
);
267 LOG("PR_GetAddrInfoByName returned null PR_GetError:%d PR_GetOSErrpr:%d",
268 PR_GetError(), PR_GetOSError());
269 return NS_ERROR_UNKNOWN_HOST
;
272 nsAutoCString canonName
;
273 if (aFlags
& nsIDNSService::RESOLVE_CANONICAL_NAME
) {
274 canonName
.Assign(PR_GetCanonNameFromAddrInfo(prai
));
277 bool filterNameCollision
=
278 !(aFlags
& nsIDNSService::RESOLVE_ALLOW_NAME_COLLISION
);
279 RefPtr
<AddrInfo
> ai(new AddrInfo(aCanonHost
, prai
, disableIPv4
,
280 filterNameCollision
, canonName
));
281 PR_FreeAddrInfo(prai
);
282 if (ai
->Addresses().IsEmpty()) {
283 LOG("PR_GetAddrInfoByName returned empty address list");
284 return NS_ERROR_UNKNOWN_HOST
;
287 ai
.forget(aAddrInfo
);
289 LOG("PR_GetAddrInfoByName resolved successfully");
293 //////////////////////////////////////
294 // COMMON/PLATFORM INDEPENDENT CODE //
295 //////////////////////////////////////
296 nsresult
GetAddrInfoInit() {
297 LOG("Initializing GetAddrInfo.\n");
299 #ifdef DNSQUERY_AVAILABLE
300 DWORD namesize
= COMPUTER_NAME_BUFFER_SIZE
;
301 if (!GetComputerNameExA(ComputerNameDnsHostname
, sDNSComputerName
,
303 sDNSComputerName
[0] = 0;
305 namesize
= MAX_COMPUTERNAME_LENGTH
+ 1;
306 if (!GetComputerNameExA(ComputerNameNetBIOS
, sNETBIOSComputerName
,
308 sNETBIOSComputerName
[0] = 0;
314 nsresult
GetAddrInfoShutdown() {
315 LOG("Shutting down GetAddrInfo.\n");
319 bool FindAddrOverride(const nsACString
& aHost
, uint16_t aAddressFamily
,
320 nsIDNSService::DNSFlags aFlags
, AddrInfo
** aAddrInfo
) {
321 RefPtr
<NativeDNSResolverOverride
> overrideService
= gOverrideService
;
322 if (!overrideService
) {
325 AutoReadLock
lock(overrideService
->mLock
);
326 auto overrides
= overrideService
->mOverrides
.Lookup(aHost
);
330 nsCString
* cname
= nullptr;
331 if (aFlags
& nsIDNSService::RESOLVE_CANONICAL_NAME
) {
332 cname
= overrideService
->mCnames
.Lookup(aHost
).DataPtrOrNull();
337 nsTArray
<NetAddr
> addresses
;
338 for (const auto& ip
: *overrides
) {
339 if (aAddressFamily
!= AF_UNSPEC
&& ip
.raw
.family
!= aAddressFamily
) {
342 addresses
.AppendElement(ip
);
346 ai
= new AddrInfo(aHost
, DNSResolverType::Native
, 0, std::move(addresses
));
348 ai
= new AddrInfo(aHost
, *cname
, DNSResolverType::Native
, 0,
349 std::move(addresses
));
352 ai
.forget(aAddrInfo
);
356 nsresult
GetAddrInfo(const nsACString
& aHost
, uint16_t aAddressFamily
,
357 nsIDNSService::DNSFlags aFlags
, AddrInfo
** aAddrInfo
,
359 if (NS_WARN_IF(aHost
.IsEmpty()) || NS_WARN_IF(!aAddrInfo
)) {
360 return NS_ERROR_NULL_POINTER
;
362 *aAddrInfo
= nullptr;
364 if (StaticPrefs::network_dns_disabled()) {
365 return NS_ERROR_UNKNOWN_HOST
;
368 #ifdef DNSQUERY_AVAILABLE
369 // The GetTTLData needs the canonical name to function properly
371 aFlags
|= nsIDNSService::RESOLVE_CANONICAL_NAME
;
375 // If there is an override for this host, then we synthetize a result.
376 if (gOverrideService
&&
377 FindAddrOverride(aHost
, aAddressFamily
, aFlags
, aAddrInfo
)) {
378 LOG("Returning IP address from NativeDNSResolverOverride");
379 return (*aAddrInfo
)->Addresses().Length() ? NS_OK
: NS_ERROR_UNKNOWN_HOST
;
383 if (StaticPrefs::network_dns_copy_string_before_call()) {
384 host
= Substring(aHost
.BeginReading(), aHost
.Length());
385 MOZ_ASSERT(aHost
.BeginReading() != host
.BeginReading());
390 if (gNativeIsLocalhost
) {
391 // pretend we use the given host but use IPv4 localhost instead!
392 host
= "localhost"_ns
;
393 aAddressFamily
= PR_AF_INET
;
396 RefPtr
<AddrInfo
> info
;
398 _GetAddrInfo_Portable(host
, aAddressFamily
, aFlags
, getter_AddRefs(info
));
400 #ifdef DNSQUERY_AVAILABLE
401 if (aGetTtl
&& NS_SUCCEEDED(rv
)) {
402 // Figure out the canonical name, or if that fails, just use the host name
405 if (info
&& !info
->CanonicalHostname().IsEmpty()) {
406 name
= info
->CanonicalHostname();
411 LOG("Getting TTL for %s (cname = %s).", host
.get(), name
.get());
413 nsresult ttlRv
= _GetTTLData_Windows(name
, &ttl
, aAddressFamily
);
414 if (NS_SUCCEEDED(ttlRv
)) {
415 auto builder
= info
->Build();
417 info
= builder
.Finish();
418 LOG("Got TTL %u for %s (name = %s).", ttl
, host
.get(), name
.get());
420 LOG_WARNING("Could not get TTL for %s (cname = %s).", host
.get(),
426 info
.forget(aAddrInfo
);
430 bool FindHTTPSRecordOverride(const nsACString
& aHost
,
431 TypeRecordResultType
& aResult
) {
432 LOG("FindHTTPSRecordOverride aHost=%s", nsCString(aHost
).get());
433 RefPtr
<NativeDNSResolverOverride
> overrideService
= gOverrideService
;
434 if (!overrideService
) {
438 AutoReadLock
lock(overrideService
->mLock
);
439 auto overrides
= overrideService
->mHTTPSRecordOverrides
.Lookup(aHost
);
445 nsAutoCString
host(aHost
);
447 LOG("resolving %s\n", host
.get());
449 nsresult rv
= packet
.FillBuffer(
450 [&](unsigned char response
[DNSPacket::MAX_SIZE
]) -> int {
451 if (overrides
->Length() > DNSPacket::MAX_SIZE
) {
454 memcpy(response
, overrides
->Elements(), overrides
->Length());
455 return overrides
->Length();
462 rv
= ParseHTTPSRecord(host
, packet
, aResult
, ttl
);
464 return NS_SUCCEEDED(rv
);
467 nsresult
ParseHTTPSRecord(nsCString
& aHost
, DNSPacket
& aDNSPacket
,
468 TypeRecordResultType
& aResult
, uint32_t& aTTL
) {
472 aDNSPacket
.SetNativePacket(true);
474 int32_t loopCount
= 64;
475 while (loopCount
> 0 && aResult
.is
<Nothing
>()) {
478 nsClassHashtable
<nsCStringHashKey
, DOHresp
> additionalRecords
;
479 rv
= aDNSPacket
.Decode(aHost
, TRRTYPE_HTTPSSVC
, cname
, true, resp
, aResult
,
480 additionalRecords
, aTTL
);
482 LOG("Decode failed %x", static_cast<uint32_t>(rv
));
485 if (!cname
.IsEmpty() && aResult
.is
<Nothing
>()) {
492 if (aResult
.is
<Nothing
>()) {
493 LOG("Result is nothing");
494 // The call succeeded, but no HTTPS records were found.
495 return NS_ERROR_UNKNOWN_HOST
;
501 nsresult
ResolveHTTPSRecord(const nsACString
& aHost
,
502 nsIDNSService::DNSFlags aFlags
,
503 TypeRecordResultType
& aResult
, uint32_t& aTTL
) {
504 if (gOverrideService
) {
505 return FindHTTPSRecordOverride(aHost
, aResult
) ? NS_OK
506 : NS_ERROR_UNKNOWN_HOST
;
509 return ResolveHTTPSRecordImpl(aHost
, aFlags
, aResult
, aTTL
);
512 nsresult
CreateAndResolveMockHTTPSRecord(const nsACString
& aHost
,
513 nsIDNSService::DNSFlags aFlags
,
514 TypeRecordResultType
& aResult
,
518 buffer
+= '\0'; // 16 bit id
520 buffer
+= '\0'; // Flags
522 buffer
+= '\0'; // Question count
524 buffer
+= 0x1; // Answer count
530 nsresult rv
= DNSPacket::EncodeHost(buffer
, aHost
);
536 buffer
+= 0x41; // TYPE 65
539 buffer
+= 0x1; // Class
544 buffer
+= 0xFF; // TTL
546 buffer
+= 0x03; // RDLENGTH
548 buffer
+= 0x01; // SvcPriority
552 nsAutoCString
host(aHost
);
554 LOG("resolving %s\n", host
.get());
556 rv
= packet
.FillBuffer(
557 [&](unsigned char response
[DNSPacket::MAX_SIZE
]) -> int {
558 if (buffer
.Length() > DNSPacket::MAX_SIZE
) {
561 memcpy(response
, buffer
.BeginReading(), buffer
.Length());
562 return buffer
.Length();
568 return ParseHTTPSRecord(host
, packet
, aResult
, aTTL
);
572 already_AddRefed
<nsINativeDNSResolverOverride
>
573 NativeDNSResolverOverride::GetSingleton() {
574 if (nsIOService::UseSocketProcess() && XRE_IsParentProcess()) {
575 return NativeDNSResolverOverrideParent::GetSingleton();
578 if (gOverrideService
) {
579 return do_AddRef(gOverrideService
);
582 gOverrideService
= new NativeDNSResolverOverride();
583 ClearOnShutdown(&gOverrideService
);
584 return do_AddRef(gOverrideService
);
587 NS_IMPL_ISUPPORTS(NativeDNSResolverOverride
, nsINativeDNSResolverOverride
)
589 NS_IMETHODIMP
NativeDNSResolverOverride::AddIPOverride(
590 const nsACString
& aHost
, const nsACString
& aIPLiteral
) {
593 if (aIPLiteral
.Equals("N/A"_ns
)) {
594 AutoWriteLock
lock(mLock
);
595 auto& overrides
= mOverrides
.LookupOrInsert(aHost
);
600 if (NS_FAILED(tempAddr
.InitFromString(aIPLiteral
))) {
601 return NS_ERROR_UNEXPECTED
;
604 AutoWriteLock
lock(mLock
);
605 auto& overrides
= mOverrides
.LookupOrInsert(aHost
);
606 overrides
.AppendElement(tempAddr
);
611 NS_IMETHODIMP
NativeDNSResolverOverride::AddHTTPSRecordOverride(
612 const nsACString
& aHost
, const uint8_t* aData
, uint32_t aLength
) {
613 AutoWriteLock
lock(mLock
);
614 nsTArray
<uint8_t> data(aData
, aLength
);
615 mHTTPSRecordOverrides
.InsertOrUpdate(aHost
, std::move(data
));
620 NS_IMETHODIMP
NativeDNSResolverOverride::SetCnameOverride(
621 const nsACString
& aHost
, const nsACString
& aCNAME
) {
622 if (aCNAME
.IsEmpty()) {
623 return NS_ERROR_UNEXPECTED
;
626 AutoWriteLock
lock(mLock
);
627 mCnames
.InsertOrUpdate(aHost
, nsCString(aCNAME
));
632 NS_IMETHODIMP
NativeDNSResolverOverride::ClearHostOverride(
633 const nsACString
& aHost
) {
634 AutoWriteLock
lock(mLock
);
635 mCnames
.Remove(aHost
);
636 auto overrides
= mOverrides
.Extract(aHost
);
645 NS_IMETHODIMP
NativeDNSResolverOverride::ClearOverrides() {
646 AutoWriteLock
lock(mLock
);
652 #ifdef MOZ_NO_HTTPS_IMPL
654 // If there is no platform specific implementation of ResolveHTTPSRecordImpl
655 // we link a dummy implementation here.
656 // Otherwise this is implemented in PlatformDNSWin/Linux/etc
657 nsresult
ResolveHTTPSRecordImpl(const nsACString
& aHost
,
658 nsIDNSService::DNSFlags aFlags
,
659 TypeRecordResultType
& aResult
, uint32_t& aTTL
) {
660 return NS_ERROR_NOT_IMPLEMENTED
;
663 void DNSThreadShutdown() {}
665 #endif // MOZ_NO_HTTPS_IMPL
667 } // namespace mozilla::net