blob: 20b17b290172342b5fc369eeb8b92d4e9abbdc57 [file] [log] [blame]
/*
* Copyright (c) 2019-2022 Apple Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "QuerierSupport.h"
#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
#include "dns_sd_internal.h"
#include "mDNSMacOSX.h"
#include "uDNS.h"
#include <CoreUtils/CommonServices.h>
#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_ANALYTICS)
#include "dnssd_analytics.h"
#endif
#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
#include "dnssec.h"
#include "dnssec_mdns_core.h"
#endif
#if MDNSRESPONDER_SUPPORTS(COMMON, LOCAL_DNS_RESOLVER_DISCOVERY)
#include "discover_resolver.h"
#endif
#include "cf_support.h"
#include <AssertMacros.h>
#include <mach/mach_time.h>
#include <mdns/preferences.h>
#include <mdns/system.h>
#include <mdns/ticks.h>
#include <mdns/xpc.h>
#include <os/variant_private.h>
#include "mdns_strict.h"
int PQWorkaroundThreshold = 0;
extern mDNS mDNSStorage;
mDNSlocal mDNSBool _Querier_QuestionBelongsToSelf(const DNSQuestion *q);
mDNSlocal void _Querier_LogDNSServices(const mdns_dns_service_manager_t manager)
{
__block mDNSu32 count = 0;
const mDNSu32 total = (mDNSu32)mdns_dns_service_manager_get_count(manager);
LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "Updated DNS services (%u)", total);
mdns_dns_service_manager_enumerate(manager,
^ bool (const mdns_dns_service_t service)
{
count++;
LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "DNS service (%u/%u) -- %@", count, total, service);
return true;
});
}
mDNSlocal dispatch_queue_t _Querier_InternalQueue(void)
{
static dispatch_once_t sOnce = 0;
static dispatch_queue_t sQueue = NULL;
dispatch_once(&sOnce,
^{
sQueue = dispatch_queue_create("com.apple.mDNSResponder.querier-support-queue", DISPATCH_QUEUE_SERIAL);
});
return sQueue;
}
mDNSexport mdns_dns_service_manager_t Querier_GetDNSServiceManager(void)
{
mDNS *const m = &mDNSStorage;
static mdns_dns_service_manager_t sDNSServiceManager = NULL;
if (sDNSServiceManager)
{
return sDNSServiceManager;
}
const mdns_dns_service_manager_t manager = mdns_dns_service_manager_create(_Querier_InternalQueue(), NULL);
if (!manager)
{
return NULL;
}
mdns_dns_service_manager_set_report_symptoms(manager, true);
mdns_dns_service_manager_enable_fail_fast_mode_for_odoh(manager, true);
mdns_dns_service_manager_enable_problematic_qtype_workaround(manager, PQWorkaroundThreshold);
if (os_variant_has_internal_diagnostics(kMDNSResponderIDStr))
{
const CFStringRef key = CFSTR("DDRRetryIntervalSecs");
const uint32_t intervalSecs = mdns_preferences_get_uint32_clamped(kMDNSResponderID, key, 0, NULL);
if (intervalSecs != 0)
{
mdns_dns_service_manager_set_ddr_retry_interval(manager, intervalSecs);
}
}
mdns_dns_service_manager_set_event_handler(manager,
^(mdns_event_t event, __unused OSStatus error)
{
KQueueLock();
switch (event)
{
case mdns_event_error:
mdns_dns_service_manager_invalidate(manager);
if (sDNSServiceManager == manager)
{
mdns_forget(&sDNSServiceManager);
}
break;
case mdns_event_update:
mdns_dns_service_manager_apply_pending_updates(manager);
mDNS_Lock(m);
Querier_ProcessDNSServiceChanges();
_Querier_LogDNSServices(manager);
mDNS_Unlock(m);
break;
case mdns_event_invalidated:
mdns_release(manager);
break;
}
KQueueUnlock("DNS Service Manager event handler");
});
sDNSServiceManager = manager;
mdns_retain(sDNSServiceManager);
mdns_dns_service_manager_activate(sDNSServiceManager);
return sDNSServiceManager;
}
mDNSlocal mdns_dns_service_t _Querier_GetNativeDNSService(const mdns_dns_service_manager_t manager,
const DNSQuestion * const q)
{
mdns_dns_service_t service;
if (q->InterfaceID)
{
const uint32_t ifIndex = (uint32_t)((uintptr_t)q->InterfaceID);
service = mdns_dns_service_manager_get_interface_scoped_native_service(manager, q->qname.c, ifIndex);
}
else
{
service = mdns_dns_service_manager_get_unscoped_native_service(manager, q->qname.c);
}
return service;
}
mDNSlocal mdns_dns_service_t _Querier_GetNonNativeDNSService(const mdns_dns_service_manager_t manager,
const DNSQuestion * const q, const mDNSBool excludeEncryptedDNS)
{
mdns_dns_service_t service;
const uint32_t ifIndex = (uint32_t)((uintptr_t)q->InterfaceID);
const mdns_dns_service_opts_t options = excludeEncryptedDNS ? mdns_dns_service_opt_none :
mdns_dns_service_opt_prefer_discovered;
if (!excludeEncryptedDNS && !uuid_is_null(q->ResolverUUID))
{
service = mdns_dns_service_manager_get_uuid_scoped_service(manager, q->ResolverUUID, ifIndex);
if (service && (mdns_dns_service_get_class(service) == nw_resolver_class_oblivious) && !q->InterfaceID)
{
mdns_dns_service_t discovered_service = mdns_dns_service_manager_get_discovered_service(manager, q->qname.c);
if (discovered_service && (mdns_dns_service_get_class(discovered_service) == nw_resolver_class_designated))
{
// Prefer discovered resolver for unscoped queries that would use oblivious resolvers,
// even if they have a resolver UUID.
service = discovered_service;
}
else
{
mdns_dns_service_t oblivious_service = mdns_dns_service_manager_get_discovered_oblivious_service(manager, service, q->qname.c);
if (oblivious_service && (mdns_dns_service_get_class(oblivious_service) == nw_resolver_class_oblivious))
{
// Prefer discovered oblivious resolver
service = oblivious_service;
}
}
}
}
else if (q->InterfaceID)
{
service = mdns_dns_service_manager_get_interface_scoped_system_service_with_options(manager, q->qname.c, ifIndex,
options);
}
else if (q->ServiceID >= 0)
{
service = mdns_dns_service_manager_get_service_scoped_system_service(manager, q->qname.c, (uint32_t)q->ServiceID);
}
else
{
service = mDNSNULL;
if (!excludeEncryptedDNS)
{
// Check for a matching discovered resolver for unscoped queries
service = mdns_dns_service_manager_get_discovered_service(manager, q->qname.c);
}
if (!service)
{
service = mdns_dns_service_manager_get_unscoped_system_service_with_options(manager, q->qname.c, options);
}
}
if (!excludeEncryptedDNS && service && !mdns_dns_service_interface_is_vpn(service))
{
// Check for encryption, and if the service isn't encrypted, fallback or fail
const mDNSBool lacksRequiredEncryption = q->RequireEncryption && !mdns_dns_service_is_encrypted(service);
if (lacksRequiredEncryption || mdns_dns_service_has_connection_problems(service))
{
if (lacksRequiredEncryption)
{
LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
"[R%u->Q%u] DNS service %llu lacks required encryption",
q->request_id, mDNSVal16(q->TargetQID), mdns_dns_service_get_id(service));
service = NULL;
}
// Check for a fallback service
if (q->CustomID != 0)
{
service = mdns_dns_service_manager_get_custom_service(manager, q->CustomID);
}
}
}
// Check if the final service is a fail-fast service.
if (service && mdns_dns_service_fail_fast_mode_enabled(service))
{
// If it's having connection problems and the DNSQuestion has already been used to probe if the service is back
// up and running. This way the client requests can fail quicker.
if (mdns_dns_service_has_connection_problems(service) && q->UsedAsFailFastProbe)
{
service = NULL;
}
}
return service;
}
mDNSlocal mDNSBool _Querier_QuestionIsEligibleForNonNativeDNSService(const DNSQuestion *const q)
{
mDNSBool eligible = mDNStrue;
#if MDNSRESPONDER_SUPPORTS(COMMON, LOCAL_DNS_RESOLVER_DISCOVERY)
if (IsSubdomain(&q->qname, THREAD_DOMAIN_NAME))
{
// We do not want the query ends with "openthread.thread.home.arpa." to choose a non-native DNS service to go
// outside of the home network.
eligible = mDNSfalse;
}
#endif
return eligible;
}
mDNSlocal mdns_dns_service_t _Querier_GetDNSService(const DNSQuestion *q, const mDNSBool excludeEncryptedDNS)
{
mdns_dns_service_t service = mDNSNULL;
const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
if (!manager)
{
return NULL;
}
service = _Querier_GetNativeDNSService(manager, q);
if (!service && _Querier_QuestionIsEligibleForNonNativeDNSService(q))
{
service = _Querier_GetNonNativeDNSService(manager, q, excludeEncryptedDNS);
}
return service;
}
mDNSlocal pid_t _Querier_GetMyPID(void)
{
static dispatch_once_t sOnce = 0;
static pid_t sPID = 0;
dispatch_once(&sOnce,
^{
sPID = getpid();
});
return sPID;
}
mDNSlocal const mDNSu8 *_Querier_GetMyUUID(void)
{
static dispatch_once_t sOnce = 0;
static mDNSu8 sUUID[16];
dispatch_once(&sOnce,
^{
mdns_system_pid_to_uuid(_Querier_GetMyPID(), sUUID);
});
return sUUID;
}
mDNSlocal mDNSBool _Querier_QuestionBelongsToSelf(const DNSQuestion *q)
{
if (q->pid != 0)
{
return ((q->pid == _Querier_GetMyPID()) ? mDNStrue : mDNSfalse);
}
else
{
return ((uuid_compare(q->uuid, _Querier_GetMyUUID()) == 0) ? mDNStrue : mDNSfalse);
}
}
mDNSlocal mDNSBool _Querier_DNSServiceIsUnscopedAndLacksPrivacy(const mdns_dns_service_t service)
{
if ((mdns_dns_service_get_scope(service) == mdns_dns_service_scope_none) &&
!mdns_dns_service_is_encrypted(service) && !mdns_dns_service_interface_is_vpn(service))
{
return mDNStrue;
}
else
{
return mDNSfalse;
}
}
#define kQuerierLogFullDNSServicePeriodSecs 60
mDNSlocal mDNSBool _Querier_ShouldLogFullDNSService(const mdns_dns_service_t service)
{
uint64_t *lastFullLogTicks = (uint64_t *)mdns_dns_service_get_context(service);
if (lastFullLogTicks)
{
const uint64_t nowTicks = mach_continuous_time();
const uint64_t diffTicks = nowTicks - *lastFullLogTicks;
if ((diffTicks / mdns_mach_ticks_per_second()) < kQuerierLogFullDNSServicePeriodSecs)
{
return mDNSfalse;
}
*lastFullLogTicks = nowTicks;
}
else
{
lastFullLogTicks = (uint64_t *)mdns_malloc(sizeof(*lastFullLogTicks));
if (lastFullLogTicks)
{
*lastFullLogTicks = mach_continuous_time();
mdns_dns_service_set_context(service, lastFullLogTicks);
mdns_dns_service_set_context_finalizer(service, mdns_free_context_finalizer);
}
}
return mDNStrue;
}
mDNSlocal mDNSBool _Querier_VPNDNSServiceExistsForQName(const domainname *const qname)
{
const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
if (manager)
{
const mdns_dns_service_t service = mdns_dns_service_manager_get_unscoped_system_service(manager, qname->c);
if (service && mdns_dns_service_interface_is_vpn(service))
{
return mDNStrue;
}
}
return mDNSfalse;
}
// So far, ODoH/DoH/DoT DNS services may be specified without any server IP addresses, just a hostname. In such a case,
// the underlying nw_connection will need to resolve the DNS service's hostname. To avoid potential dependency cycles
// because of mDNSResponder issuing GAI requests to itself, we simply prevent DNSQuestions with mDNSResponder's PID or
// Mach-O UUID from using ODoH/DoH/DoT services.
//
// A client may have explicitly requested that use of encrypted DNS protocols is prohibited for similar dependency cycle
// reasons. In this case, ProhibitEncryptedDNS will be set to true.
//
// Also, if a DNSQuestion's QNAME is in a special-use mDNS local domain, and is being sent via unicast DNS as a
// workaround for private internal networks that incorrectly use these domains for their network's DNS, then
// ODoH/DoH/DoT should not be used. It only makes sense to send the DNS queries to DNS servers belonging to the network,
// e.g., those specified via DHCP.
mDNSlocal mDNSBool _Querier_ExcludeEncryptedDNSServices(const DNSQuestion *const q)
{
return (_Querier_QuestionBelongsToSelf(q) || q->ProhibitEncryptedDNS || IsLocalDomain(&q->qname));
}
mDNSexport void Querier_SetDNSServiceForQuestion(DNSQuestion *q)
{
const mDNSBool excludeEncryptedDNS = _Querier_ExcludeEncryptedDNSServices(q);
if (!uuid_is_null(q->ResolverUUID) && excludeEncryptedDNS)
{
uuid_clear(q->ResolverUUID);
LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
"[R%u->Q%u] Cleared resolver UUID for question: " PRI_DM_NAME " (" PUB_S ")",
q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype));
}
mdns_forget(&q->dnsservice);
mdns_dns_service_t service = _Querier_GetDNSService(q, excludeEncryptedDNS);
if (!excludeEncryptedDNS)
{
mDNSBool retryPathEval = mDNSfalse;
const char *retryReason = "<unspecified>";
if (service)
{
// Check whether path evaluation needs to be retried if path evaluation for the original QNAME was done by the
// client, a CNAME traversal has taken place, the DNSQuestion is not interface-scoped, and the current DNS
// service is not native.
if ((q->flags & kDNSServiceFlagsPathEvaluationDone) && (q->lastDNSServiceID != MDNS_DNS_SERVICE_INVALID_ID) &&
!q->InterfaceID && !mdns_dns_service_is_native(service))
{
// If the current DNS service isn't identical to the previous DNS service, and the DNS service is unscoped
// and lacks privacy, then retry path evaluation. A path evaluation with the new QNAME may result in using
// a DNS service that offers privacy.
if ((mdns_dns_service_get_id(service) != q->lastDNSServiceID) &&
_Querier_DNSServiceIsUnscopedAndLacksPrivacy(service))
{
retryReason = "avoid non-private DNS service";
retryPathEval = mDNStrue;
}
// If the DNSQuestion is UUID-scoped, but there exists a VPN DNS service for its QNAME, then retry path
// evaluation in case the VPN DNS service should be used for the new QNAME.
else if (!uuid_is_null(q->ResolverUUID) && _Querier_VPNDNSServiceExistsForQName(&q->qname))
{
retryReason = "QNAME is in a VPN DNS service's domain";
retryPathEval = mDNStrue;
}
}
}
else if (!uuid_is_null(q->ResolverUUID))
{
// If the ResolverUUID is not null, but we didn't get a DNS service, then the ResolverUUID may be stale, i.e.,
// the resolver configuration with that UUID may have been deleted, so retry path evaluation.
retryReason = "ResolverUUID may be stale";
retryPathEval = mDNStrue;
}
if (retryPathEval)
{
LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
"[R%u->Q%u] Retrying path evaluation -- qname: " PRI_DM_NAME ", qtype: " PUB_S ", reason: " PUB_S,
q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), retryReason);
mDNSPlatformGetDNSRoutePolicy(q);
service = _Querier_GetDNSService(q, excludeEncryptedDNS);
}
}
q->dnsservice = service;
mdns_retain_null_safe(q->dnsservice);
mDNSBool enablesDNSSEC = mDNSfalse;
#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
enablesDNSSEC = dns_question_is_dnssec_requestor(q);
#endif
if (!q->dnsservice || _Querier_ShouldLogFullDNSService(q->dnsservice))
{
LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
"[R%u->Q%u] Question for " PRI_DM_NAME " (" PUB_S PUB_S ") assigned DNS service -- %@",
q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype),
enablesDNSSEC ? ", DNSSEC" : "", q->dnsservice);
}
else
{
LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
"[R%u->Q%u] Question for " PRI_DM_NAME " (" PUB_S PUB_S ") assigned DNS service %llu",
q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype),
enablesDNSSEC ? ", DNSSEC" : "", mdns_dns_service_get_id(q->dnsservice));
}
}
mDNSexport void Querier_RegisterPathResolver(const uuid_t resolverUUID)
{
const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
if (manager)
{
mdns_dns_service_manager_register_path_resolver(manager, resolverUUID);
}
}
mDNSexport mdns_dns_service_id_t Querier_RegisterCustomDNSService(const xpc_object_t resolverConfigDict)
{
mdns_dns_service_id_t ident = 0;
const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
if (manager)
{
ident = mdns_dns_service_manager_register_custom_service(manager, resolverConfigDict);
}
return ident;
}
mDNSexport mdns_dns_service_id_t Querier_RegisterCustomDNSServiceWithPListData(const uint8_t *dataPtr, size_t dataLen)
{
mdns_dns_service_id_t ident = 0;
const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
if (manager)
{
xpc_object_t resolverConfigDict = mdns_xpc_create_dictionary_from_plist_data(dataPtr, dataLen, NULL);
if (resolverConfigDict)
{
ident = mdns_dns_service_manager_register_custom_service(manager, resolverConfigDict);
xpc_forget(&resolverConfigDict);
}
}
return ident;
}
mDNSexport void Querier_DeregisterCustomDNSService(const mdns_dns_service_id_t ident)
{
const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
if (manager)
{
mdns_dns_service_manager_deregister_custom_service(manager, ident);
}
}
mDNSexport mdns_dns_service_id_t Querier_RegisterNativeDNSService(const mdns_dns_service_definition_t dns_service_definition)
{
mdns_dns_service_id_t ident = MDNS_DNS_SERVICE_INVALID_ID;
const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
if (manager)
{
OSStatus err = kNoErr;
ident = mdns_dns_service_manager_register_native_service(manager, dns_service_definition, &err);
if (err != kNoErr)
{
LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_FAULT,
"Failed to register native DNS service - error: %d.", (int)err);
}
}
return ident;
}
mDNSexport void Querier_DeregisterNativeDNSService(const mdns_dns_service_id_t ident)
{
const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
if (manager)
{
mdns_dns_service_manager_deregister_native_service(manager, ident);
}
}
mDNSexport void Querier_RegisterDoHURI(const char *doh_uri, const char *domain)
{
const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
if (manager)
{
mdns_dns_service_manager_register_doh_uri(manager, doh_uri, domain);
}
}
mDNSexport void Querier_ApplyDNSConfig(const dns_config_t *config)
{
const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
if (manager)
{
mdns_dns_service_manager_apply_dns_config(manager, config);
_Querier_LogDNSServices(manager);
}
}
#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_ANALYTICS)
mDNSlocal void _Querier_UpdateQuestionMetrics(DNSQuestion *const q, const mdns_querier_t querier)
{
if (querier && (mdns_querier_get_resolver_type(querier) != mdns_resolver_type_null))
{
q->metrics.querySendCount += mdns_querier_get_send_count(querier);
}
}
mDNSlocal void _Querier_UpdateDNSMessageSizeAnalytics(const mdns_querier_t querier)
{
const mdns_dns_service_t dnsservice = (mdns_dns_service_t)mdns_querier_get_context(querier);
bool is_cellular = mdns_dns_service_interface_is_cellular(dnsservice);
dns_transport_t transport = dnssd_analytics_dns_transport_for_resolver_type(mdns_querier_get_resolver_type(querier));
if (mdns_querier_get_send_count(querier) > 0)
{
const mDNSu32 len = mdns_querier_get_query_length(querier);
if (len > 0)
{
dnssd_analytics_update_dns_query_size(is_cellular, transport, len);
}
}
if ((mdns_querier_get_result_type(querier) == mdns_querier_result_type_response) &&
!mdns_querier_response_is_fabricated(querier))
{
const mDNSu32 len = mdns_querier_get_response_length(querier);
if (len > 0)
{
dnssd_analytics_update_dns_reply_size(is_cellular, transport, len);
}
}
}
#endif
#define kOrphanedQuerierMaxCount 10
mDNSlocal CFMutableSetRef _Querier_GetOrphanedQuerierSet(void)
{
static CFMutableSetRef sOrphanedQuerierSet = NULL;
if (!sOrphanedQuerierSet)
{
static const CFSetCallBacks sSetCallbacks =
{
.version = 0,
.retain = mdns_cf_callback_retain,
.release = mdns_cf_callback_release,
.copyDescription = mdns_cf_callback_copy_description
};
sOrphanedQuerierSet = CFSetCreateMutable(kCFAllocatorDefault, 0, &sSetCallbacks);
}
return sOrphanedQuerierSet;
}
mDNSlocal void _Querier_HandleQuerierResponse(const mdns_querier_t querier)
{
KQueueLock();
LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
"[Q%u] Handling concluded querier: %@", mdns_querier_get_user_id(querier), querier);
#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_ANALYTICS)
_Querier_UpdateDNSMessageSizeAnalytics(querier);
#endif
mDNS *const m = &mDNSStorage;
const mdns_querier_result_type_t resultType = mdns_querier_get_result_type(querier);
const mdns_dns_service_t dnsservice = (mdns_dns_service_t)mdns_querier_get_context(querier);
if (resultType == mdns_querier_result_type_response)
{
if (!mdns_dns_service_is_defunct(dnsservice))
{
size_t copyLen = mdns_querier_get_response_length(querier);
if (copyLen > sizeof(m->imsg.m))
{
LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
"[Q%u] Large %lu-byte response will be truncated to fit mDNSCore's %lu-byte message buffer",
mdns_querier_get_user_id(querier), (unsigned long)copyLen, (unsigned long)sizeof(m->imsg.m));
copyLen = sizeof(m->imsg.m);
}
memcpy(&m->imsg.m, mdns_querier_get_response_ptr(querier), copyLen);
const mDNSu8 *const end = ((mDNSu8 *)&m->imsg.m) + copyLen;
mDNSCoreReceiveForQuerier(m, &m->imsg.m, end, querier, dnsservice);
}
}
const CFMutableSetRef set = _Querier_GetOrphanedQuerierSet();
if (set)
{
CFSetRemoveValue(set, querier);
}
mDNSBool qIsNew = mDNSfalse;
DNSQuestion *q = Querier_GetDNSQuestion(querier, &qIsNew);
if (q)
{
#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_ANALYTICS)
_Querier_UpdateQuestionMetrics(q, q->querier);
#endif
mdns_forget(&q->querier);
const mDNSBool needUpdatedQuerier = q->NeedUpdatedQuerier;
q->NeedUpdatedQuerier = mDNSfalse;
// If the DNSQuestion became suppressed, then it doesn't need a new querier.
// If the DNSQuestion is now on the m->NewQuestions sub-list (due to a restart), then it will be handled by the
// normal AnswerNewQuestion() code path, so don't start a new querier.
if (!q->Suppressed && !qIsNew)
{
mDNSBool startNewQuerier = mDNSfalse;
switch (resultType)
{
case mdns_querier_result_type_response:
// If the querier was for a previous QNAME, then start a new querier for the new QNAME.
startNewQuerier = needUpdatedQuerier;
break;
case mdns_querier_result_type_timeout:
// If the querier timed out, then the DNSQuestion was using an orphaned querier.
// Querier_HandleUnicastQuestion() will attempt to give it a new querier.
startNewQuerier = mDNStrue;
break;
case mdns_querier_result_type_error:
// The querier encountered a fatal error, which should be rare. There's nothing we can do but try again.
// This usually happens if there's resource exhaustion, so be conservative and wait five seconds before
// trying again.
mDNS_Lock(m);
q->ThisQInterval = 5 * mDNSPlatformOneSecond;
q->LastQTime = m->timenow;
SetNextQueryTime(m, q);
mDNS_Unlock(m);
break;
case mdns_querier_result_type_connection_problem:
// If we haven't yet received the connection problem update from the DNS service manager, try to apply
// it now, so that the DNSQuestion restart can notice that the DNS service is indeed having connection
// problems. It could be that the overall connection problems status has cleared by the time we got
// this connection problem result. Either way, restart the DNSQuestion and all of its duplicates so
// that they can make progress.
if (!mdns_dns_service_has_connection_problems(dnsservice))
{
const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
if (manager)
{
mdns_dns_service_manager_apply_pending_connection_problem_updates(manager);
}
}
mDNS_Lock(m);
// Re-assign the querier, so that if the DNSQuestion is the leader of a set of duplicates, we can
// iteratively identify each subsequent duplicate as each member of the set gets restarted. This is
// because when a DNSQuestion is stopped, its querier gets passed to the next duplicate if one exists.
// If a duplicate doesn't exist, then the querier gets released.
mdns_replace(&q->querier, querier);
while (q)
{
mDNS_StopQuery_internal(m, q);
q->UsedAsFailFastProbe = mDNStrue;
mDNS_StartQuery_internal(m, q);
q = Querier_GetDNSQuestion(querier, &qIsNew);
if (q && qIsNew)
{
// If we reach the NewQuestions sub-list, we're done. Just forget the querier because it has
// already concluded and is no longer useful. AnswerNewQuestion() will handle the new
// DNSQuestion as normal.
mdns_forget(&q->querier);
q = mDNSNULL;
}
}
mDNS_Unlock(m);
break;
case mdns_querier_result_type_null:
case mdns_querier_result_type_invalidation:
case mdns_querier_result_type_resolver_invalidation:
break;
}
if (startNewQuerier)
{
mDNS_Lock(m);
Querier_HandleUnicastQuestion(q);
mDNS_Unlock(m);
}
}
}
KQueueUnlock("_Querier_HandleQuerierResponse");
}
mDNSexport void Querier_HandleUnicastQuestion(DNSQuestion *q)
{
mDNS *const m = &mDNSStorage;
mdns_querier_t querier = NULL;
if (q->querier || !q->dnsservice)
{
if (q->querier)
{
q->NeedUpdatedQuerier = !mdns_querier_match(q->querier, q->qname.c, q->qtype, q->qclass);
}
goto exit;
}
#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
const bool needDNSSEC = dns_question_is_primary_dnssec_requestor(q);
#endif
const CFMutableSetRef set = _Querier_GetOrphanedQuerierSet();
if (set)
{
__block mdns_querier_t orphan = NULL;
mdns_cfset_enumerate(set,
^ bool (const mdns_querier_t _Nonnull candidate)
{
const mdns_dns_service_t dnsservice = (mdns_dns_service_t)mdns_querier_get_context(candidate);
if ((dnsservice == q->dnsservice) && mdns_querier_match(candidate, q->qname.c, q->qtype, q->qclass))
{
#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
if ((mdns_querier_get_dnssec_ok(candidate) == needDNSSEC) &&
(mdns_querier_get_checking_disabled(candidate) == needDNSSEC))
#endif
{
orphan = candidate;
}
}
const bool proceed = (orphan == NULL);
return proceed;
});
if (orphan)
{
q->querier = orphan;
mdns_retain(q->querier);
CFSetRemoveValue(set, q->querier);
mdns_querier_set_time_limit_ms(q->querier, 0);
LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
"[Q%u->Q%u] Adopted orphaned querier", mDNSVal16(q->TargetQID), mdns_querier_get_user_id(q->querier));
}
}
if (!q->querier)
{
querier = mdns_dns_service_create_querier(q->dnsservice, NULL);
require_quiet(querier, exit);
const OSStatus err = mdns_querier_set_query(querier, q->qname.c, q->qtype, q->qclass);
require_noerr_quiet(err, exit);
#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
if (needDNSSEC)
{
mdns_querier_set_dnssec_ok(querier, true);
mdns_querier_set_checking_disabled(querier, true);
}
#endif
if (q->pid != 0)
{
mdns_querier_set_delegator_pid(querier, q->pid);
}
else
{
mdns_querier_set_delegator_uuid(querier, q->uuid);
}
mdns_retain(q->dnsservice);
mdns_querier_set_context(querier, q->dnsservice);
mdns_querier_set_context_finalizer(querier, mdns_object_context_finalizer);
mdns_querier_set_queue(querier, _Querier_InternalQueue());
mdns_retain(querier);
mdns_querier_set_result_handler(querier,
^{
_Querier_HandleQuerierResponse(querier);
mdns_release(querier);
});
mdns_querier_set_log_label(querier, "Q%u", mDNSVal16(q->TargetQID));
mdns_querier_set_user_id(querier, mDNSVal16(q->TargetQID));
q->querier = querier;
mdns_retain(q->querier);
mdns_querier_activate(q->querier);
}
q->NeedUpdatedQuerier = mDNSfalse;
#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_ANALYTICS)
if (mdns_querier_get_resolver_type(q->querier) != mdns_resolver_type_null)
{
if (q->metrics.answered)
{
uDNSMetricsClear(&q->metrics);
}
if (q->metrics.firstQueryTime == 0)
{
q->metrics.firstQueryTime = NonZeroTime(m->timenow);
}
}
else
{
q->metrics.firstQueryTime = 0;
}
#endif
exit:
q->ThisQInterval = (!q->dnsservice || q->querier) ? FutureTime : mDNSPlatformOneSecond;
q->LastQTime = m->timenow;
SetNextQueryTime(m, q);
mdns_forget(&querier);
}
mDNSlocal mDNSu32 _Querier_GetQuestionCount(void)
{
mDNSu32 count = 0;
for (const DNSQuestion *q = mDNSStorage.Questions; q; q = q->next)
{
count++;
}
return count;
}
mDNSexport void Querier_ProcessDNSServiceChanges(void)
{
mDNS *const m = &mDNSStorage;
mDNSu32 slot;
CacheGroup *cg;
CacheRecord *cr;
#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
DNSPushServer **psp;
#endif
const mDNSu32 count = _Querier_GetQuestionCount();
m->RestartQuestion = m->Questions;
for (mDNSu32 i = 0; (i < count) && m->RestartQuestion; i++)
{
DNSQuestion *const q = m->RestartQuestion;
if (mDNSOpaque16IsZero(q->TargetQID))
{
m->RestartQuestion = q->next;
continue;
}
const mDNSBool excludeEncryptedDNS = _Querier_ExcludeEncryptedDNSServices(q);
mdns_dns_service_t newService = _Querier_GetDNSService(q, excludeEncryptedDNS);
mDNSBool forcePathEval = mDNSfalse;
if (q->dnsservice != newService)
{
// If the DNS service would change, the DNSQuestion is not interface-scoped, and either there is no new DNS
// service or it lacks privacy, then force a path evaluation when the DNSQuestion restarts to determine if
// there's a DNS service that offers privacy that should be used. This DNSQuestion's resolver UUID may have
// been cleared so that it can use a VPN DNS service, but that service may have just become defunct.
if (!q->InterfaceID && (!newService || _Querier_DNSServiceIsUnscopedAndLacksPrivacy(newService)))
{
forcePathEval = mDNStrue;
}
}
else
{
// If the DNS service wouldn't change and the DNS service is UUID-scoped, perform a path evaluation now to
// see if a DNS service change occurs. This might happen if a DNSQuestion was UUID-scoped to a DoH or DoT
// service, but there's a new VPN DNS service that handles the DNSQuestion's QNAME.
if (q->dnsservice && (mdns_dns_service_get_scope(q->dnsservice) == mdns_dns_service_scope_uuid))
{
mDNSPlatformGetDNSRoutePolicy(q);
newService = _Querier_GetDNSService(q, excludeEncryptedDNS);
}
}
mDNSBool restart = mDNSfalse;
if (q->dnsservice != newService)
{
#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
// If this question had a DNS Push server associated with it, substitute the new server for the
// old one. If there is no new server, then we'll clean up the push server later.
if (!q->DuplicateOf && q->dnsPushServer)
{
if (q->dnsPushServer->dnsservice == q->dnsservice)
{
mdns_replace(&q->dnsPushServer->dnsservice, newService);
}
// If it is null, cancel the DNS push server.
if (!q->dnsPushServer->dnsservice)
{
DNSPushServerCancel(q->dnsPushServer, mDNSfalse);
}
}
#endif
restart = mDNStrue;
}
else
{
mDNSBool newSuppressed = ShouldSuppressUnicastQuery(q, newService);
if (!q->Suppressed != !newSuppressed) restart = mDNStrue;
}
if (restart)
{
if (!q->Suppressed)
{
CacheRecordRmvEventsForQuestion(m, q);
if (m->RestartQuestion == q) LocalRecordRmvEventsForQuestion(m, q);
}
if (m->RestartQuestion == q)
{
#if MDNSRESPONDER_SUPPORTS(COMMON, LOCAL_DNS_RESOLVER_DISCOVERY)
// Since we are restarting the question, retain the resolver discovery object to prevent it from being
// stopped and deallocated.
const domainname *domain_to_discover_resolver = mDNSNULL;
if (dns_question_requires_resolver_discovery(q, &domain_to_discover_resolver))
{
resolver_discovery_add(domain_to_discover_resolver, mDNSfalse);
}
#endif
mDNS_StopQuery_internal(m, q);
q->ForcePathEval = forcePathEval;
q->next = mDNSNULL;
mDNS_StartQuery_internal(m, q);
#if MDNSRESPONDER_SUPPORTS(COMMON, LOCAL_DNS_RESOLVER_DISCOVERY)
// Release the resolver discovery object retained above so that the reference count of the object goes
// back to its original value, after the restart process has finished.
if (dns_question_requires_resolver_discovery(q, &domain_to_discover_resolver))
{
resolver_discovery_remove(domain_to_discover_resolver, mDNSfalse);
}
#endif
}
}
if (m->RestartQuestion == q) m->RestartQuestion = q->next;
}
m->RestartQuestion = mDNSNULL;
#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
// The above code may have found some DNS Push servers that are no longer valid. Now that we
// are done running through the code, we need to drop our connections to those servers.
// When we get here, any such servers should have zero questions associated with them.
for (psp = &m->DNSPushServers; *psp != mDNSNULL; )
{
DNSPushServer *server = *psp;
// It's possible that a push server whose DNS server has been deleted could be still connected but
// not referenced by any questions. In this case, we just delete the push server rather than trying
// to figure out with which DNS server (if any) to associate it.
if (server->dnsservice == mDNSNULL || mdns_dns_service_is_defunct(server->dnsservice))
{
// Since we are changing the m->DNSPushServers that DNSPushServerCancel() will iterate later, we will do the
// server removal for it. And tell it to not touch the m->DNSPushServers by passing alreadyRemovedFromSystem
// == true.
// Unlink from the m->DNSPushServers list.
*psp = server->next;
server->next = mDNSNULL;
// Release all the DNS push zones that use this server from the m->DNSPushZones list.
DNSPushZoneRemove(m, server);
// Cancel the server.
DNSPushServerCancel(server, mDNStrue);
// Release the reference to the server that m->DNSPushServers list holds.
DNS_PUSH_RELEASE(server, DNSPushServerFinalize);
}
else
{
psp = &(server->next);
}
}
#endif
FORALL_CACHERECORDS(slot, cg, cr)
{
if (cr->resrec.InterfaceID) continue;
if (!cr->resrec.dnsservice || mdns_dns_service_is_defunct(cr->resrec.dnsservice))
{
mdns_forget(&cr->resrec.dnsservice);
mDNS_PurgeCacheResourceRecord(m, cr);
}
}
}
mDNSexport DNSQuestion *Querier_GetDNSQuestion(const mdns_querier_t querier, mDNSBool *const outIsNew)
{
DNSQuestion *q;
mDNS *const m = &mDNSStorage;
mDNSBool isNew = mDNSfalse;
for (q = m->Questions; q; q = q->next)
{
if (!isNew && (q == m->NewQuestions))
{
isNew = mDNStrue;
}
if (q->querier == querier)
{
break;
}
}
if (outIsNew)
{
*outIsNew = q ? isNew : mDNSfalse;
}
return q;
}
mDNSexport mDNSBool Querier_ResourceRecordIsAnswer(const ResourceRecord * const rr, const mdns_querier_t querier)
{
mDNSBool isAnswer;
const mDNSu16 qtype = mdns_querier_get_qtype(querier);
const mDNSu8 *const qname = mdns_querier_get_qname(querier);
if (qname == mDNSNULL)
{
isAnswer = mDNSfalse;
goto exit;
}
if (rr->rrclass != mdns_querier_get_qclass(querier))
{
isAnswer = mDNSfalse;
goto exit;
}
RRTypeAnswersQuestionTypeFlags flags = kRRTypeAnswersQuestionTypeFlagsNone;
#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
const mDNSBool requiresRRToValidate = mdns_querier_get_dnssec_ok(querier) && mdns_querier_get_checking_disabled(querier);
if (requiresRRToValidate)
{
flags |= kRRTypeAnswersQuestionTypeFlagsRequiresDNSSECRRToValidate;
}
#endif
isAnswer = RRTypeAnswersQuestionType(rr, qtype, flags);
if (!isAnswer)
{
goto exit;
}
isAnswer = SameDomainName(rr->name, (const domainname *)qname);
exit:
return isAnswer;
}
mDNSexport mDNSBool Querier_SameNameCacheRecordIsAnswer(const CacheRecord *const cr, const mdns_querier_t querier)
{
mDNSBool isAnswer;
const ResourceRecord *const rr = &cr->resrec;
const mDNSu16 qtype = mdns_querier_get_qtype(querier);
if (rr->rrclass != mdns_querier_get_qclass(querier))
{
isAnswer = mDNSfalse;
goto exit;
}
RRTypeAnswersQuestionTypeFlags flags = kRRTypeAnswersQuestionTypeFlagsNone;
#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
const mDNSBool requiresRRToValidate = mdns_querier_get_dnssec_ok(querier) && mdns_querier_get_checking_disabled(querier);
if (requiresRRToValidate)
{
flags |= kRRTypeAnswersQuestionTypeFlagsRequiresDNSSECRRToValidate;
}
#endif
isAnswer = RRTypeAnswersQuestionType(rr, qtype, flags);
exit:
return isAnswer;
}
#define kOrphanedQuerierTimeLimitSecs 5
mDNSexport void Querier_HandleStoppedDNSQuestion(DNSQuestion *q)
{
if (q->querier && !mdns_querier_has_concluded(q->querier))
{
const mdns_dns_service_t dnsservice = (mdns_dns_service_t)mdns_querier_get_context(q->querier);
if (!mdns_dns_service_is_defunct(dnsservice))
{
const CFMutableSetRef set = _Querier_GetOrphanedQuerierSet();
if (set && (CFSetGetCount(set) < kOrphanedQuerierMaxCount))
{
CFSetAddValue(set, q->querier);
LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
"[Q%u] Keeping orphaned querier for up to " StringifyExpansion(kOrphanedQuerierTimeLimitSecs) " seconds",
mdns_querier_get_user_id(q->querier));
mdns_querier_set_time_limit_ms(q->querier, kOrphanedQuerierTimeLimitSecs * 1000);
mdns_forget(&q->querier);
}
}
}
mdns_querier_forget(&q->querier);
mdns_forget(&q->dnsservice);
}
mDNSexport mdns_querier_t Querier_HandlePreCNAMERestart(DNSQuestion *const q)
{
q->lastDNSServiceID = q->dnsservice ? mdns_dns_service_get_id(q->dnsservice) : MDNS_DNS_SERVICE_MAX_ID;
const mdns_querier_t querier = q->querier;
q->querier = NULL;
return querier;
}
mDNSexport void Querier_HandlePostCNAMERestart(DNSQuestion *const q, const mdns_querier_t querier)
{
if (querier)
{
mDNSBool keptQuerier = mDNSfalse;
if (!q->DuplicateOf && !q->querier && q->dnsservice)
{
const mdns_dns_service_t dnsservice = (mdns_dns_service_t)mdns_querier_get_context(querier);
if (mdns_dns_service_get_id(q->dnsservice) == mdns_dns_service_get_id(dnsservice))
{
q->querier = querier;
mdns_retain(q->querier);
keptQuerier = mDNStrue;
}
}
if (!keptQuerier)
{
#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_ANALYTICS)
_Querier_UpdateQuestionMetrics(q, querier);
#endif
mdns_querier_invalidate(querier);
}
}
}
mDNSexport void Querier_PrepareQuestionForUnwindRestart(DNSQuestion *const q)
{
q->lastDNSServiceID = MDNS_DNS_SERVICE_INVALID_ID;
// Force a path evaluation if the DNSQuestion isn't interface-scoped.
if (!q->InterfaceID)
{
q->ForcePathEval = mDNStrue;
}
}
mDNSexport void Querier_HandleSleep(void)
{
const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
if (manager)
{
mdns_dns_service_manager_handle_sleep(manager);
}
}
mDNSexport void Querier_HandleWake(void)
{
const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
if (manager)
{
mdns_dns_service_manager_handle_wake(manager);
}
}
#endif // MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)