/* -*- 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/. */ #if defined(MOZ_LOGGING) #define FORCE_PR_LOG #endif #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 #include "prlog.h" #if defined(PR_LOGGING) static PRLogModuleInfo *gGetAddrInfoLog = PR_NewLogModule("GetAddrInfo"); #define LOG(msg, ...) \ PR_LOG(gGetAddrInfoLog, PR_LOG_DEBUG, ("[DNS]: " msg, ##__VA_ARGS__)) #define LOG_WARNING(msg, ...) \ PR_LOG(gGetAddrInfoLog, PR_LOG_WARNING, ("[DNS]: " msg, ##__VA_ARGS__)) #else #define LOG(args) #define LOG_WARNING(args) #endif #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 #undef GetAddrInfo #include #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 rv = FreeLibrary(mLibrary); NS_WARN_IF_FALSE(rv, "Failed to free Dnsapi.dll."); } }; static StaticRefPtr 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 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; } static MOZ_ALWAYS_INLINE nsresult _GetTTLData_Windows(const char* aHost, uint16_t* aResult) { MOZ_ASSERT(aHost); MOZ_ASSERT(aResult); nsRefPtr 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; } PDNS_RECORDA dnsData = nullptr; DNS_STATUS status = dnsapi->mDnsQueryFunc( aHost, DNS_TYPE_ANY, (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.\n", aHost); return NS_ERROR_UNKNOWN_HOST; } else if (status != NOERROR) { LOG_WARNING("DnsQuery_A failed with status %X.\n", status); return NS_ERROR_FAILURE; } unsigned int ttl = -1; PDNS_RECORDA curRecord = dnsData; for (; curRecord; curRecord = curRecord->pNext) { // Only records in the answer section are important if (curRecord->Flags.S.Section != DnsSectionAnswer) { continue; } if (curRecord->wType == DNS_TYPE_A || curRecord->wType == DNS_TYPE_AAAA) { ttl = std::min(ttl, curRecord->dwTtl); } } dnsapi->mDnsFreeFunc(dnsData, DNS_FREE_TYPE::DnsFreeRecordList); { // 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 //////////////////////////////////// // PORTABLE RUNTIME IMPLEMENTATION// //////////////////////////////////// static MOZ_ALWAYS_INLINE nsresult _GetAddrInfo_Portable(const char* aCanonHost, uint16_t aAddressFamily, uint16_t aFlags, 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 = 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); } nsAutoPtr ai(new AddrInfo(aCanonHost, prai, disableIPv4, 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, 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, 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); 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