| /* |
| * Copyright (c) 2020-2024 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. |
| * |
| * This file contains code to let mDNSResponder set up the resolver for |
| * the automatic browsing domain learned from "lb._dns-sd._udp.local PTR" |
| * query. To setup the resolver, it will get the NS record for the domain |
| * and the A/AAAA record for the resolver name in the NS record rdata. |
| * Once it knows about resolver's address, it will set the resolver with |
| * configuration change to let mDNSResponder add it as a regular DNS resolver. |
| */ |
| |
| #include "mDNSFeatures.h" // for MDNSRESPONDER_SUPPORTS(COMMON, LOCAL_DNS_RESOLVER_DISCOVERY) |
| |
| #if MDNSRESPONDER_SUPPORTS(COMMON, LOCAL_DNS_RESOLVER_DISCOVERY) |
| |
| //====================================================================================================================== |
| // MARK: - Headers |
| |
| #include <stdio.h> |
| #include <mdns/private.h> |
| #include "mDNSEmbeddedAPI.h" |
| #include "uds_daemon.h" |
| #include "bsd_queue.h" // For SLIST. |
| #include "discover_resolver.h" |
| #include "tls-keychain.h" // For init_tls_cert(). |
| #include "uDNS.h" // For mDNS_StartQuery_internal() and mDNS_StopQuery_internal(). |
| |
| |
| #include "DNSCommon.h" // For mDNS_Lock() and mDNS_Unlock(). |
| |
| // for require_* |
| #if defined(__APPLE__) |
| #include <AssertMacros.h> |
| #elif defined(POSIX_BUILD) |
| #include "DebugServices.h" |
| #else |
| #ifndef require |
| #define require(assertion, exception_label) \ |
| do { \ |
| if (!(assertion)) { \ |
| goto exception_label; \ |
| } \ |
| } while (false) |
| #endif // #ifndef require |
| |
| #ifndef require_action |
| #define require_action(assertion, exception_label, action) \ |
| do { \ |
| if (!(assertion)) { \ |
| { \ |
| action; \ |
| } \ |
| goto exception_label; \ |
| } \ |
| } while (false) |
| #endif // #ifndef require_action |
| #endif |
| |
| #include "mdns_strict.h" |
| |
| //====================================================================================================================== |
| // MARK: - Macros |
| |
| // Release macros that are used to do reference counting for the discover_resolver_t object. |
| #define discover_resolver_release(OBJ) \ |
| do { \ |
| (OBJ)->ref_count--; \ |
| LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG, "discover_resolver_t released " \ |
| "- ref count after releasing: %u.", (OBJ)->ref_count); \ |
| if ((OBJ)->ref_count == 0) { \ |
| _MDNS_STRICT_DISPOSE_TEMPLATE((OBJ), (OBJ)->finalizer); \ |
| } \ |
| } while (false) |
| |
| #define discover_resolver_forget(PTR) \ |
| do { \ |
| if (*(PTR)) { \ |
| discover_resolver_release(*(PTR)); \ |
| *(PTR) = mDNSNULL; \ |
| } \ |
| } while (mDNSfalse) |
| |
| //====================================================================================================================== |
| // MARK: - Structures |
| |
| // the addresse of the discovered resolver get by resolving the resolver's name |
| typedef struct resolver_address resolver_address_t; |
| struct resolver_address { |
| mDNSAddr addr; |
| resolver_address_t * NULLABLE next; |
| }; |
| |
| // the name of the discovered resolver for the given domain. |
| typedef struct discover_resolver_name discover_resolver_name_t; |
| struct discover_resolver_name { |
| domainname resolver_name; // the discovered resolver name for the given domain |
| mDNSInterfaceID NULLABLE interface_id; // the interface id where the response was received |
| DNSQuestion ipv4_question; // used to query for IPv4 address of the resolver name |
| DNSQuestion ipv6_question; // used to query for IPv6 address of the resolver name |
| |
| resolver_address_t * NULLABLE addresses; // linked list of all addresses for the resolver name |
| mDNSs32 next_update_time; // If non-zero, it indicates the next time when we should update the DNS |
| // service that has been previously registered |
| discover_resolver_name_t * NULLABLE next; |
| }; |
| |
| // the context of resolver discovery. |
| typedef struct discover_resolver_context discover_resolver_context_t; |
| struct discover_resolver_context { |
| DNSQuestion ns_question; // used to query for the resolver name for the given domain |
| discover_resolver_name_t * NULLABLE resolver_names; // linked list of all resolver names for the given domain |
| }; |
| |
| // This object is used by the caller to start or stop the resolver discovery. |
| typedef struct discover_resolver discover_resolver_t; |
| typedef void (* discover_resolver_finalizer_t)(discover_resolver_t * NULLABLE discover_resolver); |
| struct discover_resolver { |
| domainname domain; // The domain that is used to discover the local DNS resolver. |
| mDNSs32 next_stop_time; // If non-zero, indicate when the object should be released. |
| mDNSu32 ref_count; // The reference count. If it is zero, the object will be finalized. |
| mDNSu32 use_count; // The use count that indicates how many clients need this resolver |
| // discovery. |
| |
| discover_resolver_context_t * context; // The context. |
| discover_resolver_finalizer_t finalizer; // The finalizer that will be called to invalidate any ongoing |
| // activity and free the memory associated with this object, when |
| // the reference count becomes 0. |
| }; |
| |
| // The single-linked node type that contains discover_resolver_t. |
| typedef struct discover_resolver_node discover_resolver_node_t; |
| struct discover_resolver_node { |
| SLIST_ENTRY(discover_resolver_node) __entries; |
| discover_resolver_t * NULLABLE discover_resolver; |
| }; |
| |
| // The single-linked list that contains multiple discover_resolver_t objects. |
| typedef struct discover_resolver_slist discover_resolver_slist_t; |
| SLIST_HEAD(discover_resolver_slist, discover_resolver_node); |
| |
| //====================================================================================================================== |
| // MARK: - Globals |
| |
| extern mDNS mDNSStorage; |
| static discover_resolver_slist_t * g_discover_resolvers = NULL; |
| |
| //====================================================================================================================== |
| // MARK: - Forward Declarations |
| |
| discover_resolver_t * |
| discover_resolver_create(const domainname * NONNULL domain); |
| |
| static void |
| discover_resolver_finalize(discover_resolver_t * NULLABLE discover_resolver); |
| #define MDNS_DISPOSE_DISCOVER_RESOLVER(obj) _MDNS_STRICT_DISPOSE_TEMPLATE(obj, discover_resolver_finalize) |
| |
| static discover_resolver_context_t * |
| discover_resolver_context_create(void); |
| |
| static void |
| discover_resolver_context_dispose(discover_resolver_context_t * NULLABLE context); |
| #define MDNS_DISPOSE_DISCOVER_RESOLVER_CONTEXT(obj) _MDNS_STRICT_DISPOSE_TEMPLATE(obj, discover_resolver_context_dispose) |
| |
| static bool |
| discover_resolver_start(discover_resolver_context_t * NULLABLE context, const domainname * NONNULL domain); |
| |
| void |
| discover_resolver_stop(discover_resolver_context_t * NULLABLE context); |
| |
| static mDNSs32 |
| _discover_resolver_get_next_dns_service_update_time(void); |
| |
| static void |
| _discover_resolver_update_dns_service(void); |
| |
| static mDNSs32 |
| _discover_resolver_get_next_unused_resolver_discovery_stop_time(void); |
| |
| static void |
| _discover_resolver_stop_unused_resolver_discovery(void); |
| |
| //====================================================================================================================== |
| // MARK: - Functions |
| |
| // discover_resolver_slist_t |
| |
| static discover_resolver_slist_t * NULLABLE |
| discover_resolver_slist_create(void) |
| { |
| discover_resolver_slist_t * const me = mdns_calloc(1, sizeof(*me)); |
| require(me != NULL, exit); |
| |
| SLIST_INIT(me); |
| |
| exit: |
| return me; |
| } |
| |
| //====================================================================================================================== |
| |
| static discover_resolver_node_t * |
| discover_resolver_slist_add_front(discover_resolver_slist_t * const NONNULL me, |
| discover_resolver_t * const NONNULL discover_resolver) |
| { |
| discover_resolver_node_t * const n = mdns_calloc(1, sizeof(*n)); |
| require(n != NULL, exit); |
| |
| n->discover_resolver = discover_resolver; |
| SLIST_INSERT_HEAD(me, n, __entries); |
| |
| exit: |
| return n; |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| discover_resolver_slist_empty(const discover_resolver_slist_t * const NONNULL me) |
| { |
| return SLIST_EMPTY(me); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| discover_resolver_slist_dispose(discover_resolver_slist_t * const NONNULL me) |
| { |
| discover_resolver_node_t * n; |
| |
| while (!SLIST_EMPTY(me)) { |
| n = SLIST_FIRST(me); |
| SLIST_REMOVE_HEAD(me, __entries); |
| mdns_free(n); |
| } |
| |
| discover_resolver_slist_t * temp_me = me; |
| mdns_free(temp_me); |
| } |
| #define MDNS_DISPOSE_DISCOVER_RESOLVER_SLIST(obj) _MDNS_STRICT_DISPOSE_TEMPLATE(obj, discover_resolver_slist_dispose) |
| |
| //====================================================================================================================== |
| |
| // resolver_address_t |
| |
| static resolver_address_t * |
| resolver_address_create(const void * const NONNULL addr_data, mDNSAddr_Type addr_type) |
| { |
| resolver_address_t * resolver_address = NULL; |
| |
| require(addr_type == mDNSAddrType_IPv4|| addr_type == mDNSAddrType_IPv6, exit); |
| |
| resolver_address = mdns_calloc(1, sizeof(*resolver_address)); |
| require(resolver_address != NULL, exit); |
| |
| if (addr_type == mDNSAddrType_IPv4) { |
| memcpy(&resolver_address->addr.ip.v4, addr_data, sizeof(resolver_address->addr.ip.v4)); |
| } else { // sa_family == mDNSAddrType_IPv6 |
| memcpy(&resolver_address->addr.ip.v6, addr_data, sizeof(resolver_address->addr.ip.v6)); |
| } |
| |
| resolver_address->addr.type = addr_type; |
| |
| exit: |
| return resolver_address; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| resolver_address_delete(resolver_address_t * NONNULL to_be_deleted) |
| { |
| resolver_address_t * temp = to_be_deleted; |
| mdns_free(temp); |
| } |
| |
| //====================================================================================================================== |
| |
| static resolver_address_t * |
| resolver_addresses_add(const void * const NONNULL addr_data, const mDNSAddr_Type addr_type, |
| resolver_address_t * NULLABLE * const NONNULL out_addresses) |
| { |
| resolver_address_t * resolver_address = resolver_address_create(addr_data, addr_type); |
| require(resolver_address != NULL, exit); |
| |
| if (*out_addresses != NULL) { |
| resolver_address->next = *out_addresses; |
| } |
| *out_addresses = resolver_address; |
| |
| exit: |
| return resolver_address; |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| resolver_addresses_remove(const void * const NONNULL addr_data, mDNSAddr_Type addr_type, |
| resolver_address_t * NULLABLE * const NONNULL out_addresses) |
| { |
| bool succeeded; |
| |
| require_action(addr_type == mDNSAddrType_IPv4 || addr_type == mDNSAddrType_IPv6, exit, succeeded = false); |
| |
| resolver_address_t * prev = NULL; |
| resolver_address_t * current = NULL; |
| resolver_address_t * next; |
| |
| for (current = *out_addresses; current != NULL; current = next) { |
| bool found = false; |
| next = current->next; |
| if (current->addr.type != addr_type) { |
| continue; |
| } |
| if (addr_type == mDNSAddrType_IPv4) { |
| found = (memcmp(¤t->addr.ip.v4, addr_data, sizeof(current->addr.ip.v4)) == 0); |
| } else { // sa_family == AF_INET6 |
| found = (memcmp(¤t->addr.ip.v6, addr_data, sizeof(current->addr.ip.v6)) == 0); |
| } |
| if (found) { |
| break; |
| } |
| prev = current; |
| } |
| |
| require_action(current != NULL, exit, succeeded = false); |
| |
| if (prev != NULL) { |
| prev->next = current->next; |
| } else { |
| *out_addresses = current->next; |
| } |
| |
| resolver_address_delete(current); |
| |
| succeeded = true; |
| exit: |
| return succeeded; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| resolver_addresses_remove_all(resolver_address_t * const NONNULL addresses) |
| { |
| resolver_address_t * current, * next; |
| for (current = addresses; current != NULL; current = next) { |
| next = current->next; |
| resolver_address_delete(current); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static resolver_address_t * |
| resolver_addresses_find(const void * const NONNULL addr_data, const mDNSAddr_Type addr_type, |
| resolver_address_t * const addresses) |
| { |
| resolver_address_t * current = NULL; |
| |
| require(addr_type == mDNSAddrType_IPv4 || addr_type == mDNSAddrType_IPv6, exit); |
| |
| for (current = addresses; current != NULL; current = current->next) { |
| bool found = false; |
| if (current->addr.type != mDNSAddrType_IPv6) { |
| continue; |
| } |
| if (addr_type == mDNSAddrType_IPv4) { |
| found = (memcmp(¤t->addr.ip.v4, addr_data, sizeof(current->addr.ip.v4)) == 0); |
| } else { // sa_family == AF_INET6 |
| found = (memcmp(¤t->addr.ip.v6, addr_data, sizeof(current->addr.ip.v6)) == 0); |
| } |
| if (found) { |
| break; |
| } |
| } |
| |
| exit: |
| return current; |
| } |
| |
| //====================================================================================================================== |
| // discover_resolver_name_t |
| |
| static discover_resolver_name_t * |
| discover_resolver_name_create(const domainname * const NONNULL resolver_name, const mDNSInterfaceID interface_id) |
| { |
| bool succeeded; |
| discover_resolver_name_t * resolver = NULL; |
| |
| resolver = mdns_calloc(1, sizeof(*resolver)); |
| require_action(resolver != NULL, exit, succeeded = false); |
| |
| AssignDomainName(&resolver->resolver_name, resolver_name); |
| resolver->interface_id = interface_id; |
| |
| succeeded = true; |
| exit: |
| if (!succeeded) { |
| mdns_free(resolver); |
| } |
| return resolver; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| discover_resolver_name_delete(discover_resolver_name_t * NONNULL to_be_deleted) |
| { |
| if (to_be_deleted->addresses != NULL) { |
| resolver_addresses_remove_all(to_be_deleted->addresses); |
| } |
| discover_resolver_name_t * NULLABLE temp_to_be_deleted = to_be_deleted; |
| mdns_free(temp_to_be_deleted); |
| } |
| |
| //====================================================================================================================== |
| |
| static discover_resolver_name_t * |
| discover_resolver_name_add(const domainname * const name, const mDNSInterfaceID interface_id, |
| discover_resolver_name_t * NULLABLE * const NONNULL out_resolver_names) |
| { |
| bool succeeded; |
| discover_resolver_name_t * new_resolver_name = NULL; |
| |
| new_resolver_name = discover_resolver_name_create(name, interface_id); |
| require_action(new_resolver_name != NULL, exit, succeeded = false); |
| |
| memset(&new_resolver_name->ipv4_question, 0, sizeof(new_resolver_name->ipv4_question)); |
| memset(&new_resolver_name->ipv6_question, 0, sizeof(new_resolver_name->ipv6_question)); |
| |
| if (*out_resolver_names != NULL) { |
| new_resolver_name->next = *out_resolver_names; |
| } |
| *out_resolver_names = new_resolver_name; |
| |
| succeeded = true; |
| exit: |
| if (!succeeded) { |
| if (new_resolver_name != NULL) { |
| discover_resolver_name_delete(new_resolver_name); |
| } |
| } |
| return new_resolver_name; |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| discover_resolver_name_remove(const domainname * const resolver_name, const mDNSInterfaceID interface_id, |
| discover_resolver_name_t * NULLABLE * const NONNULL out_resolver_names) |
| { |
| bool succeeded; |
| discover_resolver_name_t * prev = NULL; |
| discover_resolver_name_t * current, * next; |
| |
| for (current = *out_resolver_names; current != NULL; current = next) { |
| next = current->next; |
| if (SameDomainName(¤t->resolver_name, resolver_name) && current->interface_id == interface_id) { |
| break; |
| } |
| prev = current; |
| } |
| |
| require_action(current != NULL, exit, succeeded = false); |
| |
| if (prev != NULL) { |
| prev->next = current->next; |
| } else { |
| *out_resolver_names = current->next; |
| } |
| discover_resolver_name_delete(current); |
| |
| succeeded = true; |
| exit: |
| return succeeded; |
| } |
| |
| //====================================================================================================================== |
| |
| static discover_resolver_name_t * |
| discover_resolver_name_find(const domainname * const name, mDNSInterfaceID interface_id, |
| discover_resolver_name_t * const NULLABLE resolver_names) |
| { |
| discover_resolver_name_t * resolver_name = NULL; |
| |
| for (resolver_name = resolver_names; resolver_name != NULL; resolver_name = resolver_name->next) { |
| if (SameDomainName(&resolver_name->resolver_name, name) && resolver_name->interface_id == interface_id) { |
| break; |
| } |
| } |
| |
| return resolver_name; |
| } |
| |
| //====================================================================================================================== |
| |
| bool |
| resolver_discovery_add(const domainname * const NONNULL domain_to_discover, const bool grab_mdns_lock) |
| { |
| bool succeeded; |
| discover_resolver_node_t * np; |
| discover_resolver_t * discover_resolver_to_retain = NULL; |
| |
| // The list has not been initialized. |
| if (g_discover_resolvers == NULL) { |
| g_discover_resolvers = discover_resolver_slist_create(); |
| require_action(g_discover_resolvers != NULL, exit, succeeded = false); |
| } |
| |
| // Looking for the existing resolver discovery. |
| SLIST_FOREACH(np, g_discover_resolvers, __entries) { |
| if (!SameDomainName(&np->discover_resolver->domain, domain_to_discover)) { |
| continue; |
| } |
| discover_resolver_to_retain = np->discover_resolver; |
| break; |
| } |
| |
| // Increase the use count if it exists. |
| if (discover_resolver_to_retain != NULL) { |
| discover_resolver_to_retain->use_count++; |
| |
| LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG, "Use the ongoing resolver discovery -- " |
| "domain: " PRI_DM_NAME ", use count: %u", DM_NAME_PARAM(&discover_resolver_to_retain->domain), |
| discover_resolver_to_retain->use_count); |
| |
| // Clears the stop time in case we are waiting for stopping it. |
| discover_resolver_to_retain->next_stop_time = 0; |
| discover_resolver_to_retain = NULL; |
| succeeded = true; |
| goto exit; |
| } |
| |
| // Or create and start a new resolver discovery for the given domain. |
| discover_resolver_to_retain = discover_resolver_create(domain_to_discover); |
| require_action(discover_resolver_to_retain != NULL, exit, succeeded = false); |
| |
| if (grab_mdns_lock) { |
| mDNS_Lock(&mDNSStorage); |
| } |
| succeeded = discover_resolver_start(discover_resolver_to_retain->context, domain_to_discover); |
| if (grab_mdns_lock) { |
| mDNS_Unlock(&mDNSStorage); |
| } |
| require(succeeded, exit); |
| |
| // Add the new one to the list. |
| np = discover_resolver_slist_add_front(g_discover_resolvers, discover_resolver_to_retain); |
| require_action(np != NULL, exit, succeeded = false); |
| |
| discover_resolver_to_retain->use_count = 1; |
| |
| LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "Start new resolver discovery -- " |
| "domain: " PRI_DM_NAME ", use count: %u", DM_NAME_PARAM(&discover_resolver_to_retain->domain), |
| discover_resolver_to_retain->use_count); |
| |
| discover_resolver_to_retain = NULL; |
| |
| exit: |
| discover_resolver_forget(&discover_resolver_to_retain); |
| |
| return succeeded; |
| } |
| |
| //====================================================================================================================== |
| |
| bool |
| resolver_discovery_remove(const domainname * const NONNULL domain_to_discover, const bool grab_mdns_lock) |
| { |
| bool succeeded = false; |
| discover_resolver_node_t *np, *np_temp; |
| mDNS *const m = &mDNSStorage; |
| |
| require_action(g_discover_resolvers != NULL, exit, LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_FAULT, |
| "Trying to stop a domain resolver discovery that does not exist - domain: " PRI_DM_NAME ".", |
| DM_NAME_PARAM(domain_to_discover))); |
| |
| SLIST_FOREACH_SAFE(np, g_discover_resolvers, __entries, np_temp) { |
| |
| discover_resolver_t *const discover_resolver = np->discover_resolver; |
| if (!SameDomainName(&discover_resolver->domain, domain_to_discover)) { |
| continue; |
| } |
| |
| discover_resolver->use_count--; |
| |
| LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG, "One less resolver discovery use count -- " |
| "domain: " PRI_DM_NAME ", use count: %u", DM_NAME_PARAM(&discover_resolver->domain), |
| discover_resolver->use_count); |
| |
| if (discover_resolver->use_count == 0) { |
| const mDNSs32 gracePeriodInSeconds = 5; |
| const mDNSs32 gracePeriodPlatformTime = gracePeriodInSeconds * mDNSPlatformOneSecond; |
| |
| if (grab_mdns_lock) { |
| mDNS_Lock(&mDNSStorage); |
| } |
| |
| discover_resolver->next_stop_time = NonZeroTime(m->timenow + gracePeriodPlatformTime); |
| |
| if (grab_mdns_lock) { |
| mDNS_Unlock(&mDNSStorage); |
| } |
| |
| LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG, "Planning to stop the resolver discovery -- " |
| "domain: " PRI_DM_NAME ", grace period: %ds", DM_NAME_PARAM(&discover_resolver->domain), |
| gracePeriodInSeconds); |
| } |
| |
| succeeded = true; |
| break; |
| } |
| |
| // If the entire list is now empty, delete the list. |
| if (discover_resolver_slist_empty(g_discover_resolvers)) { |
| MDNS_DISPOSE_DISCOVER_RESOLVER_SLIST(g_discover_resolvers); |
| } |
| |
| exit: |
| return succeeded; |
| } |
| |
| //====================================================================================================================== |
| |
| mDNSs32 |
| resolver_discovery_get_next_scheduled_event(void) |
| { |
| mDNSs32 next_event = 0; |
| |
| // See if we need to update the registered native DNS service. |
| const mDNSs32 next_update_time = _discover_resolver_get_next_dns_service_update_time(); |
| if ((next_update_time != 0) && ((next_event == 0) || (next_update_time - next_event < 0))) { |
| next_event = next_update_time; |
| } |
| |
| // See if we need to deregister the native DNS service that has not been used for a while. |
| const mDNSs32 next_stop_time = _discover_resolver_get_next_unused_resolver_discovery_stop_time(); |
| if ((next_stop_time != 0) && ((next_event == 0) || (next_stop_time - next_event < 0))) { |
| next_event = next_stop_time; |
| } |
| |
| return next_event; |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| resolver_discovery_perform_periodic_tasks(void) |
| { |
| _discover_resolver_update_dns_service(); |
| _discover_resolver_stop_unused_resolver_discovery(); |
| } |
| |
| //====================================================================================================================== |
| |
| bool |
| dns_question_requires_resolver_discovery(const DNSQuestion * NONNULL q, const domainname ** const out_domain) |
| { |
| if (!q->ForceMCast && !IsRootDomain(Do53_UNICAST_DISCOVERY_DOMAIN) |
| && IsSubdomain(&q->qname, Do53_UNICAST_DISCOVERY_DOMAIN)) { |
| *out_domain = Do53_UNICAST_DISCOVERY_DOMAIN; |
| return true; |
| } else { |
| *out_domain = NULL; |
| return false; |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| discover_resolver_t * NULLABLE |
| discover_resolver_create(const domainname * const NONNULL domain) |
| { |
| discover_resolver_t * returned_discover_resolver = NULL; |
| discover_resolver_context_t * discover_resolver_context = NULL; |
| |
| discover_resolver_t * discover_resolver = mdns_calloc(1, sizeof(*discover_resolver)); |
| require(discover_resolver != NULL, exit); |
| |
| AssignDomainName(&discover_resolver->domain, domain); |
| |
| discover_resolver_context = discover_resolver_context_create(); |
| require(discover_resolver_context != NULL, exit); |
| |
| discover_resolver->context = discover_resolver_context; |
| discover_resolver_context = NULL; |
| |
| discover_resolver->finalizer = discover_resolver_finalize; |
| discover_resolver->ref_count = 1; |
| |
| returned_discover_resolver = discover_resolver; |
| discover_resolver = NULL; |
| exit: |
| MDNS_DISPOSE_DISCOVER_RESOLVER_CONTEXT(discover_resolver_context); |
| MDNS_DISPOSE_DISCOVER_RESOLVER(discover_resolver); |
| return returned_discover_resolver; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| discover_resolver_finalize(discover_resolver_t * const NULLABLE discover_resolver) |
| { |
| if (discover_resolver == NULL) { |
| return; |
| } |
| |
| if (discover_resolver->context != NULL) { |
| discover_resolver_stop(discover_resolver->context); |
| MDNS_DISPOSE_DISCOVER_RESOLVER_CONTEXT(discover_resolver->context); |
| } |
| |
| discover_resolver_t * temp_discover_resolver = discover_resolver; |
| mdns_free(temp_discover_resolver); |
| } |
| |
| //====================================================================================================================== |
| // discover_resolver_context_t |
| |
| static discover_resolver_context_t * |
| discover_resolver_context_create(void) |
| { |
| discover_resolver_context_t * const context = mdns_calloc(1, sizeof(*context)); |
| |
| memset(&context->ns_question, 0, sizeof(context->ns_question)); |
| context->resolver_names = NULL; |
| |
| return context; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| discover_resolver_context_dispose(discover_resolver_context_t * const NULLABLE context) |
| { |
| if (context == NULL) { |
| return; |
| } |
| |
| if (context->resolver_names != NULL) { |
| discover_resolver_name_t * current, * next; |
| for (current = context->resolver_names; current != NULL; current = next) { |
| next = current->next; |
| discover_resolver_name_delete(current); |
| } |
| context->resolver_names = NULL; |
| } |
| discover_resolver_context_t * temp_context = context; |
| mdns_free(temp_context); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| discover_resolver_setup_question(DNSQuestion * const NONNULL q, mDNSInterfaceID interface_id, |
| const domainname * const NONNULL q_name, uint16_t q_type, bool force_multicast, |
| mDNSQuestionCallback * const NULLABLE callback, void * const NONNULL context) |
| { |
| q->InterfaceID = interface_id; |
| q->flags = force_multicast ? kDNSServiceFlagsForceMulticast : 0; |
| AssignDomainName(&q->qname, q_name); |
| q->qtype = q_type; |
| q->qclass = kDNSClass_IN; |
| q->LongLived = false; |
| q->ExpectUnique = false; |
| q->ForceMCast = force_multicast; |
| q->ReturnIntermed = false; |
| q->SuppressUnusable = false; |
| q->AppendSearchDomains = false; |
| q->TimeoutQuestion = 0; |
| q->WakeOnResolve = 0; |
| q->UseBackgroundTraffic = false; |
| q->pid = mDNSPlatformGetPID(); |
| q->euid = 0; |
| q->QuestionCallback = callback; |
| q->QuestionContext = context; |
| } |
| |
| |
| //====================================================================================================================== |
| |
| static void |
| _schedule_dns_service_update(discover_resolver_name_t * const resolver_name) |
| { |
| mDNS * const m = &mDNSStorage; |
| mDNS_Lock(m); |
| const mDNSs32 time_now = m->timenow; |
| mDNS_Unlock(m); |
| |
| // Wait for 0.005s before updating the DNS service. |
| const mDNSs32 update_pending_time = mDNSPlatformOneSecond / 200; |
| resolver_name->next_update_time = NonZeroTime(time_now + update_pending_time); |
| } |
| |
| //====================================================================================================================== |
| |
| MDNS_CLOSED_ENUM(_resolver_dns_service_update_result_t, int8_t, |
| _resolver_dns_service_update_result_error = -1, |
| _resolver_dns_service_update_result_no_change = 0, |
| _resolver_dns_service_update_result_newly_registered = 1, |
| _resolver_dns_service_update_result_updated = 2, |
| _resolver_dns_service_update_result_deregistered = 3 |
| ); |
| |
| static _resolver_dns_service_update_result_t |
| _native_dns_service_update(const domainname * const domain, discover_resolver_name_t * const resolver_name) |
| { |
| (void)domain; |
| (void)resolver_name; |
| return _resolver_dns_service_update_result_no_change; |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| native_dns_service_deregister(discover_resolver_name_t * const NONNULL resolver_name) |
| { |
| (void) resolver_name; |
| return false; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| discover_resolver_addr_query_callback(mDNS * const NONNULL m, DNSQuestion * const NONNULL q, |
| const ResourceRecord * const NONNULL answer, const QC_result change_event) |
| { |
| discover_resolver_context_t * context = q->QuestionContext; |
| discover_resolver_name_t * resolver_name = NULL; |
| const void * const addr_data = answer->rdata->u.data; |
| const mDNSAddr_Type addr_type = answer->rrtype == kDNSType_A ? mDNSAddrType_IPv4 : mDNSAddrType_IPv6; |
| |
| mDNS_Lock(m); |
| char if_name[64]; // The same size as the ((NetworkInterfaceInfo *)0)->ifname). |
| mdns_compile_time_check_local(sizeof(if_name) == sizeof(((NetworkInterfaceInfo *)0)->ifname)); |
| |
| const char * const if_name_ptr = InterfaceNameForID(m, answer->InterfaceID); |
| if (if_name_ptr) { |
| mDNSPlatformStrLCopy(if_name, if_name_ptr, sizeof(if_name)); |
| } else { |
| mDNS_snprintf(if_name, sizeof(if_name), "<ID: %u>", IIDPrintable(answer->InterfaceID)); |
| } |
| mDNS_Unlock(m); |
| |
| bool address_add; |
| mDNSAddr addr_changed; |
| |
| mdns_require_quiet(change_event == QC_add ||change_event == QC_rmv, exit); |
| mdns_require_quiet(answer->rrtype == kDNSType_A || answer->rrtype == kDNSType_AAAA, exit); |
| mdns_require_quiet(q->InterfaceID == answer->InterfaceID, exit); |
| |
| // Find the corresponding discover_resolver_name_t that starts this address query. |
| resolver_name = discover_resolver_name_find(&q->qname, q->InterfaceID, context->resolver_names); |
| mdns_require_quiet(resolver_name, exit); |
| |
| // Try to find if there is existing address in the list. |
| resolver_address_t * resolver_addr = resolver_addresses_find(addr_data, addr_type, resolver_name->addresses); |
| |
| if (change_event == QC_add) { |
| // Should have no duplicate addresses for the QC_add event. |
| mdns_require_quiet(resolver_addr == NULL, exit); |
| |
| // Add the address into list. |
| resolver_addr = resolver_addresses_add(addr_data, addr_type, &resolver_name->addresses); |
| mdns_require_quiet(resolver_addr != NULL, exit); |
| memcpy(&addr_changed, &resolver_addr->addr, sizeof(addr_changed)); |
| |
| address_add = true; |
| } else { |
| // Should be the added address in the list and should not be removed twice. |
| mdns_require_quiet(resolver_addr != NULL, exit); |
| // Should already have configured resolver. |
| |
| memcpy(&addr_changed, &resolver_addr->addr, sizeof(addr_changed)); |
| |
| // Remove the address from the list. |
| resolver_addresses_remove(addr_data, addr_type, &resolver_name->addresses); |
| |
| address_add = false; |
| } |
| |
| // Schedule new DNS configuration update. |
| _schedule_dns_service_update(resolver_name); |
| |
| LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[Q%u] Resolver " PUB_ADD_RMV " - " |
| "browsing domain: " PRI_DM_NAME ", resolver name: " PRI_DM_NAME ", address: " PRI_IP_ADDR |
| ", interface: " PUB_S ".", mDNSVal16(q->TargetQID), ADD_RMV_PARAM(address_add), |
| DM_NAME_PARAM(&context->ns_question.qname), DM_NAME_PARAM(&q->qname), &addr_changed, if_name); |
| |
| exit: |
| return; |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| discover_resolver_start_addr_query(discover_resolver_context_t * const NONNULL context, |
| discover_resolver_name_t * const NONNULL resolver_name, const domainname * const name) |
| { |
| bool succeeded; |
| |
| DNSQuestion * const ipv4 = &resolver_name->ipv4_question; |
| DNSQuestion * const ipv6 = &resolver_name->ipv6_question; |
| |
| // Send address queries as normal |
| discover_resolver_setup_question(ipv4, resolver_name->interface_id, name, kDNSType_A, false, |
| discover_resolver_addr_query_callback, context); |
| discover_resolver_setup_question(ipv6, resolver_name->interface_id, name, kDNSType_AAAA, false, |
| discover_resolver_addr_query_callback, context); |
| |
| // discover_resolver_start_addr_query() is called as a callback, therefore, mDNSCore lock is not held. |
| // mDNS_StartQuery() must be used here instead of mDNS_StartQuery_internal(). |
| mStatus mdns_err = mDNS_StartQuery(&mDNSStorage, ipv4); |
| require_action(mdns_err == mStatus_NoError, exit, succeeded = false); |
| |
| mdns_err = mDNS_StartQuery(&mDNSStorage, ipv6); |
| require_action(mdns_err == mStatus_NoError, exit, succeeded = false); |
| |
| LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "Starting A/AAAA queries for resolver - name: " PRI_DM_NAME, |
| DM_NAME_PARAM(name)); |
| |
| succeeded = true; |
| exit: |
| return succeeded; |
| } |
| |
| static bool |
| discover_resolver_stop_addr_query(discover_resolver_name_t * const NONNULL resolver_name) |
| { |
| bool succeeded = true; |
| |
| // Remove the DNS config added by the resolved addresses. |
| succeeded = native_dns_service_deregister(resolver_name); |
| |
| // Stop the address queries. |
| // discover_resolver_stop_addr_query() is called with lock held, and the two mDNS_StopQuery_internal() below are |
| // used to stop the two queries started by mDNS_StartQuery() in discover_resolver_start_addr_query(). |
| mStatus ipv4_err = mDNS_StopQuery_internal(&mDNSStorage, &resolver_name->ipv4_question); |
| mStatus ipv6_err = mDNS_StopQuery_internal(&mDNSStorage, &resolver_name->ipv6_question); |
| if (ipv4_err != mStatus_NoError || ipv6_err != mStatus_NoError) { |
| succeeded = false; |
| } |
| |
| return succeeded; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| discover_resolver_ns_query_callback(mDNS * const NONNULL UNUSED m, DNSQuestion * const NONNULL q, |
| const ResourceRecord * const NONNULL answer, const QC_result change_event) |
| { |
| bool succeeded; |
| bool new_resolver_name_created = false; |
| discover_resolver_context_t * context = q->QuestionContext; |
| const mDNSInterfaceID if_id = answer->InterfaceID; |
| const char * const if_name = InterfaceNameForID(&mDNSStorage, if_id); |
| |
| require_action(change_event == QC_add || change_event == QC_rmv, exit, succeeded = false); |
| |
| if (if_id == mDNSInterface_LocalOnly) { |
| succeeded = true; |
| goto exit; |
| } |
| |
| // Find out the corresponding discover_resolver_name_t for resolver name, if exists. |
| const domainname * const name = &answer->rdata->u.name; |
| discover_resolver_name_t * resolver_name = discover_resolver_name_find(name, if_id, context->resolver_names); |
| |
| if (change_event == QC_add) { |
| // If there is existing discover_resolver_name_t, do not start duplicate query. |
| if (resolver_name != NULL) { |
| succeeded = true; |
| goto exit; |
| } |
| |
| // Add current name into list. |
| resolver_name = discover_resolver_name_add(name, if_id, &context->resolver_names); |
| require_action(resolver_name != NULL, exit, succeeded = false); |
| new_resolver_name_created = true; |
| |
| LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "Name server found for the browsing domain - " |
| PRI_DM_NAME " -NS-> " PRI_DM_NAME ", interface: " PUB_S, DM_NAME_PARAM(&q->qname), DM_NAME_PARAM(name), |
| if_name ? if_name : "any"); |
| |
| // Start to resolve the resolver name. |
| succeeded = discover_resolver_start_addr_query(context, resolver_name, name); |
| require_action(succeeded, exit, false); |
| } else { // change_event == QC_rmv |
| // Should have the added discover_resolver_name_t |
| require_action(resolver_name != NULL, exit, succeeded = false); |
| |
| LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "Name server disappeared for the browsing domain - " |
| PRI_DM_NAME " -NS-> " PRI_DM_NAME ", interface: " PUB_S, DM_NAME_PARAM(&q->qname), DM_NAME_PARAM(name), |
| if_name ? if_name : "any"); |
| |
| // Stop all the address queries started by this resolver name. |
| // Since discover_resolver_ns_query_callback() is always called as a callback function without mDNS_Lock held, |
| // we need to grab the lock explicitly. |
| mDNS_Lock(&mDNSStorage); |
| discover_resolver_stop_addr_query(resolver_name); |
| mDNS_Unlock(&mDNSStorage); |
| |
| // Remove the name from the list |
| discover_resolver_name_remove(name, if_id, &context->resolver_names); |
| } |
| |
| succeeded = true; |
| exit: |
| if (!succeeded) { |
| if (new_resolver_name_created) { |
| discover_resolver_name_remove(name, if_id, &context->resolver_names); |
| } |
| } |
| return; |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| discover_resolver_start_ns_query(discover_resolver_context_t * const NONNULL context, |
| const domainname * const NONNULL domain) |
| { |
| bool succeeded; |
| |
| // Start NS query with kDNSServiceFlagsForceMulticast. |
| DNSQuestion * const q = &context->ns_question; |
| |
| discover_resolver_setup_question(q, mDNSInterface_Any, domain, kDNSType_NS, true, |
| discover_resolver_ns_query_callback, context); |
| |
| LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "Sending NS query to discover name server - " |
| "browsing domain: " PRI_DM_NAME, DM_NAME_PARAM(domain)); |
| |
| // discover_resolver_start_ns_query() is called with lock held, therefore, mDNS_StartQuery_internal() must be used |
| // here instead of mDNS_StartQuery(). |
| mStatus err = mDNS_StartQuery_internal(&mDNSStorage, q); |
| require_action(err == mStatus_NoError, exit, succeeded = false); |
| |
| succeeded = true; |
| exit: |
| return succeeded; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| discover_resolver_stop_ns_query(discover_resolver_context_t * const NONNULL context) |
| { |
| // Stop all address subqueries. |
| for (discover_resolver_name_t * current = context->resolver_names; current != NULL; current = current->next) { |
| discover_resolver_stop_addr_query(current); |
| } |
| |
| // Stop the original NS query. |
| mDNS_StopQuery_internal(&mDNSStorage, &context->ns_question); |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| discover_resolver_start(discover_resolver_context_t * const NULLABLE context, |
| const domainname * const NONNULL domain) |
| { |
| bool succeeded; |
| require_action(context != NULL, exit, succeeded = false); |
| |
| LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "Starting the resolver discovery for domain - " |
| "domain: " PRI_DM_NAME, DM_NAME_PARAM(domain)); |
| // Start NS query with kDNSServiceFlagsForceMulticast to learn about the browsing domain with mDNS. |
| succeeded = discover_resolver_start_ns_query(context, domain); |
| |
| exit: |
| return succeeded; |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| discover_resolver_stop(discover_resolver_context_t * const NULLABLE context) |
| { |
| if (context == NULL) { |
| return; |
| } |
| discover_resolver_stop_ns_query(context); |
| } |
| |
| //====================================================================================================================== |
| |
| static mDNSs32 |
| _discover_resolver_get_next_dns_service_update_time(void) |
| { |
| mDNSs32 next_time = 0; |
| mdns_require_quiet(g_discover_resolvers, exit); |
| |
| discover_resolver_node_t *np, *np_temp; |
| SLIST_FOREACH_SAFE(np, g_discover_resolvers, __entries, np_temp) { |
| const discover_resolver_t *const discover_resolver = np->discover_resolver; |
| |
| if (discover_resolver == mDNSNULL || discover_resolver->context == mDNSNULL || |
| discover_resolver->context->resolver_names == mDNSNULL) { |
| continue; |
| } |
| |
| const discover_resolver_name_t * const resolver_name = discover_resolver->context->resolver_names; |
| const mDNSs32 next_update_time = resolver_name->next_update_time; |
| if (next_update_time == 0) { |
| continue; |
| } |
| |
| if ((next_time == 0) || ((next_update_time - next_time) < 0)) { |
| next_time = next_update_time; |
| } |
| } |
| |
| exit: |
| return next_time; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _discover_resolver_update_dns_service(void) |
| { |
| const mDNSs32 time_now = mDNSStorage.timenow; |
| mdns_require_return(g_discover_resolvers); |
| |
| discover_resolver_node_t *np, *np_temp; |
| SLIST_FOREACH_SAFE(np, g_discover_resolvers, __entries, np_temp) { |
| discover_resolver_t *const discover_resolver = np->discover_resolver; |
| |
| if (discover_resolver == mDNSNULL || discover_resolver->context == mDNSNULL || |
| discover_resolver->context->resolver_names == mDNSNULL) { |
| continue; |
| } |
| |
| discover_resolver_name_t * const resolver_name = discover_resolver->context->resolver_names; |
| const mDNSs32 next_update_time = resolver_name->next_update_time; |
| if ((next_update_time == 0) || ((next_update_time - time_now) > 0)) { |
| continue; |
| } |
| |
| const _resolver_dns_service_update_result_t err = _native_dns_service_update(&discover_resolver->domain, |
| resolver_name); |
| |
| LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "Discovered local resolver configuration updated" |
| " - name: " PRI_DM_NAME ", result: %d", DM_NAME_PARAM(&discover_resolver->domain), err); |
| |
| resolver_name->next_update_time = 0; |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static mDNSs32 |
| _discover_resolver_get_next_unused_resolver_discovery_stop_time(void) |
| { |
| mDNSs32 next_time = 0; |
| mdns_require_quiet(g_discover_resolvers, exit); |
| |
| // Find the next stop time that is closest to us. |
| discover_resolver_node_t *np, *np_temp; |
| SLIST_FOREACH_SAFE(np, g_discover_resolvers, __entries, np_temp) { |
| const discover_resolver_t *const discover_resolver = np->discover_resolver; |
| if (discover_resolver->use_count != 0) { |
| continue; |
| } |
| |
| const mDNSs32 next_stop_time = discover_resolver->next_stop_time; |
| if (next_stop_time == 0) { |
| continue; |
| } |
| |
| if ((next_time == 0) || (next_time - next_stop_time > 0)) { |
| next_time = next_stop_time; |
| } |
| } |
| |
| exit: |
| return next_time; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _discover_resolver_stop_unused_resolver_discovery(void) |
| { |
| const mDNSs32 time_now = mDNSStorage.timenow; |
| mdns_require_return(g_discover_resolvers); |
| |
| discover_resolver_node_t *np, *np_temp; |
| SLIST_FOREACH_SAFE(np, g_discover_resolvers, __entries, np_temp) { |
| if (np->discover_resolver->use_count != 0) { |
| continue; |
| } |
| |
| const mDNSs32 next_stop_time = np->discover_resolver->next_stop_time; |
| if ((next_stop_time == 0) || (time_now - next_stop_time < 0)) { |
| continue; |
| } |
| |
| // Now (next_stop_time <= time_now) indicates that we have passed the next_stop_time. |
| // Therefore, it is time to cancel the resolver discovery. |
| LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "Stopping the resolver discovery -- " |
| "domain: " PRI_DM_NAME, DM_NAME_PARAM(&np->discover_resolver->domain)); |
| |
| discover_resolver_forget(&np->discover_resolver); |
| SLIST_REMOVE(g_discover_resolvers, np, discover_resolver_node, __entries); |
| mdns_free(np); |
| } |
| } |
| |
| #else // MDNSRESPONDER_SUPPORTS(COMMON, LOCAL_DNS_RESOLVER_DISCOVERY) |
| |
| // iso C requires a translation unit to contain at least one declaration |
| typedef int __make_iso_c_happy_about_no_declaration; |
| |
| #endif // MDNSRESPONDER_SUPPORTS(COMMON, LOCAL_DNS_RESOLVER_DISCOVERY) |