diff options
Diffstat (limited to 'netwerk/dns/GetAddrInfo.cpp')
-rw-r--r-- | netwerk/dns/GetAddrInfo.cpp | 584 |
1 files changed, 584 insertions, 0 deletions
diff --git a/netwerk/dns/GetAddrInfo.cpp b/netwerk/dns/GetAddrInfo.cpp new file mode 100644 index 0000000000..0dd1f0099a --- /dev/null +++ b/netwerk/dns/GetAddrInfo.cpp @@ -0,0 +1,584 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GetAddrInfo.h" + +#ifdef DNSQUERY_AVAILABLE +// There is a bug in windns.h where the type of parameter ppQueryResultsSet for +// DnsQuery_A is dependent on UNICODE being set. It should *always* be +// PDNS_RECORDA, but if UNICODE is set it is PDNS_RECORDW. To get around this +// we make sure that UNICODE is unset. +# undef UNICODE +# include <ws2tcpip.h> +# undef GetAddrInfo +# include <windns.h> +#endif // DNSQUERY_AVAILABLE + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/net/DNS.h" +#include "NativeDNSResolverOverrideParent.h" +#include "prnetdb.h" +#include "nsIOService.h" +#include "nsHostResolver.h" +#include "nsError.h" +#include "mozilla/net/DNS.h" +#include <algorithm> +#include "prerror.h" + +#include "mozilla/Logging.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/net/DNSPacket.h" +#include "nsIDNSService.h" + +namespace mozilla::net { + +static StaticRefPtr<NativeDNSResolverOverride> gOverrideService; + +LazyLogModule gGetAddrInfoLog("GetAddrInfo"); +#define LOG(msg, ...) \ + MOZ_LOG(gGetAddrInfoLog, LogLevel::Debug, ("[DNS]: " msg, ##__VA_ARGS__)) +#define LOG_WARNING(msg, ...) \ + MOZ_LOG(gGetAddrInfoLog, LogLevel::Warning, ("[DNS]: " msg, ##__VA_ARGS__)) + +#ifdef DNSQUERY_AVAILABLE + +# define COMPUTER_NAME_BUFFER_SIZE 100 +static char sDNSComputerName[COMPUTER_NAME_BUFFER_SIZE]; +static char sNETBIOSComputerName[MAX_COMPUTERNAME_LENGTH + 1]; + +//////////////////////////// +// WINDOWS IMPLEMENTATION // +//////////////////////////// + +// Ensure consistency of PR_* and AF_* constants to allow for legacy usage of +// PR_* constants with this API. +static_assert(PR_AF_INET == AF_INET && PR_AF_INET6 == AF_INET6 && + PR_AF_UNSPEC == AF_UNSPEC, + "PR_AF_* must match AF_*"); + +// If successful, returns in aResult a TTL value that is smaller or +// equal with the one already there. Gets the TTL value by calling +// to DnsQuery_A and iterating through the returned +// records to find the one with the smallest TTL value. +static MOZ_ALWAYS_INLINE nsresult _CallDnsQuery_A_Windows( + const nsACString& aHost, uint16_t aAddressFamily, DWORD aFlags, + std::function<void(PDNS_RECORDA)> aCallback) { + NS_ConvertASCIItoUTF16 name(aHost); + + auto callDnsQuery_A = [&](uint16_t reqFamily) { + PDNS_RECORDA dnsData = nullptr; + DNS_STATUS status = DnsQuery_A(aHost.BeginReading(), reqFamily, aFlags, + nullptr, &dnsData, nullptr); + if (status == DNS_INFO_NO_RECORDS || status == DNS_ERROR_RCODE_NAME_ERROR || + !dnsData) { + LOG("No DNS records found for %s. status=%lX. reqFamily = %X\n", + aHost.BeginReading(), status, reqFamily); + return NS_ERROR_FAILURE; + } else if (status != NOERROR) { + LOG_WARNING("DnsQuery_A failed with status %lX.\n", status); + return NS_ERROR_UNEXPECTED; + } + + for (PDNS_RECORDA curRecord = dnsData; curRecord; + curRecord = curRecord->pNext) { + // Only records in the answer section are important + if (curRecord->Flags.S.Section != DnsSectionAnswer) { + continue; + } + if (curRecord->wType != reqFamily) { + continue; + } + + aCallback(curRecord); + } + + DnsFree(dnsData, DNS_FREE_TYPE::DnsFreeRecordList); + return NS_OK; + }; + + if (aAddressFamily == PR_AF_UNSPEC || aAddressFamily == PR_AF_INET) { + callDnsQuery_A(DNS_TYPE_A); + } + + if (aAddressFamily == PR_AF_UNSPEC || aAddressFamily == PR_AF_INET6) { + callDnsQuery_A(DNS_TYPE_AAAA); + } + return NS_OK; +} + +bool recordTypeMatchesRequest(uint16_t wType, uint16_t aAddressFamily) { + if (aAddressFamily == PR_AF_UNSPEC) { + return wType == DNS_TYPE_A || wType == DNS_TYPE_AAAA; + } + if (aAddressFamily == PR_AF_INET) { + return wType == DNS_TYPE_A; + } + if (aAddressFamily == PR_AF_INET6) { + return wType == DNS_TYPE_AAAA; + } + return false; +} + +static MOZ_ALWAYS_INLINE nsresult _GetTTLData_Windows(const nsACString& aHost, + uint32_t* aResult, + uint16_t aAddressFamily) { + MOZ_ASSERT(!aHost.IsEmpty()); + MOZ_ASSERT(aResult); + if (aAddressFamily != PR_AF_UNSPEC && aAddressFamily != PR_AF_INET && + aAddressFamily != PR_AF_INET6) { + return NS_ERROR_UNEXPECTED; + } + + // In order to avoid using ANY records which are not always implemented as a + // "Gimme what you have" request in hostname resolvers, we should send A + // and/or AAAA requests, based on the address family requested. + const DWORD ttlFlags = + (DNS_QUERY_STANDARD | DNS_QUERY_NO_NETBT | DNS_QUERY_NO_HOSTS_FILE | + DNS_QUERY_NO_MULTICAST | DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE | + DNS_QUERY_DONT_RESET_TTL_VALUES); + unsigned int ttl = (unsigned int)-1; + _CallDnsQuery_A_Windows( + aHost, aAddressFamily, ttlFlags, + [&ttl, &aHost, aAddressFamily](PDNS_RECORDA curRecord) { + if (recordTypeMatchesRequest(curRecord->wType, aAddressFamily)) { + ttl = std::min<unsigned int>(ttl, curRecord->dwTtl); + } else { + LOG("Received unexpected record type %u in response for %s.\n", + curRecord->wType, aHost.BeginReading()); + } + }); + + if (ttl == (unsigned int)-1) { + LOG("No useable TTL found."); + return NS_ERROR_FAILURE; + } + + *aResult = ttl; + return NS_OK; +} + +static MOZ_ALWAYS_INLINE nsresult +_DNSQuery_A_SingleLabel(const nsACString& aCanonHost, uint16_t aAddressFamily, + uint16_t aFlags, AddrInfo** aAddrInfo) { + bool setCanonName = aFlags & nsHostResolver::RES_CANON_NAME; + nsAutoCString canonName; + const DWORD flags = (DNS_QUERY_STANDARD | DNS_QUERY_NO_MULTICAST | + DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE); + nsTArray<NetAddr> addresses; + + _CallDnsQuery_A_Windows( + aCanonHost, aAddressFamily, flags, [&](PDNS_RECORDA curRecord) { + MOZ_DIAGNOSTIC_ASSERT(curRecord->wType == DNS_TYPE_A || + curRecord->wType == DNS_TYPE_AAAA); + if (setCanonName) { + canonName.Assign(curRecord->pName); + } + NetAddr addr{}; + addr.inet.family = AF_INET; + addr.inet.ip = curRecord->Data.A.IpAddress; + addresses.AppendElement(addr); + }); + + LOG("Query for: %s has %zu results", aCanonHost.BeginReading(), + addresses.Length()); + if (addresses.IsEmpty()) { + return NS_ERROR_UNKNOWN_HOST; + } + RefPtr<AddrInfo> ai(new AddrInfo( + aCanonHost, canonName, DNSResolverType::Native, 0, std::move(addresses))); + ai.forget(aAddrInfo); + + return NS_OK; +} + +#endif + +//////////////////////////////////// +// PORTABLE RUNTIME IMPLEMENTATION// +//////////////////////////////////// + +static MOZ_ALWAYS_INLINE nsresult +_GetAddrInfo_Portable(const nsACString& aCanonHost, uint16_t aAddressFamily, + uint16_t aFlags, AddrInfo** aAddrInfo) { + MOZ_ASSERT(!aCanonHost.IsEmpty()); + MOZ_ASSERT(aAddrInfo); + + // We accept the same aFlags that nsHostResolver::ResolveHost accepts, but we + // need to translate the aFlags into a form that PR_GetAddrInfoByName + // accepts. + int prFlags = PR_AI_ADDRCONFIG; + if (!(aFlags & nsHostResolver::RES_CANON_NAME)) { + prFlags |= PR_AI_NOCANONNAME; + } + + // We need to remove IPv4 records manually because PR_GetAddrInfoByName + // doesn't support PR_AF_INET6. + bool disableIPv4 = aAddressFamily == PR_AF_INET6; + if (disableIPv4) { + aAddressFamily = PR_AF_UNSPEC; + } + +#if defined(DNSQUERY_AVAILABLE) + if (StaticPrefs::network_dns_dns_query_single_label() && + !aCanonHost.Contains('.') && aCanonHost != "localhost"_ns) { + // For some reason we can't use DnsQuery_A to get the computer's IP. + if (!aCanonHost.Equals(nsDependentCString(sDNSComputerName), + nsCaseInsensitiveCStringComparator) && + !aCanonHost.Equals(nsDependentCString(sNETBIOSComputerName), + nsCaseInsensitiveCStringComparator)) { + // This is a single label name resolve without a dot. + // We use DNSQuery_A for these. + LOG("Resolving %s using DnsQuery_A (computername: %s)\n", + aCanonHost.BeginReading(), sDNSComputerName); + return _DNSQuery_A_SingleLabel(aCanonHost, aAddressFamily, aFlags, + aAddrInfo); + } + } +#endif + + LOG("Resolving %s using PR_GetAddrInfoByName", aCanonHost.BeginReading()); + PRAddrInfo* prai = + PR_GetAddrInfoByName(aCanonHost.BeginReading(), aAddressFamily, prFlags); + + if (!prai) { + LOG("PR_GetAddrInfoByName returned null PR_GetError:%d PR_GetOSErrpr:%d", + PR_GetError(), PR_GetOSError()); + return NS_ERROR_UNKNOWN_HOST; + } + + nsAutoCString canonName; + if (aFlags & nsHostResolver::RES_CANON_NAME) { + canonName.Assign(PR_GetCanonNameFromAddrInfo(prai)); + } + + bool filterNameCollision = + !(aFlags & nsHostResolver::RES_ALLOW_NAME_COLLISION); + RefPtr<AddrInfo> ai(new AddrInfo(aCanonHost, prai, disableIPv4, + filterNameCollision, canonName)); + PR_FreeAddrInfo(prai); + if (ai->Addresses().IsEmpty()) { + LOG("PR_GetAddrInfoByName returned empty address list"); + return NS_ERROR_UNKNOWN_HOST; + } + + ai.forget(aAddrInfo); + + LOG("PR_GetAddrInfoByName resolved successfully"); + return NS_OK; +} + +////////////////////////////////////// +// COMMON/PLATFORM INDEPENDENT CODE // +////////////////////////////////////// +nsresult GetAddrInfoInit() { + LOG("Initializing GetAddrInfo.\n"); + +#ifdef DNSQUERY_AVAILABLE + DWORD namesize = COMPUTER_NAME_BUFFER_SIZE; + if (!GetComputerNameExA(ComputerNameDnsHostname, sDNSComputerName, + &namesize)) { + sDNSComputerName[0] = 0; + } + namesize = MAX_COMPUTERNAME_LENGTH + 1; + if (!GetComputerNameExA(ComputerNameNetBIOS, sNETBIOSComputerName, + &namesize)) { + sNETBIOSComputerName[0] = 0; + } +#endif + return NS_OK; +} + +nsresult GetAddrInfoShutdown() { + LOG("Shutting down GetAddrInfo.\n"); + return NS_OK; +} + +bool FindAddrOverride(const nsACString& aHost, uint16_t aAddressFamily, + uint16_t aFlags, AddrInfo** aAddrInfo) { + RefPtr<NativeDNSResolverOverride> overrideService = gOverrideService; + if (!overrideService) { + return false; + } + AutoReadLock lock(overrideService->mLock); + auto overrides = overrideService->mOverrides.Lookup(aHost); + if (!overrides) { + return false; + } + nsCString* cname = nullptr; + if (aFlags & nsHostResolver::RES_CANON_NAME) { + cname = overrideService->mCnames.Lookup(aHost).DataPtrOrNull(); + } + + RefPtr<AddrInfo> ai; + + nsTArray<NetAddr> addresses; + for (const auto& ip : *overrides) { + if (aAddressFamily != AF_UNSPEC && ip.raw.family != aAddressFamily) { + continue; + } + addresses.AppendElement(ip); + } + + if (!cname) { + ai = new AddrInfo(aHost, DNSResolverType::Native, 0, std::move(addresses)); + } else { + ai = new AddrInfo(aHost, *cname, DNSResolverType::Native, 0, + std::move(addresses)); + } + + ai.forget(aAddrInfo); + return true; +} + +nsresult GetAddrInfo(const nsACString& aHost, uint16_t aAddressFamily, + uint16_t aFlags, AddrInfo** aAddrInfo, bool aGetTtl) { + if (NS_WARN_IF(aHost.IsEmpty()) || NS_WARN_IF(!aAddrInfo)) { + return NS_ERROR_NULL_POINTER; + } + *aAddrInfo = nullptr; + + if (StaticPrefs::network_dns_disabled()) { + return NS_ERROR_UNKNOWN_HOST; + } + +#ifdef DNSQUERY_AVAILABLE + // The GetTTLData needs the canonical name to function properly + if (aGetTtl) { + aFlags |= nsHostResolver::RES_CANON_NAME; + } +#endif + + // If there is an override for this host, then we synthetize a result. + if (gOverrideService && + FindAddrOverride(aHost, aAddressFamily, aFlags, aAddrInfo)) { + LOG("Returning IP address from NativeDNSResolverOverride"); + return (*aAddrInfo)->Addresses().Length() ? NS_OK : NS_ERROR_UNKNOWN_HOST; + } + + nsAutoCString host; + if (StaticPrefs::network_dns_copy_string_before_call()) { + host = Substring(aHost.BeginReading(), aHost.Length()); + MOZ_ASSERT(aHost.BeginReading() != host.BeginReading()); + } else { + host = aHost; + } + + if (gNativeIsLocalhost) { + // pretend we use the given host but use IPv4 localhost instead! + host = "localhost"_ns; + aAddressFamily = PR_AF_INET; + } + + RefPtr<AddrInfo> info; + nsresult rv = + _GetAddrInfo_Portable(host, aAddressFamily, aFlags, getter_AddRefs(info)); + +#ifdef DNSQUERY_AVAILABLE + if (aGetTtl && NS_SUCCEEDED(rv)) { + // Figure out the canonical name, or if that fails, just use the host name + // we have. + nsAutoCString name; + if (info && !info->CanonicalHostname().IsEmpty()) { + name = info->CanonicalHostname(); + } else { + name = host; + } + + LOG("Getting TTL for %s (cname = %s).", host.get(), name.get()); + uint32_t ttl = 0; + nsresult ttlRv = _GetTTLData_Windows(name, &ttl, aAddressFamily); + if (NS_SUCCEEDED(ttlRv)) { + auto builder = info->Build(); + builder.SetTTL(ttl); + info = builder.Finish(); + LOG("Got TTL %u for %s (name = %s).", ttl, host.get(), name.get()); + } else { + LOG_WARNING("Could not get TTL for %s (cname = %s).", host.get(), + name.get()); + } + } +#endif + + info.forget(aAddrInfo); + return rv; +} + +bool FindHTTPSRecordOverride(const nsACString& aHost, + TypeRecordResultType& aResult) { + LOG("FindHTTPSRecordOverride aHost=%s", nsCString(aHost).get()); + RefPtr<NativeDNSResolverOverride> overrideService = gOverrideService; + if (!overrideService) { + return false; + } + + AutoReadLock lock(overrideService->mLock); + auto overrides = overrideService->mHTTPSRecordOverrides.Lookup(aHost); + if (!overrides) { + return false; + } + + DNSPacket packet; + nsAutoCString host(aHost); + nsAutoCString cname; + + LOG("resolving %s\n", host.get()); + // Perform the query + nsresult rv = packet.FillBuffer( + [&](unsigned char response[DNSPacket::MAX_SIZE]) -> int { + if (overrides->Length() > DNSPacket::MAX_SIZE) { + return -1; + } + memcpy(response, overrides->Elements(), overrides->Length()); + return overrides->Length(); + }); + if (NS_FAILED(rv)) { + return false; + } + + uint32_t ttl = 0; + rv = ParseHTTPSRecord(host, packet, aResult, ttl); + + return NS_SUCCEEDED(rv); +} + +nsresult ParseHTTPSRecord(nsCString& aHost, DNSPacket& aDNSPacket, + TypeRecordResultType& aResult, uint32_t& aTTL) { + nsAutoCString cname; + nsresult rv; + + aDNSPacket.SetNativePacket(true); + + int32_t loopCount = 64; + while (loopCount > 0 && aResult.is<Nothing>()) { + loopCount--; + DOHresp resp; + nsClassHashtable<nsCStringHashKey, DOHresp> additionalRecords; + rv = aDNSPacket.Decode(aHost, TRRTYPE_HTTPSSVC, cname, true, resp, aResult, + additionalRecords, aTTL); + if (NS_FAILED(rv)) { + LOG("Decode failed %x", static_cast<uint32_t>(rv)); + return rv; + } + if (!cname.IsEmpty() && aResult.is<Nothing>()) { + aHost = cname; + cname.Truncate(); + continue; + } + } + + if (aResult.is<Nothing>()) { + LOG("Result is nothing"); + // The call succeeded, but no HTTPS records were found. + return NS_ERROR_UNKNOWN_HOST; + } + + return NS_OK; +} + +nsresult ResolveHTTPSRecord(const nsACString& aHost, uint16_t aFlags, + TypeRecordResultType& aResult, uint32_t& aTTL) { + if (gOverrideService) { + return FindHTTPSRecordOverride(aHost, aResult) ? NS_OK + : NS_ERROR_UNKNOWN_HOST; + } + + return ResolveHTTPSRecordImpl(aHost, aFlags, aResult, aTTL); +} + +// static +already_AddRefed<nsINativeDNSResolverOverride> +NativeDNSResolverOverride::GetSingleton() { + if (nsIOService::UseSocketProcess() && XRE_IsParentProcess()) { + return NativeDNSResolverOverrideParent::GetSingleton(); + } + + if (gOverrideService) { + return do_AddRef(gOverrideService); + } + + gOverrideService = new NativeDNSResolverOverride(); + ClearOnShutdown(&gOverrideService); + return do_AddRef(gOverrideService); +} + +NS_IMPL_ISUPPORTS(NativeDNSResolverOverride, nsINativeDNSResolverOverride) + +NS_IMETHODIMP NativeDNSResolverOverride::AddIPOverride( + const nsACString& aHost, const nsACString& aIPLiteral) { + NetAddr tempAddr; + + if (aIPLiteral.Equals("N/A"_ns)) { + AutoWriteLock lock(mLock); + auto& overrides = mOverrides.LookupOrInsert(aHost); + overrides.Clear(); + return NS_OK; + } + + if (NS_FAILED(tempAddr.InitFromString(aIPLiteral))) { + return NS_ERROR_UNEXPECTED; + } + + AutoWriteLock lock(mLock); + auto& overrides = mOverrides.LookupOrInsert(aHost); + overrides.AppendElement(tempAddr); + + return NS_OK; +} + +NS_IMETHODIMP NativeDNSResolverOverride::AddHTTPSRecordOverride( + const nsACString& aHost, const uint8_t* aData, uint32_t aLength) { + AutoWriteLock lock(mLock); + nsTArray<uint8_t> data(aData, aLength); + mHTTPSRecordOverrides.InsertOrUpdate(aHost, std::move(data)); + + return NS_OK; +} + +NS_IMETHODIMP NativeDNSResolverOverride::SetCnameOverride( + const nsACString& aHost, const nsACString& aCNAME) { + if (aCNAME.IsEmpty()) { + return NS_ERROR_UNEXPECTED; + } + + AutoWriteLock lock(mLock); + mCnames.InsertOrUpdate(aHost, nsCString(aCNAME)); + + return NS_OK; +} + +NS_IMETHODIMP NativeDNSResolverOverride::ClearHostOverride( + const nsACString& aHost) { + AutoWriteLock lock(mLock); + mCnames.Remove(aHost); + auto overrides = mOverrides.Extract(aHost); + if (!overrides) { + return NS_OK; + } + + overrides->Clear(); + return NS_OK; +} + +NS_IMETHODIMP NativeDNSResolverOverride::ClearOverrides() { + AutoWriteLock lock(mLock); + mOverrides.Clear(); + mCnames.Clear(); + return NS_OK; +} + +#ifdef MOZ_NO_HTTPS_IMPL + +// If there is no platform specific implementation of ResolveHTTPSRecordImpl +// we link a dummy implementation here. +// Otherwise this is implemented in GetAddrInfoWin/Linux/etc +nsresult ResolveHTTPSRecordImpl(const nsACString& aHost, uint16_t aFlags, + TypeRecordResultType& aResult, uint32_t& aTTL) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +#endif // MOZ_NO_HTTPS_IMPL + +} // namespace mozilla::net |