gecko-dev/netwerk/dns/GetAddrInfo.cpp
Wes Kocher 4e9f80ed2e Backed out 14 changesets (bug 1165515) for b2g mochitest-6 permafail CLOSED TREE
Backed out changeset 9b97e2aa2ed9 (bug 1165515)
Backed out changeset 150606c022a2 (bug 1165515)
Backed out changeset 4e875a488349 (bug 1165515)
Backed out changeset 467e7feeb546 (bug 1165515)
Backed out changeset d6b6cc373197 (bug 1165515)
Backed out changeset 0615265b593c (bug 1165515)
Backed out changeset fafd1dce9f08 (bug 1165515)
Backed out changeset d1df869245f9 (bug 1165515)
Backed out changeset 6876a7c63611 (bug 1165515)
Backed out changeset b7841c94a9a3 (bug 1165515)
Backed out changeset e5e3617f7c73 (bug 1165515)
Backed out changeset 39be3db95978 (bug 1165515)
Backed out changeset 0ec74176f8de (bug 1165515)
Backed out changeset 5b928dd10d71 (bug 1165515)
2015-06-01 17:57:58 -07:00

448 lines
13 KiB
C++

/* -*- 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"
#include "mozilla/net/DNS.h"
#include "prnetdb.h"
#include "nsHostResolver.h"
#include "nsError.h"
#include "mozilla/Mutex.h"
#include "nsAutoPtr.h"
#include "mozilla/StaticPtr.h"
#include "MainThreadUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/net/DNS.h"
#include <algorithm>
#include "prerror.h"
#if defined(ANDROID) && ANDROID_VERSION > 19
#include <resolv_netid.h>
#endif
#include "mozilla/Logging.h"
static PRLogModuleInfo *gGetAddrInfoLog = PR_NewLogModule("GetAddrInfo");
#define LOG(msg, ...) \
MOZ_LOG(gGetAddrInfoLog, PR_LOG_DEBUG, ("[DNS]: " msg, ##__VA_ARGS__))
#define LOG_WARNING(msg, ...) \
MOZ_LOG(gGetAddrInfoLog, PR_LOG_WARNING, ("[DNS]: " msg, ##__VA_ARGS__))
#if 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
namespace mozilla {
namespace net {
#if DNSQUERY_AVAILABLE
////////////////////////////
// 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_*");
// We intentionally leak this mutex. This is because we can run into a
// situation where the worker threads are still running until the process
// is actually fully shut down, and at any time one of those worker
// threads can access gDnsapiInfoLock.
static OffTheBooksMutex* gDnsapiInfoLock = nullptr;
struct DnsapiInfo
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DnsapiInfo);
HMODULE mLibrary;
decltype(&DnsQuery_A) mDnsQueryFunc;
decltype(&DnsFree) mDnsFreeFunc;
private:
// This will either be called during shutdown of the GetAddrInfo module, or
// when a worker thread is done doing a lookup (ie: within
// _GetAddrInfo_Windows). Note that the lock must be held when this is
// called.
~DnsapiInfo()
{
if (gDnsapiInfoLock) {
gDnsapiInfoLock->AssertCurrentThreadOwns();
} else {
MOZ_ASSERT_UNREACHABLE("No mutex available during GetAddrInfo "
"shutdown.");
return;
}
LOG("Freeing Dnsapi.dll");
MOZ_ASSERT(mLibrary);
DebugOnly<BOOL> rv = FreeLibrary(mLibrary);
NS_WARN_IF_FALSE(rv, "Failed to free Dnsapi.dll.");
}
};
static StaticRefPtr<DnsapiInfo> gDnsapiInfo;
static MOZ_ALWAYS_INLINE nsresult
_GetAddrInfoInit_Windows()
{
// This is necessary to ensure strict thread safety because if two threads
// run this function at the same time they can potentially create two
// mutexes.
MOZ_ASSERT(NS_IsMainThread(),
"Do not initialize GetAddrInfo off main thread!");
if (!gDnsapiInfoLock) {
gDnsapiInfoLock = new OffTheBooksMutex("GetAddrInfo.cpp::gDnsapiInfoLock");
}
OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock);
if (gDnsapiInfo) {
MOZ_ASSERT_UNREACHABLE("GetAddrInfo is being initialized multiple times!");
return NS_ERROR_ALREADY_INITIALIZED;
}
HMODULE library = LoadLibraryA("Dnsapi.dll");
if (NS_WARN_IF(!library)) {
return NS_ERROR_FAILURE;
}
FARPROC dnsQueryFunc = GetProcAddress(library, "DnsQuery_A");
FARPROC dnsFreeFunc = GetProcAddress(library, "DnsFree");
if (NS_WARN_IF(!dnsQueryFunc) || NS_WARN_IF(!dnsFreeFunc)) {
DebugOnly<BOOL> rv = FreeLibrary(library);
NS_WARN_IF_FALSE(rv, "Failed to free Dnsapi.dll.");
return NS_ERROR_FAILURE;
}
DnsapiInfo* info = new DnsapiInfo;
info->mLibrary = library;
info->mDnsQueryFunc = (decltype(info->mDnsQueryFunc)) dnsQueryFunc;
info->mDnsFreeFunc = (decltype(info->mDnsFreeFunc)) dnsFreeFunc;
gDnsapiInfo = info;
return NS_OK;
}
static MOZ_ALWAYS_INLINE nsresult
_GetAddrInfoShutdown_Windows()
{
OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock);
if (NS_WARN_IF(!gDnsapiInfo) || NS_WARN_IF(!gDnsapiInfoLock)) {
MOZ_ASSERT_UNREACHABLE("GetAddrInfo not initialized!");
return NS_ERROR_NOT_INITIALIZED;
}
gDnsapiInfo = nullptr;
return NS_OK;
}
// 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 dnsapi->mDnsQueryFunc and iterating through the returned
// records to find the one with the smallest TTL value.
static MOZ_ALWAYS_INLINE nsresult
_GetMinTTLForRequestType_Windows(DnsapiInfo * dnsapi, const char* aHost,
uint16_t aRequestType, unsigned int* aResult)
{
MOZ_ASSERT(dnsapi);
MOZ_ASSERT(aHost);
MOZ_ASSERT(aResult);
PDNS_RECORDA dnsData = nullptr;
DNS_STATUS status = dnsapi->mDnsQueryFunc(
aHost,
aRequestType,
(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),
nullptr,
&dnsData,
nullptr);
if (status == DNS_INFO_NO_RECORDS || status == DNS_ERROR_RCODE_NAME_ERROR
|| !dnsData) {
LOG("No DNS records found for %s. status=%X. aRequestType = %X\n",
aHost, status, aRequestType);
return NS_ERROR_FAILURE;
} else if (status != NOERROR) {
LOG_WARNING("DnsQuery_A failed with status %X.\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 == aRequestType) {
*aResult = std::min<unsigned int>(*aResult, curRecord->dwTtl);
} else {
LOG("Received unexpected record type %u in response for %s.\n",
curRecord->wType, aHost);
}
}
dnsapi->mDnsFreeFunc(dnsData, DNS_FREE_TYPE::DnsFreeRecordList);
return NS_OK;
}
static MOZ_ALWAYS_INLINE nsresult
_GetTTLData_Windows(const char* aHost, uint16_t* aResult, uint16_t aAddressFamily)
{
MOZ_ASSERT(aHost);
MOZ_ASSERT(aResult);
if (aAddressFamily != PR_AF_UNSPEC &&
aAddressFamily != PR_AF_INET &&
aAddressFamily != PR_AF_INET6) {
return NS_ERROR_UNEXPECTED;
}
nsRefPtr<DnsapiInfo> dnsapi = nullptr;
{
OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock);
dnsapi = gDnsapiInfo;
}
if (!dnsapi) {
LOG_WARNING("GetAddrInfo has been shutdown or has not been initialized.");
return NS_ERROR_NOT_INITIALIZED;
}
// 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.
unsigned int ttl = -1;
if (aAddressFamily == PR_AF_UNSPEC || aAddressFamily == PR_AF_INET) {
_GetMinTTLForRequestType_Windows(dnsapi, aHost, DNS_TYPE_A, &ttl);
}
if (aAddressFamily == PR_AF_UNSPEC || aAddressFamily == PR_AF_INET6) {
_GetMinTTLForRequestType_Windows(dnsapi, aHost, DNS_TYPE_AAAA, &ttl);
}
{
// dnsapi's destructor is not thread-safe, so we release explicitly here
OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock);
dnsapi = nullptr;
}
if (ttl == -1) {
LOG("No useable TTL found.");
return NS_ERROR_FAILURE;
}
*aResult = ttl;
return NS_OK;
}
#endif
#if defined(ANDROID) && ANDROID_VERSION >= 19
// Make the same as nspr functions.
static MOZ_ALWAYS_INLINE PRAddrInfo*
_Android_GetAddrInfoForNetInterface(const char* hostname,
uint16_t af,
uint16_t flags,
const char* aNetworkInterface)
{
if ((af != PR_AF_INET && af != PR_AF_UNSPEC) ||
(flags & ~ PR_AI_NOCANONNAME) != PR_AI_ADDRCONFIG) {
PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
return nullptr;
}
struct addrinfo *res, hints;
int rv;
memset(&hints, 0, sizeof(hints));
if (!(flags & PR_AI_NOCANONNAME)) {
hints.ai_flags |= AI_CANONNAME;
}
#ifdef AI_ADDRCONFIG
if ((flags & PR_AI_ADDRCONFIG) &&
strcmp(hostname, "localhost") != 0 &&
strcmp(hostname, "localhost.localdomain") != 0 &&
strcmp(hostname, "localhost6") != 0 &&
strcmp(hostname, "localhost6.localdomain6") != 0) {
hints.ai_flags |= AI_ADDRCONFIG;
}
#endif
hints.ai_family = (af == PR_AF_INET) ? AF_INET : AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
#if ANDROID_VERSION == 19
rv = android_getaddrinfoforiface(hostname, NULL, &hints, aNetworkInterface,
0, &res);
#else
uint32_t netId = atoi(aNetworkInterface);
rv = android_getaddrinfofornet(hostname, NULL, &hints, netId, 0, &res);
#endif
#ifdef AI_ADDRCONFIG
if (rv == EAI_BADFLAGS && (hints.ai_flags & AI_ADDRCONFIG)) {
hints.ai_flags &= ~AI_ADDRCONFIG;
#if ANDROID_VERSION == 19
rv = android_getaddrinfoforiface(hostname, NULL, &hints, aNetworkInterface,
0, &res);
#else
uint32_t netId = atoi(aNetworkInterface);
rv = android_getaddrinfofornet(hostname, NULL, &hints, netId, 0, &res);
#endif
}
#endif
if (rv == 0) {
return (PRAddrInfo *) res;
}
PR_SetError(PR_DIRECTORY_LOOKUP_ERROR, rv);
return nullptr;
}
#endif
////////////////////////////////////
// PORTABLE RUNTIME IMPLEMENTATION//
////////////////////////////////////
static MOZ_ALWAYS_INLINE nsresult
_GetAddrInfo_Portable(const char* aCanonHost, uint16_t aAddressFamily,
uint16_t aFlags, const char* aNetworkInterface,
AddrInfo** aAddrInfo)
{
MOZ_ASSERT(aCanonHost);
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;
}
PRAddrInfo* prai;
#if defined(ANDROID) && ANDROID_VERSION >= 19
if (aNetworkInterface && aNetworkInterface[0] != '\0') {
prai = _Android_GetAddrInfoForNetInterface(aCanonHost,
aAddressFamily,
prFlags,
aNetworkInterface);
} else
#endif
{
prai = PR_GetAddrInfoByName(aCanonHost, aAddressFamily, prFlags);
}
if (!prai) {
return NS_ERROR_UNKNOWN_HOST;
}
const char* canonName = nullptr;
if (aFlags & nsHostResolver::RES_CANON_NAME) {
canonName = PR_GetCanonNameFromAddrInfo(prai);
}
bool filterNameCollision = !(aFlags & nsHostResolver::RES_ALLOW_NAME_COLLISION);
nsAutoPtr<AddrInfo> ai(new AddrInfo(aCanonHost, prai, disableIPv4,
filterNameCollision, canonName));
PR_FreeAddrInfo(prai);
if (ai->mAddresses.isEmpty()) {
return NS_ERROR_UNKNOWN_HOST;
}
*aAddrInfo = ai.forget();
return NS_OK;
}
//////////////////////////////////////
// COMMON/PLATFORM INDEPENDENT CODE //
//////////////////////////////////////
nsresult
GetAddrInfoInit() {
LOG("Initializing GetAddrInfo.\n");
#if DNSQUERY_AVAILABLE
return _GetAddrInfoInit_Windows();
#else
return NS_OK;
#endif
}
nsresult
GetAddrInfoShutdown() {
LOG("Shutting down GetAddrInfo.\n");
#if DNSQUERY_AVAILABLE
return _GetAddrInfoShutdown_Windows();
#else
return NS_OK;
#endif
}
nsresult
GetAddrInfo(const char* aHost, uint16_t aAddressFamily, uint16_t aFlags,
const char* aNetworkInterface, AddrInfo** aAddrInfo, bool aGetTtl)
{
if (NS_WARN_IF(!aHost) || NS_WARN_IF(!aAddrInfo)) {
return NS_ERROR_NULL_POINTER;
}
#if DNSQUERY_AVAILABLE
// The GetTTLData needs the canonical name to function properly
if (aGetTtl) {
aFlags |= nsHostResolver::RES_CANON_NAME;
}
#endif
*aAddrInfo = nullptr;
nsresult rv = _GetAddrInfo_Portable(aHost, aAddressFamily, aFlags,
aNetworkInterface, aAddrInfo);
#if DNSQUERY_AVAILABLE
if (aGetTtl && NS_SUCCEEDED(rv)) {
// Figure out the canonical name, or if that fails, just use the host name
// we have.
const char *name = nullptr;
if (*aAddrInfo != nullptr && (*aAddrInfo)->mCanonicalName) {
name = (*aAddrInfo)->mCanonicalName;
} else {
name = aHost;
}
LOG("Getting TTL for %s (cname = %s).", aHost, name);
uint16_t ttl = 0;
nsresult ttlRv = _GetTTLData_Windows(name, &ttl, aAddressFamily);
if (NS_SUCCEEDED(ttlRv)) {
(*aAddrInfo)->ttl = ttl;
LOG("Got TTL %u for %s (name = %s).", ttl, aHost, name);
} else {
LOG_WARNING("Could not get TTL for %s (cname = %s).", aHost, name);
}
}
#endif
return rv;
}
} // namespace net
} // namespace mozilla