/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=4 sw=2 sts=2 et cin: */ /* 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/glean/NetwerkMetrics.h" #include "mozilla/net/DNSPacket.h" #include "nsIDNSService.h" #include "mozilla/StaticPrefs_network.h" #include "mozilla/ThreadLocal.h" #include #include #include namespace mozilla::net { #define LOG(msg, ...) \ MOZ_LOG(gGetAddrInfoLog, LogLevel::Debug, ("[DNS]: " msg, ##__VA_ARGS__)) struct DNSContext { nsresult mRv = NS_OK; TypeRecordResultType* mResult; nsCString mHost; uint32_t* mTTL; }; // Callback for DNSServiceQueryRecord void QueryCallback(DNSServiceRef aSDRef, DNSServiceFlags aFlags, uint32_t aInterfaceIndex, DNSServiceErrorType aErrorCode, const char* aFullname, uint16_t aRRType, uint16_t aRRClass, uint16_t aRDLen, const void* aRdata, uint32_t aTtl, void* aContext) { struct DNSContext* context = (struct DNSContext*)aContext; LOG("DNS response name: %s type: %u rdlen %u class %u ttl %u", aFullname, aRRType, aRDLen, aRRClass, aTtl); if (aErrorCode != kDNSServiceErr_NoError) { LOG("Error resolving record: %d\n", aErrorCode); context->mRv = NS_ERROR_UNKNOWN_HOST; return; } if (NS_FAILED(context->mRv)) { LOG("Parsing already failed for a previous record"); return; } // Process the rdata for HTTPS records (type 65) if (aRRType != TRRTYPE_HTTPSSVC || aRDLen == 0) { context->mRv = NS_ERROR_UNKNOWN_HOST; return; } nsDependentCString fullname(aFullname); if (fullname.Length() && fullname.Last() == '.') { // The fullname argument is always FQDN fullname.Rebind(aFullname, fullname.Length() - 1); } struct SVCB parsed; nsresult rv = DNSPacket::ParseHTTPS( aRDLen, parsed, 0, (const unsigned char*)aRdata, aRDLen, fullname); if (NS_FAILED(rv)) { LOG("ParseHTTPS failed\n"); context->mRv = rv; return; } if (parsed.mSvcDomainName.IsEmpty() && parsed.mSvcFieldPriority == 0) { // For AliasMode SVCB RRs, a TargetName of "." indicates that the // service is not available or does not exist. return; } if (parsed.mSvcFieldPriority == 0) { // Alias form SvcDomainName must not have the "." value (empty) if (parsed.mSvcDomainName.IsEmpty()) { context->mRv = NS_ERROR_UNEXPECTED; return; } LOG("alias mode %s -> %s", context->mHost.get(), parsed.mSvcDomainName.get()); context->mHost = parsed.mSvcDomainName; ToLowerCase(context->mHost); return; } if (!context->mResult->is()) { *context->mResult = mozilla::AsVariant(CopyableTArray()); } auto& results = context->mResult->as(); results.AppendElement(parsed); *context->mTTL = std::min(*context->mTTL, aTtl); } nsresult ResolveHTTPSRecordImpl(const nsACString& aHost, nsIDNSService::DNSFlags aFlags, TypeRecordResultType& aResult, uint32_t& aTTL) { nsAutoCString host(aHost); nsAutoCString cname; if (xpc::IsInAutomation() && !StaticPrefs::network_dns_native_https_query_in_automation()) { return NS_ERROR_UNKNOWN_HOST; } LOG("resolving %s\n", host.get()); TimeStamp startTime = TimeStamp::Now(); struct DNSContext context{ .mResult = &aResult, .mHost = host, .mTTL = &aTTL, }; DNSServiceRef sdRef; DNSServiceErrorType err; err = DNSServiceQueryRecord(&sdRef, 0, // No flags 0, // All interfaces host.get(), TRRTYPE_HTTPSSVC, kDNSServiceClass_IN, QueryCallback, &context); if (err != kDNSServiceErr_NoError) { LOG("DNSServiceQueryRecord failed: %d\n", err); return NS_ERROR_UNKNOWN_HOST; } int fd = DNSServiceRefSockFD(sdRef); fd_set readfds; FD_ZERO(&readfds); FD_SET(fd, &readfds); // If the domain queried results in NXDOMAIN, then QueryCallback will // never get called, and select will hang forever. We need to use a // timeout so that select() eventually returns. struct timeval timeout; timeout.tv_sec = StaticPrefs::network_dns_native_https_timeout_mac(); timeout.tv_usec = 0; int result = select(fd + 1, &readfds, NULL, NULL, &timeout); if (result > 0 && FD_ISSET(fd, &readfds)) { // Process the result DNSServiceProcessResult(sdRef); } else if (result < 0) { LOG("select() failed"); } else if (result == 0) { LOG("select timed out"); } // Cleanup DNSServiceRefDeallocate(sdRef); mozilla::glean::networking::dns_native_https_call_time.AccumulateRawDuration( TimeStamp::Now() - startTime); LOG("resolving %s done %x ttl=%u", host.get(), context.mRv, aTTL); if (NS_FAILED(context.mRv)) { return context.mRv; } if (aResult.is()) { // The call succeeded, but no HTTPS records were found. return NS_ERROR_UNKNOWN_HOST; } if (aTTL == UINT32_MAX) { aTTL = 60; // Defaults to 60 seconds } return NS_OK; } void DNSThreadShutdown() {} } // namespace mozilla::net