| /* |
| * Copyright (c) 2019-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. |
| */ |
| |
| #include "dnssd_private.h" |
| |
| #include "dnssd_object.h" |
| #include "dnssd_xpc.h" |
| #include "dnssd_svcb.h" |
| |
| #include <CoreUtils/CoreUtils.h> |
| #include <os/object_private.h> |
| #include <xpc/private.h> |
| #include "mdns_strict.h" |
| |
| //====================================================================================================================== |
| // MARK: - Kind Declarations |
| |
| #define DNSSD_STRUCT(NAME) struct dnssd_ ## NAME ## _s |
| #define DNSSD_TYPE(NAME) dnssd_ ## NAME ## _t |
| |
| #define DNSSD_KIND_DECLARE(NAME) \ |
| static DNSSD_TYPE(NAME) \ |
| _dnssd_ ## NAME ## _alloc(void); \ |
| \ |
| static char * \ |
| _dnssd_ ## NAME ## _copy_description(DNSSD_TYPE(NAME) object, bool debug, bool privacy); \ |
| \ |
| static void \ |
| _dnssd_ ## NAME ## _finalize(DNSSD_TYPE(NAME) object) |
| |
| // Note: The last check checks if the base's type is equal to that of the superkind. If it's not, then the pointer |
| // comparison used as the argument to sizeof will cause a "comparison of distinct pointer types" warning, so long as |
| // the warning hasn't been disabled. |
| |
| #define DNSSD_BASE_CHECK(NAME, SUPER) \ |
| check_compile_time(offsetof(DNSSD_STRUCT(NAME), base) == 0); \ |
| check_compile_time(sizeof_field(DNSSD_STRUCT(NAME), base) == sizeof(DNSSD_STRUCT(SUPER))); \ |
| extern int _dnssd_base_type_check[sizeof(&(((DNSSD_TYPE(NAME))0)->base) == ((DNSSD_TYPE(SUPER))0))] |
| |
| #define DNSSD_KIND_DEFINE(NAME, SUPER) \ |
| static const struct dnssd_kind_s _dnssd_ ## NAME ## _kind = { \ |
| MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN() \ |
| &_dnssd_ ## SUPER ## _kind, \ |
| # NAME, \ |
| _dnssd_ ## NAME ## _copy_description, \ |
| _dnssd_ ## NAME ## _finalize, \ |
| MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_END() \ |
| }; \ |
| \ |
| static DNSSD_TYPE(NAME) \ |
| _dnssd_ ## NAME ## _alloc(void) \ |
| { \ |
| DNSSD_TYPE(NAME) obj = dnssd_object_ ## NAME ## _alloc(sizeof(*obj)); \ |
| require_quiet(obj, exit); \ |
| \ |
| const dnssd_object_t base = (dnssd_object_t)obj; \ |
| base->kind = &_dnssd_ ## NAME ## _kind; \ |
| \ |
| exit: \ |
| return obj; \ |
| } \ |
| DNSSD_BASE_CHECK(NAME, SUPER) |
| |
| DNSSD_KIND_DECLARE(getaddrinfo); |
| DNSSD_KIND_DECLARE(getaddrinfo_result); |
| DNSSD_KIND_DECLARE(cname_array); |
| |
| typedef char * (*dnssd_copy_description_f)(dnssd_any_t object, bool debug, bool privacy); |
| typedef void (*dnssd_finalize_f)(dnssd_any_t object); |
| |
| typedef const struct dnssd_kind_s * dnssd_kind_t; |
| struct dnssd_kind_s { |
| dnssd_kind_t superkind; // This kind's superkind. |
| const char * name; // Name of this kind. |
| dnssd_copy_description_f copy_description; // Creates a textual description of object. |
| dnssd_finalize_f finalize; // Releases object's resources right before the object is freed. |
| }; |
| |
| //====================================================================================================================== |
| // MARK: - Object Kind Definition |
| |
| struct dnssd_object_s { |
| _OS_OBJECT_HEADER(const void * __ptrauth_objc_isa_pointer _os_obj_isa, _os_obj_refcnt, _os_obj_xref_cnt); |
| dnssd_kind_t kind; // Pointer to an object's kind. |
| }; |
| |
| static const struct dnssd_kind_s _dnssd_object_kind = { |
| NULL, // No superkind. |
| "object", |
| NULL, // No copy_description method. |
| NULL, // No finalize method. |
| }; |
| |
| //====================================================================================================================== |
| // MARK: - GetAddrInfo Kind Definition |
| |
| typedef enum { |
| dnssd_getaddrinfo_state_nascent = 0, |
| dnssd_getaddrinfo_state_starting = 1, |
| dnssd_getaddrinfo_state_started = 2, |
| dnssd_getaddrinfo_state_failed = 3, |
| dnssd_getaddrinfo_state_invalidated = 4, |
| } dnssd_getaddrinfo_state_t; |
| |
| struct dnssd_getaddrinfo_s { |
| struct dnssd_object_s base; // Object base. |
| dnssd_getaddrinfo_t next; // Next getaddrinfo object in list. |
| uint64_t command_id; // Command ID. |
| dispatch_queue_t user_queue; // User's dispatch queue for result and event handlers. |
| dispatch_queue_t mutex_queue; // Mutex for accessing result_list from different queues. |
| xpc_object_t params; // Parameters dictionary for getaddrinfo command. |
| xpc_object_t hostname; // Reference to hostname from parameters dictionary. |
| dnssd_cname_array_t cnames_a; // Array of hostname's canonical names for A results. |
| dnssd_cname_array_t cnames_aaaa; // Array of hostname's canonical names for AAAA results. |
| dispatch_source_t event_source; // Data source for triggering result and event handlers. |
| dnssd_getaddrinfo_result_t result_list; // List of getaddrinfo results. |
| dnssd_getaddrinfo_result_handler_t result_handler; // User's result handler. |
| dnssd_event_handler_t event_handler; // User's event handler. |
| mdns_xpc_string_t tracker_hostname; // The last result's tracker hostname. Used for deduping. |
| mdns_xpc_string_t tracker_owner; // The last result's tracker owner. Used for deduping. |
| dnssd_getaddrinfo_state_t state; // Internal state. |
| OSStatus error; // Pending error. |
| bool user_activated; // True if the object has been activated by user. |
| }; |
| |
| DNSSD_KIND_DEFINE(getaddrinfo, object); |
| |
| //====================================================================================================================== |
| // MARK: - GetAddrInfo Result Kind Definition |
| |
| struct dnssd_getaddrinfo_result_s { |
| struct dnssd_object_s base; // Object base. |
| dnssd_getaddrinfo_result_t next; // Next getaddrinfo result in list. |
| sockaddr_ip addr; // IPv4 or IPv6 address of hostname. |
| xpc_object_t hostname; // Requested hostname to resolve. |
| xpc_object_t actual_hostname; // The actual/canonical hostname of the requested hostname. |
| dnssd_cname_array_t cnames; // Array of hostname's canonical names. |
| xpc_object_t provider_name; // Provider name. |
| xpc_object_t ech_config; // SVCB ECH config. |
| xpc_object_t address_hints; // SVCB address hints. |
| xpc_object_t doh_uri; // SVCB DoH URI. |
| xpc_object_t doh_path; // SVCB DoH Path. |
| xpc_object_t odoh_config; // SVCB Oblivious DoH config. |
| xpc_object_t alpn_values; // SVCB ALPN values. |
| xpc_object_t service_name; // SVCB name. |
| mdns_xpc_string_t tracker_hostname; // Tracker hostname [1]. |
| mdns_xpc_string_t tracker_owner; // Tracker owner [2]. |
| xpc_object_t validation_data; // Validation data. |
| mdns_xpc_string_t ede_text; // Extended DNS Error extra text for negative results. |
| uint16_t port; // SVCB port. |
| uint16_t priority; // SVCB priority. |
| uint32_t if_index; // Interface index to which the result pertains. |
| dnssd_getaddrinfo_result_type_t type; // Type of getaddrinfo result. |
| dnssd_getaddrinfo_result_protocol_t protocol; // Protocol used for getaddrinfo result. |
| dnssd_negative_reason_t negative_reason; // The reason code for a negative result. |
| uint16_t ede_code; // Extended DNS Error code for negative results. |
| bool is_from_cache; // True if the result was an answer from the cache. |
| bool valid_svcb; // True if SVCB info is valid. |
| bool tracker_approved; // True if the tracker is an approved domain for the app. |
| bool tracker_can_block; // True if a request to this known tracker can be blocked. |
| bool ede_valid; // True if Extended DNS Error is valid for negative result. |
| }; |
| |
| // Notes: |
| // 1. If non-NULL, identifies the known tracker domain name, if any, that appears earliest in the requested hostname's |
| // CNAME chain. |
| // 2. If non-NULL, identifies the entity (usually a company) that owns the tracker hostname. |
| |
| DNSSD_KIND_DEFINE(getaddrinfo_result, object); |
| |
| //====================================================================================================================== |
| // MARK: - CName Array Kind Definition |
| |
| struct dnssd_cname_array_s { |
| struct dnssd_object_s base; // Object base. |
| xpc_object_t xpc_array; // Underlying array of cnames as strings. Important: Must not be modified. |
| }; |
| |
| DNSSD_KIND_DEFINE(cname_array, object); |
| |
| //====================================================================================================================== |
| // MARK: - Constants |
| |
| #define DNSSD_EVENT_HAVE_RESULTS (1U << 0) // Results are available. |
| #define DNSSD_EVENT_REMOVE_ALL (1U << 1) // Previously delivered results are no longer valid. |
| #define DNSSD_EVENT_ERROR (1U << 2) // An error was encountered. |
| |
| // Strings for redacted description items. |
| |
| #define DNSSD_REDACTED_HOSTNAME_STR "<redacted hostname>" |
| #define DNSSD_REDACTED_IPv4_ADDRESS_STR "<redacted IPv4 address>" |
| #define DNSSD_REDACTED_IPv6_ADDRESS_STR "<redacted IPv6 address>" |
| |
| //====================================================================================================================== |
| // MARK: - Local Prototypes |
| |
| static dispatch_queue_t |
| _dnssd_client_queue(void); |
| |
| static xpc_connection_t |
| _dnssd_client_connection(void); |
| |
| static uint64_t |
| _dnssd_client_get_new_id(void); |
| |
| static void |
| _dnssd_client_activate_getaddrinfo_async(dnssd_getaddrinfo_t gai); |
| |
| static void |
| _dnssd_client_register_getaddrinfo(dnssd_getaddrinfo_t gai); |
| |
| static void |
| _dnssd_client_deregister_getaddrinfo(dnssd_getaddrinfo_t gai); |
| |
| static OSStatus |
| _dnssd_client_send_getaddrinfo_command(dnssd_getaddrinfo_t gai); |
| |
| static void |
| _dnssd_client_fail_getaddrinfo(dnssd_getaddrinfo_t gai, OSStatus error); |
| |
| static void |
| _dnssd_getaddrinfo_append_results(dnssd_getaddrinfo_t gai, dnssd_getaddrinfo_result_t result_list); |
| |
| static void |
| _dnssd_getaddrinfo_remove_all_results(dnssd_getaddrinfo_t gai); |
| |
| static dnssd_getaddrinfo_result_t |
| _dnssd_getaddrinfo_take_results(dnssd_getaddrinfo_t gai); |
| |
| static void |
| _dnssd_getaddrinfo_post_error_event(dnssd_getaddrinfo_t gai, OSStatus error); |
| |
| static dnssd_getaddrinfo_result_t |
| _dnssd_getaddrinfo_create_result_from_dictionary(dnssd_getaddrinfo_t gai, xpc_object_t result_dict, |
| OSStatus *out_error); |
| |
| static dnssd_cname_array_t |
| _dnssd_cname_array_create(xpc_object_t xpc_array, OSStatus *out_error); |
| |
| static dnssd_cname_array_t |
| _dnssd_get_empty_cname_array(void); |
| |
| static DNSServiceErrorType |
| _dnssd_osstatus_to_dns_service_error(OSStatus status); |
| |
| static int |
| _dnssd_snprintf(char **dst, const char *end, const char *format, ...); |
| |
| #if !defined(dnssd_release_null_safe) |
| #define dnssd_release_null_safe(X) \ |
| do { \ |
| if (X) { \ |
| dnssd_release(X); \ |
| } \ |
| } while(0) |
| #endif |
| |
| #if !defined(dnssd_forget) |
| #define dnssd_forget(X) ForgetCustom(X, dnssd_release) |
| #endif |
| |
| //====================================================================================================================== |
| // MARK: - Object Public Methods |
| |
| void |
| dnssd_retain(const dnssd_any_t any) |
| { |
| os_retain(any.object); |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_release(const dnssd_any_t any) |
| { |
| os_release(any.object); |
| } |
| |
| //====================================================================================================================== |
| |
| char * |
| dnssd_copy_description(dnssd_any_t object) |
| { |
| return dnssd_object_copy_description(object, false, false); |
| } |
| |
| //====================================================================================================================== |
| // MARK: - Object Private Methods |
| |
| char * |
| dnssd_object_copy_description(const dnssd_any_t any, const bool debug, const bool privacy) |
| { |
| const dnssd_object_t me = any.object; |
| for (dnssd_kind_t kind = me->kind; kind; kind = kind->superkind) { |
| if (kind->copy_description) { |
| char *desc = kind->copy_description(me, debug, privacy); |
| return desc; |
| } |
| } |
| return NULL; |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_object_finalize(const dnssd_any_t any) |
| { |
| const dnssd_object_t me = any.object; |
| for (dnssd_kind_t kind = me->kind; kind; kind = kind->superkind) { |
| if (kind->finalize) { |
| kind->finalize(me); |
| } |
| } |
| } |
| |
| //====================================================================================================================== |
| // MARK: - GetAddrInfo Public Methods |
| |
| dnssd_getaddrinfo_t |
| dnssd_getaddrinfo_create(void) |
| { |
| dnssd_getaddrinfo_t gai = NULL; |
| dnssd_getaddrinfo_t obj = _dnssd_getaddrinfo_alloc(); |
| require_quiet(obj, exit); |
| |
| obj->params = xpc_dictionary_create(NULL, NULL, 0); |
| require_quiet(obj->params, exit); |
| |
| obj->mutex_queue = dispatch_queue_create("com.apple.dnssd.getaddrinfo.mutex", DISPATCH_QUEUE_SERIAL); |
| require_quiet(obj->mutex_queue, exit); |
| |
| gai = obj; |
| obj = NULL; |
| |
| exit: |
| dnssd_release_null_safe(obj); |
| return gai; |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_set_queue(dnssd_getaddrinfo_t me, dispatch_queue_t queue) |
| { |
| if (!me->user_activated) { |
| dispatch_retain(queue); |
| dispatch_release_null_safe(me->user_queue); |
| me->user_queue = queue; |
| } else if (!me->user_queue) { |
| me->user_queue = queue; |
| dispatch_retain(me->user_queue); |
| _dnssd_client_activate_getaddrinfo_async(me); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_set_flags(dnssd_getaddrinfo_t me, DNSServiceFlags flags) |
| { |
| if (!me->user_activated) { |
| dnssd_xpc_parameters_set_flags(me->params, flags); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_set_account_id(dnssd_getaddrinfo_t me, const char * account_id) |
| { |
| if (!me->user_activated) { |
| dnssd_xpc_parameters_set_account_id(me->params, account_id); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_set_hostname(dnssd_getaddrinfo_t me, const char *hostname) |
| { |
| if (!me->user_activated) { |
| dnssd_xpc_parameters_set_hostname(me->params, hostname); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_set_interface_index(dnssd_getaddrinfo_t me, uint32_t interface_index) |
| { |
| if (!me->user_activated) { |
| dnssd_xpc_parameters_set_interface_index(me->params, interface_index); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_set_protocols(dnssd_getaddrinfo_t me, DNSServiceProtocol protocols) |
| { |
| if (!me->user_activated) { |
| dnssd_xpc_parameters_set_protocols(me->params, protocols); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_set_service_scheme(dnssd_getaddrinfo_t me, const char *service_scheme) |
| { |
| if (!me->user_activated) { |
| dnssd_xpc_parameters_set_service_scheme(me->params, service_scheme); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_set_delegate_pid(dnssd_getaddrinfo_t me, pid_t pid) |
| { |
| if (!me->user_activated) { |
| dnssd_xpc_parameters_set_delegate_pid(me->params, pid); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_set_delegate_uuid(dnssd_getaddrinfo_t me, uuid_t uuid) |
| { |
| if (!me->user_activated) { |
| dnssd_xpc_parameters_set_delegate_uuid(me->params, uuid); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_set_delegate_audit_token(dnssd_getaddrinfo_t me, audit_token_t audit_token) |
| { |
| if (!me->user_activated) { |
| dnssd_xpc_parameters_set_delegate_audit_token(me->params, &audit_token); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_set_result_handler(dnssd_getaddrinfo_t me, dnssd_getaddrinfo_result_handler_t handler) |
| { |
| dnssd_getaddrinfo_result_handler_t const new_handler = handler ? Block_copy(handler) : NULL; |
| BlockForget(&me->result_handler); |
| me->result_handler = new_handler; |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_set_event_handler(dnssd_getaddrinfo_t me, dnssd_event_handler_t handler) |
| { |
| dnssd_event_handler_t const new_handler = handler ? Block_copy(handler) : NULL; |
| BlockForget(&me->event_handler); |
| me->event_handler = new_handler; |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_set_need_encrypted_query(dnssd_getaddrinfo_t me, bool need, _Nullable xpc_object_t fallback_config) |
| { |
| if (!me->user_activated) { |
| dnssd_xpc_parameters_set_need_encrypted_query(me->params, need, fallback_config); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_set_use_failover(const dnssd_getaddrinfo_t me, const bool use_failover) |
| { |
| require_return(!me->user_activated); |
| dnssd_xpc_parameters_set_use_failover(me->params, use_failover); |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_set_log_privacy_level(const dnssd_getaddrinfo_t me, const dnssd_log_privacy_level_t level) |
| { |
| require_return(!me->user_activated); |
| dnssd_xpc_parameters_set_log_privacy_level(me->params, level); |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_add_resolver_uuid(dnssd_getaddrinfo_t me, uuid_t _Nonnull uuid) |
| { |
| if (!me->user_activated) { |
| dnssd_xpc_parameters_add_resolver_uuid(me->params, uuid); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_set_validation_data(const dnssd_getaddrinfo_t me, const uint8_t * const data_ptr, |
| const size_t data_len) |
| { |
| require_return(!me->user_activated); |
| dnssd_xpc_parameters_set_validation_data(me->params, data_ptr, data_len); |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_activate(dnssd_getaddrinfo_t me) |
| { |
| if (!me->user_activated) { |
| if (me->user_queue) { |
| _dnssd_client_activate_getaddrinfo_async(me); |
| } |
| me->user_activated = true; |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dnssd_client_invalidate_getaddrinfo(dnssd_getaddrinfo_t gai); |
| |
| static void |
| _dnssd_getaddrinfo_invalidate(dnssd_getaddrinfo_t me); |
| |
| void |
| dnssd_getaddrinfo_invalidate(dnssd_getaddrinfo_t me) |
| { |
| dnssd_retain(me); |
| dispatch_async(_dnssd_client_queue(), |
| ^{ |
| _dnssd_client_invalidate_getaddrinfo(me); |
| dnssd_release(me); |
| }); |
| } |
| |
| static void |
| _dnssd_client_invalidate_getaddrinfo(dnssd_getaddrinfo_t gai) |
| { |
| require_quiet(gai->state != dnssd_getaddrinfo_state_invalidated, exit); |
| |
| _dnssd_client_deregister_getaddrinfo(gai); |
| if ((gai->state == dnssd_getaddrinfo_state_starting) || (gai->state == dnssd_getaddrinfo_state_started)) { |
| xpc_object_t msg = xpc_dictionary_create(NULL, NULL, 0); |
| if (msg) { |
| dnssd_xpc_message_set_id(msg, gai->command_id); |
| dnssd_xpc_message_set_command(msg, DNSSD_COMMAND_STOP); |
| xpc_connection_send_message_with_reply(_dnssd_client_connection(), msg, _dnssd_client_queue(), |
| ^(xpc_object_t reply) |
| { |
| (void)reply; |
| }); |
| xpc_forget(&msg); |
| } |
| } |
| _dnssd_getaddrinfo_invalidate(gai); |
| gai->state = dnssd_getaddrinfo_state_invalidated; |
| |
| exit: |
| return; |
| } |
| |
| static void |
| _dnssd_getaddrinfo_invalidate(dnssd_getaddrinfo_t me) |
| { |
| dispatch_source_forget(&me->event_source); |
| _dnssd_getaddrinfo_remove_all_results(me); |
| |
| if (me->user_queue) { |
| dnssd_retain(me); |
| dispatch_async(me->user_queue, |
| ^{ |
| if (me->event_handler) { |
| me->event_handler(dnssd_event_invalidated, kDNSServiceErr_NoError); |
| } |
| dnssd_release(me); |
| }); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_prohibit_encrypted_dns(const dnssd_getaddrinfo_t me, const bool prohibit) |
| { |
| require_return(!me->user_activated); |
| dnssd_xpc_parameters_set_prohibit_encrypted_dns(me->params, prohibit); |
| } |
| |
| //====================================================================================================================== |
| // MARK: - GetAddrInfo Private Methods |
| |
| static char * |
| _dnssd_getaddrinfo_copy_description(dnssd_getaddrinfo_t me, const bool debug, const bool privacy) |
| { |
| const char *hostname_str; |
| if (me->hostname) { |
| hostname_str = xpc_string_get_string_ptr(me->hostname); |
| if (privacy && hostname_str) { |
| hostname_str = DNSSD_REDACTED_HOSTNAME_STR; |
| } |
| } else { |
| hostname_str = NULL; |
| } |
| char *desc = NULL; |
| char *buf_ptr = NULL; |
| size_t buf_len = 0; |
| for (;;) |
| { |
| int n; |
| char * dst = buf_ptr; |
| char * const end = &buf_ptr[buf_len]; |
| size_t desc_len = 0; |
| if (debug) { |
| n = _dnssd_snprintf(&dst, end, "dnssd_%s (%p): ", me->base.kind->name, (void *)me); |
| require_quiet(n >= 0, exit); |
| desc_len += (size_t)n; |
| } |
| n = _dnssd_snprintf(&dst, end, "hostname: %s", hostname_str ? hostname_str : "<NO HOSTNAME>"); |
| require_quiet(n >= 0, exit); |
| desc_len += (size_t)n; |
| |
| if (!buf_ptr) { |
| buf_len = desc_len + 1; |
| buf_ptr = (char *)mdns_malloc(buf_len); |
| require_quiet(buf_ptr, exit); |
| buf_ptr[0] = '\0'; |
| } else { |
| break; |
| } |
| } |
| desc = buf_ptr; |
| buf_ptr = NULL; |
| |
| exit: |
| FreeNullSafe(buf_ptr); |
| return desc; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dnssd_getaddrinfo_finalize(dnssd_getaddrinfo_t me) |
| { |
| dispatch_forget(&me->user_queue); |
| dispatch_forget(&me->mutex_queue); |
| xpc_forget(&me->params); |
| xpc_forget(&me->hostname); |
| dnssd_forget(&me->cnames_a); |
| dnssd_forget(&me->cnames_aaaa); |
| BlockForget(&me->result_handler); |
| BlockForget(&me->event_handler); |
| mdns_xpc_string_forget(&me->tracker_hostname); |
| mdns_xpc_string_forget(&me->tracker_owner); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dnssd_getaddrinfo_append_results(dnssd_getaddrinfo_t me, dnssd_getaddrinfo_result_t result_list) |
| { |
| dispatch_sync(me->mutex_queue, |
| ^{ |
| dnssd_getaddrinfo_result_t *ptr = &me->result_list; |
| while (*ptr) { |
| ptr = &(*ptr)->next; |
| } |
| *ptr = result_list; |
| }); |
| dispatch_source_merge_data(me->event_source, DNSSD_EVENT_HAVE_RESULTS); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dnssd_getaddrinfo_remove_all_results(dnssd_getaddrinfo_t me) |
| { |
| dnssd_forget(&me->cnames_a); |
| dnssd_forget(&me->cnames_aaaa); |
| dnssd_getaddrinfo_result_t result_list = _dnssd_getaddrinfo_take_results(me); |
| if (me->event_source) { |
| dispatch_source_merge_data(me->event_source, DNSSD_EVENT_REMOVE_ALL); |
| } |
| |
| dnssd_getaddrinfo_result_t result; |
| while ((result = result_list) != NULL) { |
| result_list = result->next; |
| dnssd_release(result); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static dnssd_getaddrinfo_result_t |
| _dnssd_getaddrinfo_take_results(dnssd_getaddrinfo_t me) |
| { |
| __block dnssd_getaddrinfo_result_t list; |
| dispatch_sync(me->mutex_queue, |
| ^{ |
| list = me->result_list; |
| me->result_list = NULL; |
| }); |
| return list; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dnssd_getaddrinfo_post_error_event(dnssd_getaddrinfo_t me, OSStatus error) |
| { |
| dispatch_sync(me->mutex_queue, |
| ^{ |
| me->error = error; |
| }); |
| dispatch_source_merge_data(me->event_source, DNSSD_EVENT_ERROR); |
| } |
| |
| //====================================================================================================================== |
| // MARK: - GetAddrInfo Result Public Methods |
| |
| dnssd_getaddrinfo_result_type_t |
| dnssd_getaddrinfo_result_get_type(dnssd_getaddrinfo_result_t me) |
| { |
| return me->type; |
| } |
| |
| //====================================================================================================================== |
| |
| const char * |
| dnssd_getaddrinfo_result_get_actual_hostname(dnssd_getaddrinfo_result_t me) |
| { |
| const char * const tmp = xpc_string_get_string_ptr(me->actual_hostname); |
| return tmp; |
| } |
| |
| //====================================================================================================================== |
| |
| const struct sockaddr * |
| dnssd_getaddrinfo_result_get_address(dnssd_getaddrinfo_result_t me) |
| { |
| return &me->addr.sa; |
| } |
| |
| //====================================================================================================================== |
| |
| const char * |
| dnssd_getaddrinfo_result_get_hostname(dnssd_getaddrinfo_result_t me) |
| { |
| const char * const tmp = xpc_string_get_string_ptr(me->hostname); |
| return tmp; |
| } |
| |
| //====================================================================================================================== |
| |
| const char * |
| dnssd_getaddrinfo_result_get_doh_uri(dnssd_getaddrinfo_result_t me) |
| { |
| return xpc_string_get_string_ptr(me->doh_uri); |
| } |
| |
| //====================================================================================================================== |
| |
| const char * |
| dnssd_getaddrinfo_result_get_doh_path(dnssd_getaddrinfo_result_t me) |
| { |
| return xpc_string_get_string_ptr(me->doh_path); |
| } |
| |
| //====================================================================================================================== |
| |
| uint16_t |
| dnssd_getaddrinfo_result_get_service_port(dnssd_getaddrinfo_result_t me) |
| { |
| return me->port; |
| } |
| |
| //====================================================================================================================== |
| |
| uint16_t |
| dnssd_getaddrinfo_result_get_service_priority(dnssd_getaddrinfo_result_t me) |
| { |
| return me->priority; |
| } |
| |
| //====================================================================================================================== |
| |
| const char * |
| dnssd_getaddrinfo_result_get_service_name(dnssd_getaddrinfo_result_t me) |
| { |
| return xpc_string_get_string_ptr(me->service_name); |
| } |
| |
| //====================================================================================================================== |
| |
| bool |
| dnssd_getaddrinfo_result_service_is_valid(dnssd_getaddrinfo_result_t me) |
| { |
| return me->valid_svcb; |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_result_enumerate_alpn_values(dnssd_getaddrinfo_result_t me, |
| DNSSD_NOESCAPE dnssd_getaddrinfo_enumerate_alpn_values_block_t enumerator) |
| { |
| if (me->alpn_values != NULL) { |
| xpc_array_apply(me->alpn_values, ^bool(__unused size_t index, xpc_object_t _Nonnull value) { |
| const char *string = xpc_string_get_string_ptr(value); |
| return enumerator(string); |
| }); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| dnssd_getaddrinfo_result_enumerate_service_address_hints(dnssd_getaddrinfo_result_t me, |
| DNSSD_NOESCAPE dnssd_getaddrinfo_enumerate_addresses_block_t enumerator) |
| { |
| if (me->address_hints != NULL) { |
| xpc_array_apply(me->address_hints, ^bool(__unused size_t index, xpc_object_t _Nonnull value) { |
| const void *bytes = xpc_data_get_bytes_ptr(value); |
| return enumerator((const struct sockaddr *)bytes); |
| }); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| const void * |
| dnssd_getaddrinfo_result_get_ech_config(dnssd_getaddrinfo_result_t me, size_t *out_length) |
| { |
| const void * ech_ptr; |
| size_t ech_len; |
| |
| if (me->ech_config) { |
| ech_ptr = xpc_data_get_bytes_ptr(me->ech_config); |
| ech_len = xpc_data_get_length(me->ech_config); |
| } else { |
| ech_ptr = NULL; |
| ech_len = 0; |
| } |
| if (out_length) { |
| *out_length = ech_len; |
| } |
| return ech_ptr; |
| } |
| |
| //====================================================================================================================== |
| |
| const void * |
| dnssd_getaddrinfo_result_get_odoh_config(dnssd_getaddrinfo_result_t me, size_t *out_length) |
| { |
| const void * odoh_ptr; |
| size_t odoh_len; |
| |
| if (me->odoh_config) { |
| odoh_ptr = xpc_data_get_bytes_ptr(me->odoh_config); |
| odoh_len = xpc_data_get_length(me->odoh_config); |
| } else { |
| odoh_ptr = NULL; |
| odoh_len = 0; |
| } |
| if (out_length) { |
| *out_length = odoh_len; |
| } |
| return odoh_ptr; |
| } |
| |
| //====================================================================================================================== |
| |
| uint32_t |
| dnssd_getaddrinfo_result_get_interface_index(dnssd_getaddrinfo_result_t me) |
| { |
| return me->if_index; |
| } |
| |
| //====================================================================================================================== |
| |
| dnssd_getaddrinfo_result_protocol_t |
| dnssd_getaddrinfo_result_get_protocol(dnssd_getaddrinfo_result_t me) |
| { |
| return me->protocol; |
| } |
| |
| //====================================================================================================================== |
| |
| const char * |
| dnssd_getaddrinfo_result_get_provider_name(dnssd_getaddrinfo_result_t me) |
| { |
| return xpc_string_get_string_ptr(me->provider_name); |
| } |
| |
| //====================================================================================================================== |
| |
| dnssd_cname_array_t |
| dnssd_getaddrinfo_result_get_cnames(const dnssd_getaddrinfo_result_t me) |
| { |
| return (me->cnames ? me->cnames : _dnssd_get_empty_cname_array()); |
| } |
| |
| //====================================================================================================================== |
| |
| bool |
| dnssd_getaddrinfo_result_is_from_cache(const dnssd_getaddrinfo_result_t me) |
| { |
| return me->is_from_cache; |
| } |
| |
| //====================================================================================================================== |
| |
| const char * |
| dnssd_getaddrinfo_result_get_tracker_hostname(const dnssd_getaddrinfo_result_t me) |
| { |
| return mdns_xpc_string_get_string_ptr(me->tracker_hostname); |
| } |
| |
| //====================================================================================================================== |
| |
| const char * |
| dnssd_getaddrinfo_result_get_tracker_owner(const dnssd_getaddrinfo_result_t me) |
| { |
| return mdns_xpc_string_get_string_ptr(me->tracker_owner); |
| } |
| |
| //====================================================================================================================== |
| |
| bool |
| dnssd_getaddrinfo_result_get_tracker_is_approved(const dnssd_getaddrinfo_result_t me) |
| { |
| return me->tracker_approved; |
| } |
| |
| //====================================================================================================================== |
| |
| bool |
| dnssd_getaddrinfo_result_get_tracker_can_block_request(const dnssd_getaddrinfo_result_t me) |
| { |
| return me->tracker_can_block; |
| } |
| |
| //====================================================================================================================== |
| |
| dnssd_negative_reason_t |
| dnssd_getaddrinfo_result_get_negative_reason(const dnssd_getaddrinfo_result_t me) |
| { |
| return me->negative_reason; |
| } |
| |
| //====================================================================================================================== |
| |
| const uint8_t * |
| dnssd_getaddrinfo_result_get_validation_data(const dnssd_getaddrinfo_result_t me, size_t * const out_length) |
| { |
| const uint8_t *data_ptr; |
| size_t data_len; |
| if (me->validation_data) { |
| data_ptr = xpc_data_get_bytes_ptr(me->validation_data); |
| data_len = xpc_data_get_length(me->validation_data); |
| } else { |
| data_ptr = NULL; |
| data_len = 0; |
| } |
| if (out_length) { |
| *out_length = data_len; |
| } |
| return data_ptr; |
| } |
| |
| //====================================================================================================================== |
| |
| bool |
| dnssd_getaddrinfo_result_has_extended_dns_error(const dnssd_getaddrinfo_result_t me) |
| { |
| return me->ede_valid; |
| } |
| |
| //====================================================================================================================== |
| |
| uint16_t |
| dnssd_getaddrinfo_result_get_extended_dns_error_code(const dnssd_getaddrinfo_result_t me) |
| { |
| return me->ede_code; |
| } |
| |
| //====================================================================================================================== |
| |
| const char * |
| dnssd_getaddrinfo_result_get_extended_dns_error_text(const dnssd_getaddrinfo_result_t me) |
| { |
| const char *text = mdns_xpc_string_get_string_ptr(me->ede_text); |
| return (text ? text : ""); |
| } |
| |
| //====================================================================================================================== |
| // MARK: - GetAddrInfo Result Private Methods |
| |
| static char * |
| _dnssd_getaddrinfo_result_copy_description(dnssd_getaddrinfo_result_t me, const bool debug, const bool privacy) |
| { |
| const char *hostname; |
| if (me->hostname) { |
| hostname = xpc_string_get_string_ptr(me->hostname); |
| if (privacy && hostname) { |
| hostname = DNSSD_REDACTED_HOSTNAME_STR; |
| } |
| } else { |
| hostname = NULL; |
| } |
| char addr_buf[INET6_ADDRSTRLEN + 1 + Max(IF_NAMESIZE, 10) + 1]; |
| const char *addr_str; |
| if (me->addr.sa.sa_family == AF_INET) { |
| if (privacy) { |
| addr_str = DNSSD_REDACTED_IPv4_ADDRESS_STR; |
| } else { |
| mdns_compile_time_check_local(sizeof(addr_buf) >= INET_ADDRSTRLEN); |
| addr_str = inet_ntop(AF_INET, &me->addr.v4.sin_addr.s_addr, addr_buf, (socklen_t)sizeof(addr_buf)); |
| } |
| } else if (me->addr.sa.sa_family == AF_INET6) { |
| if (privacy) { |
| addr_str = DNSSD_REDACTED_IPv6_ADDRESS_STR; |
| } else { |
| const struct sockaddr_in6 * const sin6 = &me->addr.v6; |
| mdns_compile_time_check_local(sizeof(addr_buf) >= INET6_ADDRSTRLEN); |
| addr_str = inet_ntop(AF_INET6, sin6->sin6_addr.s6_addr, addr_buf, (socklen_t)sizeof(addr_buf)); |
| if (addr_str && (sin6->sin6_scope_id > 0)) { |
| char * const dst = &addr_buf[strlen(addr_buf)]; |
| const char * const end = &addr_buf[countof(addr_buf)]; |
| char ifname[IF_NAMESIZE + 1]; |
| if (if_indextoname(sin6->sin6_scope_id, ifname)) { |
| snprintf(dst, (size_t)(end - dst), "%%%s", ifname); |
| } else { |
| snprintf(dst, (size_t)(end - dst), "%%%u", sin6->sin6_scope_id); |
| } |
| } |
| } |
| } else { |
| addr_str = NULL; |
| } |
| char *desc = NULL; |
| char *buf_ptr = NULL; |
| size_t buf_len = 0; |
| for (;;) |
| { |
| char *dst = buf_ptr; |
| char * const end = &buf_ptr[buf_len]; |
| size_t desc_len = 0; |
| int n; |
| if (debug) { |
| n = _dnssd_snprintf(&dst, end, "dnssd_%s (%p): ", me->base.kind->name, (void *)me); |
| require_quiet(n >= 0, exit); |
| desc_len += (size_t)n; |
| } |
| n = _dnssd_snprintf(&dst, end, "hostname: %s, address: %s, type: %s, ifindex: %lu", |
| hostname ? hostname : "<NO HOSTNAME>", addr_str ? addr_str : "<NO ADDR>", |
| dnssd_getaddrinfo_result_type_to_string(me->type), (unsigned long)me->if_index); |
| require_quiet(n >= 0, exit); |
| desc_len += (size_t)n; |
| if (!buf_ptr) { |
| buf_len = desc_len + 1; |
| buf_ptr = (char *)mdns_malloc(buf_len); |
| require_quiet(buf_ptr, exit); |
| buf_ptr[0] = '\0'; |
| } else { |
| break; |
| } |
| } |
| desc = buf_ptr; |
| buf_ptr = NULL; |
| |
| exit: |
| FreeNullSafe(buf_ptr); |
| return desc; |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| _dnssd_getaddrinfo_result_finalize(dnssd_getaddrinfo_result_t me) |
| { |
| xpc_forget(&me->hostname); |
| xpc_forget(&me->actual_hostname); |
| dnssd_forget(&me->cnames); |
| xpc_forget(&me->provider_name); |
| xpc_forget(&me->doh_uri); |
| xpc_forget(&me->doh_path); |
| xpc_forget(&me->odoh_config); |
| xpc_forget(&me->alpn_values); |
| xpc_forget(&me->service_name); |
| mdns_xpc_string_forget(&me->tracker_hostname); |
| mdns_xpc_string_forget(&me->tracker_owner); |
| xpc_forget(&me->ech_config); |
| xpc_forget(&me->address_hints); |
| xpc_forget(&me->validation_data); |
| mdns_xpc_string_forget(&me->ede_text); |
| } |
| |
| //====================================================================================================================== |
| |
| static OSStatus |
| _dnssd_getaddrinfo_set_cnames(const dnssd_getaddrinfo_t me, const int record_type, const xpc_object_t xpc_cname_array) |
| { |
| dnssd_cname_array_t *cnames_ptr; |
| switch (record_type) { |
| case kDNSServiceType_A: |
| cnames_ptr = &me->cnames_a; |
| break; |
| |
| case kDNSServiceType_AAAA: |
| cnames_ptr = &me->cnames_aaaa; |
| break; |
| |
| default: |
| cnames_ptr = NULL; |
| break; |
| } |
| OSStatus err; |
| if (cnames_ptr) { |
| dnssd_forget(cnames_ptr); |
| *cnames_ptr = _dnssd_cname_array_create(xpc_cname_array, &err); |
| require_noerr_quiet(err, exit); |
| } |
| err = kNoErr; |
| |
| exit: |
| return err; |
| } |
| |
| //====================================================================================================================== |
| |
| static xpc_object_t |
| _dnssd_getaddrinfo_get_cname_array(const dnssd_getaddrinfo_t me, const int type) |
| { |
| switch (type) { |
| case kDNSServiceType_A: |
| return me->cnames_a; |
| |
| case kDNSServiceType_AAAA: |
| return me->cnames_aaaa; |
| |
| default: |
| return NULL; |
| } |
| } |
| |
| //====================================================================================================================== |
| // MARK: - dnssd_cname_array Public Methods |
| |
| size_t |
| dnssd_cname_array_get_count(const dnssd_cname_array_t me) |
| { |
| return (me->xpc_array ? xpc_array_get_count(me->xpc_array) : 0); |
| } |
| |
| //====================================================================================================================== |
| |
| const char * |
| dnssd_cname_array_get_cname(const dnssd_cname_array_t me, const size_t index) |
| { |
| return (me->xpc_array ? xpc_array_get_string(me->xpc_array, index) : NULL); |
| } |
| |
| //====================================================================================================================== |
| // MARK: - dnssd_cname_array Private Methods |
| |
| static dnssd_cname_array_t |
| _dnssd_cname_array_create(const xpc_object_t xpc_array, OSStatus * const out_error) |
| { |
| OSStatus err; |
| dnssd_cname_array_t array = NULL; |
| dnssd_cname_array_t obj = _dnssd_cname_array_alloc(); |
| require_action_quiet(obj, exit, err = kNoMemoryErr); |
| |
| if (xpc_array) { |
| obj->xpc_array = xpc_copy(xpc_array); |
| require_action_quiet(obj->xpc_array, exit, err = kNoResourcesErr); |
| } |
| array = obj; |
| obj = NULL; |
| err = kNoErr; |
| |
| exit: |
| if (out_error) { |
| *out_error = err; |
| } |
| dnssd_release_null_safe(obj); |
| return array; |
| } |
| |
| //====================================================================================================================== |
| |
| static char * |
| _dnssd_cname_array_copy_description(const dnssd_cname_array_t me, const bool debug, const bool privacy) |
| { |
| char *desc = NULL; |
| char *buf_ptr = NULL; |
| size_t buf_len = 0; |
| for (;;) |
| { |
| __block int n; |
| __block char *dst = buf_ptr; |
| const char * const end = &buf_ptr[buf_len]; |
| __block size_t desc_len = 0; |
| if (debug) { |
| n = _dnssd_snprintf(&dst, end, "dnssd_%s (%p): ", me->base.kind->name, (void *)me); |
| require_quiet(n >= 0, exit); |
| desc_len += (size_t)n; |
| } |
| n = _dnssd_snprintf(&dst, end, "["); |
| require_quiet(n >= 0, exit); |
| desc_len += (size_t)n; |
| |
| if (privacy) { |
| n = _dnssd_snprintf(&dst, end, "<%zu redacted cnames>", |
| me->xpc_array ? xpc_array_get_count(me->xpc_array) : 0); |
| require_quiet(n >= 0, exit); |
| desc_len += (size_t)n; |
| } else if (me->xpc_array) { |
| const bool ok = xpc_array_apply(me->xpc_array, |
| ^ bool (const size_t index, const xpc_object_t _Nonnull cname) |
| { |
| const char *cname_str = xpc_string_get_string_ptr(cname); |
| if (!cname_str) { |
| cname_str = ""; |
| } |
| n = _dnssd_snprintf(&dst, end, "%s%s", (index == 0) ? "" : ", ", cname_str); |
| if (likely(n >= 0)) { |
| desc_len += (size_t)n; |
| return true; |
| } else { |
| return false; |
| } |
| }); |
| require_quiet(ok, exit); |
| } |
| n = _dnssd_snprintf(&dst, end, "]"); |
| require_quiet(n >= 0, exit); |
| desc_len += (size_t)n; |
| |
| if (!buf_ptr) { |
| buf_len = desc_len + 1; |
| buf_ptr = (char *)mdns_malloc(buf_len); |
| require_quiet(buf_ptr, exit); |
| buf_ptr[0] = '\0'; |
| } else { |
| break; |
| } |
| } |
| desc = buf_ptr; |
| buf_ptr = NULL; |
| |
| exit: |
| FreeNullSafe(buf_ptr); |
| return desc; |
| } |
| |
| //====================================================================================================================== |
| |
| void |
| _dnssd_cname_array_finalize(dnssd_cname_array_t me) |
| { |
| xpc_forget(&me->xpc_array); |
| } |
| |
| //====================================================================================================================== |
| // MARK: - dnssd Client |
| |
| static dnssd_getaddrinfo_t g_gai_list = NULL; |
| |
| static dispatch_queue_t |
| _dnssd_client_queue(void) |
| { |
| static dispatch_once_t once = 0; |
| static dispatch_queue_t queue = NULL; |
| |
| dispatch_once(&once, |
| ^{ |
| queue = dispatch_queue_create("com.apple.dnssd.client", DISPATCH_QUEUE_SERIAL); |
| }); |
| return queue; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dnssd_client_handle_message(xpc_object_t msg); |
| static void |
| _dnssd_client_handle_interruption(void); |
| |
| static xpc_connection_t |
| _dnssd_client_connection(void) |
| { |
| static dispatch_once_t once = 0; |
| static xpc_connection_t connection = NULL; |
| |
| dispatch_once(&once, |
| ^{ |
| connection = xpc_connection_create_mach_service(DNSSD_MACH_SERVICE_NAME, _dnssd_client_queue(), |
| XPC_CONNECTION_MACH_SERVICE_PRIVILEGED); |
| xpc_connection_set_event_handler(connection, |
| ^(xpc_object_t event) |
| { |
| if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) { |
| _dnssd_client_handle_message(event); |
| } else if (event == XPC_ERROR_CONNECTION_INTERRUPTED) { |
| _dnssd_client_handle_interruption(); |
| } |
| }); |
| xpc_connection_activate(connection); |
| }); |
| return connection; |
| } |
| |
| static void |
| _dnssd_client_handle_message(xpc_object_t msg) |
| { |
| // Check if the server sent a keepalive command, which requires an immediate reply. |
| const char * const command = dnssd_xpc_message_get_command(msg); |
| if (command && (strcmp(command, DNSSD_COMMAND_KEEPALIVE) == 0)) { |
| xpc_object_t reply = xpc_dictionary_create_reply(msg); |
| if (reply) { |
| xpc_dictionary_send_reply(reply); |
| xpc_forget(&reply); |
| } |
| goto exit; |
| } |
| const uint64_t command_id = dnssd_xpc_message_get_id(msg, NULL); |
| dnssd_getaddrinfo_t gai; |
| for (gai = g_gai_list; gai; gai = gai->next) { |
| if (gai->command_id == command_id) { |
| break; |
| } |
| } |
| require_quiet(gai, exit); |
| |
| const OSStatus error = dnssd_xpc_message_get_error(msg, NULL); |
| if (!error) { |
| xpc_object_t const result_array = dnssd_xpc_message_get_results(msg); |
| require_quiet(result_array, exit); |
| |
| dnssd_getaddrinfo_result_t result_list = NULL; |
| __block dnssd_getaddrinfo_result_t * result_ptr = &result_list; |
| xpc_array_apply(result_array, |
| ^ bool (__unused size_t index, xpc_object_t _Nonnull result_dict) |
| { |
| const dnssd_getaddrinfo_result_t result = _dnssd_getaddrinfo_create_result_from_dictionary(gai, result_dict, |
| NULL); |
| if (result) { |
| *result_ptr = result; |
| result_ptr = &result->next; |
| } |
| return true; |
| }); |
| require_quiet(result_list, exit); |
| |
| _dnssd_getaddrinfo_append_results(gai, result_list); |
| result_list = NULL; |
| } else { |
| _dnssd_client_fail_getaddrinfo(gai, error); |
| } |
| |
| exit: |
| return; |
| } |
| |
| static void |
| _dnssd_client_handle_interruption(void) |
| { |
| dnssd_getaddrinfo_t next_gai; |
| for (dnssd_getaddrinfo_t gai = g_gai_list; gai; gai = next_gai) { |
| next_gai = gai->next; |
| gai->state = dnssd_getaddrinfo_state_starting; |
| const OSStatus err = _dnssd_client_send_getaddrinfo_command(gai); |
| if (!err) { |
| _dnssd_getaddrinfo_remove_all_results(gai); |
| } else { |
| _dnssd_client_fail_getaddrinfo(gai, err); |
| } |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static uint64_t |
| _dnssd_client_get_new_id(void) |
| { |
| static uint64_t last_id = 0; |
| return ++last_id; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dnssd_client_activate_getaddrinfo(dnssd_getaddrinfo_t gai); |
| |
| static OSStatus |
| _dnssd_getaddrinfo_activate(dnssd_getaddrinfo_t gai); |
| |
| static void |
| _dnssd_client_activate_getaddrinfo_async(dnssd_getaddrinfo_t gai) |
| { |
| dnssd_retain(gai); |
| dispatch_async(_dnssd_client_queue(), |
| ^{ |
| _dnssd_client_activate_getaddrinfo(gai); |
| dnssd_release(gai); |
| }); |
| } |
| |
| static void |
| _dnssd_client_activate_getaddrinfo(dnssd_getaddrinfo_t gai) |
| { |
| OSStatus err; |
| require_action_quiet(gai->state == dnssd_getaddrinfo_state_nascent, exit, err = kNoErr); |
| |
| err = _dnssd_getaddrinfo_activate(gai); |
| if (err) { |
| gai->state = dnssd_getaddrinfo_state_failed; |
| goto exit; |
| } |
| |
| gai->command_id = _dnssd_client_get_new_id(); |
| gai->state = dnssd_getaddrinfo_state_starting; |
| |
| _dnssd_client_register_getaddrinfo(gai); |
| |
| err = _dnssd_client_send_getaddrinfo_command(gai); |
| if (err) { |
| _dnssd_client_fail_getaddrinfo(gai, err); |
| } |
| |
| exit: |
| return; |
| } |
| |
| static void |
| _dnssd_getaddrinfo_process_events(dnssd_getaddrinfo_t gai, unsigned long events); |
| |
| static OSStatus |
| _dnssd_getaddrinfo_activate(dnssd_getaddrinfo_t me) |
| { |
| OSStatus err; |
| xpc_object_t const hostname = dnssd_xpc_parameters_get_hostname_object(me->params); |
| require_action_quiet(hostname, exit, err = kParamErr); |
| |
| me->hostname = xpc_copy(hostname); |
| require_action_quiet(me->hostname, exit, err = kNoResourcesErr); |
| |
| me->event_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0, me->user_queue); |
| require_action_quiet(me->event_source, exit, err = kNoResourcesErr); |
| |
| dnssd_retain(me); |
| dispatch_source_t const event_source = me->event_source; |
| dispatch_source_set_event_handler(me->event_source, |
| ^{ |
| _dnssd_getaddrinfo_process_events(me, dispatch_source_get_data(event_source)); |
| }); |
| dispatch_source_set_cancel_handler(me->event_source, |
| ^{ |
| dnssd_release(me); |
| }); |
| dispatch_activate(me->event_source); |
| err = kNoErr; |
| |
| exit: |
| if (err) { |
| dnssd_retain(me); |
| dispatch_async(me->user_queue, |
| ^{ |
| if (me->event_handler) { |
| me->event_handler(dnssd_event_error, _dnssd_osstatus_to_dns_service_error(err)); |
| } |
| dnssd_release(me); |
| }); |
| } |
| return err; |
| } |
| |
| static void |
| _dnssd_getaddrinfo_process_events(dnssd_getaddrinfo_t me, unsigned long events) |
| { |
| if (events & DNSSD_EVENT_REMOVE_ALL) { |
| if (me->event_handler) { |
| me->event_handler(dnssd_event_remove_all, kDNSServiceErr_NoError); |
| } |
| } |
| |
| if (events & DNSSD_EVENT_HAVE_RESULTS) { |
| dnssd_getaddrinfo_result_t result; |
| dnssd_getaddrinfo_result_t result_array[32]; |
| dnssd_getaddrinfo_result_t result_list = _dnssd_getaddrinfo_take_results(me); |
| |
| size_t result_count = 0; |
| while ((result = result_list) != NULL) { |
| result_list = result->next; |
| result->next = NULL; |
| result_array[result_count++] = result; |
| |
| if ((result_count == countof(result_array)) || !result_list) { |
| if (me->result_handler) { |
| me->result_handler(result_array, result_count); |
| } |
| for (size_t i = 0; i < result_count; ++i) { |
| dnssd_release(result_array[i]); |
| } |
| result_count = 0; |
| } |
| } |
| } |
| |
| if (events & DNSSD_EVENT_ERROR) { |
| __block OSStatus error; |
| dispatch_sync(me->mutex_queue, |
| ^{ |
| error = me->error; |
| me->error = kNoErr; |
| }); |
| if (me->event_handler && error) { |
| me->event_handler(dnssd_event_error, _dnssd_osstatus_to_dns_service_error(error)); |
| } |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dnssd_client_register_getaddrinfo(dnssd_getaddrinfo_t gai) |
| { |
| gai->next = g_gai_list; |
| g_gai_list = gai; |
| dnssd_retain(gai); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dnssd_client_deregister_getaddrinfo(dnssd_getaddrinfo_t gai) |
| { |
| dnssd_getaddrinfo_t *ptr; |
| for (ptr = &g_gai_list; *ptr; ptr = &(*ptr)->next) |
| { |
| if (*ptr == gai) { |
| break; |
| } |
| } |
| if (*ptr) { |
| *ptr = gai->next; |
| gai->next = NULL; |
| dnssd_release(gai); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dnssd_client_handle_getaddrinfo_reply(dnssd_getaddrinfo_t gai, xpc_object_t reply); |
| |
| static OSStatus |
| _dnssd_client_send_getaddrinfo_command(dnssd_getaddrinfo_t gai) |
| { |
| OSStatus err; |
| xpc_object_t msg = xpc_dictionary_create(NULL, NULL, 0); |
| require_action_quiet(msg, exit, err = kNoResourcesErr); |
| |
| dnssd_xpc_message_set_id(msg, gai->command_id); |
| dnssd_xpc_message_set_command(msg, DNSSD_COMMAND_GETADDRINFO); |
| dnssd_xpc_message_set_parameters(msg, gai->params); |
| |
| dnssd_retain(gai); |
| xpc_connection_send_message_with_reply(_dnssd_client_connection(), msg, _dnssd_client_queue(), |
| ^(xpc_object_t reply) |
| { |
| _dnssd_client_handle_getaddrinfo_reply(gai, reply); |
| dnssd_release(gai); |
| }); |
| xpc_forget(&msg); |
| err = kNoErr; |
| |
| exit: |
| return err; |
| } |
| |
| static void |
| _dnssd_client_handle_getaddrinfo_reply(dnssd_getaddrinfo_t gai, xpc_object_t reply) |
| { |
| require_quiet(gai->state == dnssd_getaddrinfo_state_starting, exit); |
| |
| if (xpc_get_type(reply) == XPC_TYPE_DICTIONARY) { |
| const OSStatus error = dnssd_xpc_message_get_error(reply, NULL); |
| if (error) { |
| _dnssd_client_fail_getaddrinfo(gai, error); |
| } else { |
| gai->state = dnssd_getaddrinfo_state_started; |
| } |
| } else if (reply != XPC_ERROR_CONNECTION_INTERRUPTED) { |
| OSStatus error; |
| if (reply == XPC_ERROR_CONNECTION_INVALID) { |
| error = kDNSServiceErr_ServiceNotRunning; |
| } else { |
| error = kDNSServiceErr_Unknown; |
| } |
| _dnssd_client_fail_getaddrinfo(gai, error); |
| } |
| |
| exit: |
| return; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dnssd_client_fail_getaddrinfo(dnssd_getaddrinfo_t gai, OSStatus error) |
| { |
| _dnssd_client_deregister_getaddrinfo(gai); |
| gai->state = dnssd_getaddrinfo_state_failed; |
| _dnssd_getaddrinfo_post_error_event(gai, error); |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| _dnssd_extract_result_dict_values(xpc_object_t result, xpc_object_t *out_hostname, DNSServiceErrorType *out_error, |
| DNSServiceFlags *out_flags, uint32_t *out_interface_index, uint16_t *out_type, uint16_t *out_class, |
| xpc_object_t *out_rdata, dnssd_getaddrinfo_result_protocol_t *out_protocol, xpc_object_t *out_provider_name, |
| mdns_xpc_string_t *out_tracker_hostname, mdns_xpc_string_t *out_tracker_owner, bool *out_tracker_approved, |
| bool *out_tracker_can_block_request, dnssd_negative_reason_t *out_negative_reason, |
| xpc_object_t *out_validation_data, mdns_xpc_dictionary_t *out_extended_dns_error); |
| |
| static dnssd_getaddrinfo_result_t |
| _dnssd_getaddrinfo_result_create(dnssd_getaddrinfo_result_type_t type, xpc_object_t hostname, |
| xpc_object_t actual_hostname, dnssd_cname_array_t cname_array, int addr_family, const void *addr_data, |
| uint32_t interface_index, dnssd_getaddrinfo_result_protocol_t protocol, xpc_object_t provider_name, |
| mdns_xpc_string_t tracker_hostname, mdns_xpc_string_t tracker_owner, bool tracker_approved, |
| bool tracker_can_block_request, dnssd_negative_reason_t negative_reason, xpc_object_t validation_data, |
| mdns_xpc_dictionary_t extended_dns_error, OSStatus *out_error); |
| |
| static dnssd_getaddrinfo_result_t |
| _dnssd_getaddrinfo_result_create_svcb(xpc_object_t hostname, xpc_object_t actual_hostname, const void *svcb_data, |
| size_t svcb_length, uint32_t interface_index, dnssd_getaddrinfo_result_protocol_t protocol, |
| xpc_object_t provider_name, mdns_xpc_string_t tracker_hostname, mdns_xpc_string_t tracker_owner, |
| bool tracker_approved, bool tracker_can_block_request, dnssd_negative_reason_t negative_reason, |
| xpc_object_t validation_data, OSStatus *out_error); |
| |
| static dnssd_getaddrinfo_result_t |
| _dnssd_getaddrinfo_create_result_from_dictionary(dnssd_getaddrinfo_t me, xpc_object_t result_dict, OSStatus *out_error) |
| { |
| OSStatus err; |
| xpc_object_t actual_hostname, rdata, provider_name; |
| mdns_xpc_string_t tracker_hostname, tracker_owner; |
| DNSServiceErrorType error; |
| DNSServiceFlags flags; |
| uint32_t if_index; |
| uint16_t rtype; |
| bool tracker_approved; |
| bool tracker_can_block_request; |
| dnssd_getaddrinfo_result_protocol_t protocol; |
| dnssd_negative_reason_t negative_reason; |
| xpc_object_t validation_data; |
| mdns_xpc_dictionary_t extended_dns_error; |
| |
| dnssd_getaddrinfo_result_t result = NULL; |
| const bool ok = _dnssd_extract_result_dict_values(result_dict, &actual_hostname, &error, &flags, &if_index, |
| &rtype, NULL, &rdata, &protocol, &provider_name, &tracker_hostname, &tracker_owner, &tracker_approved, |
| &tracker_can_block_request, &negative_reason, &validation_data, &extended_dns_error); |
| require_action_quiet(ok, exit, err = kMalformedErr); |
| require_action_quiet((error == kDNSServiceErr_NoError) || (error == kDNSServiceErr_NoSuchRecord), exit, |
| err = kUnexpectedErr); |
| |
| // Dedupe the tracker hostname and owner strings, which will very likely be the same across multiple results. |
| if (tracker_hostname) { |
| mdns_xpc_string_replace_if_different(&me->tracker_hostname, tracker_hostname); |
| tracker_hostname = me->tracker_hostname; |
| } |
| if (tracker_owner) { |
| mdns_xpc_string_replace_if_different(&me->tracker_owner, tracker_owner); |
| tracker_owner = me->tracker_owner; |
| } |
| switch(rtype) { |
| case kDNSServiceType_A: |
| case kDNSServiceType_AAAA: { |
| const xpc_object_t cname_update = dnssd_xpc_result_get_cname_update(result_dict); |
| if (cname_update) { |
| _dnssd_getaddrinfo_set_cnames(me, rtype, cname_update); |
| } |
| dnssd_getaddrinfo_result_type_t result_type; |
| if (error == kDNSServiceErr_NoSuchRecord) { |
| result_type = dnssd_getaddrinfo_result_type_no_address; |
| } else { |
| if (flags & kDNSServiceFlagsAdd) { |
| if (flags & kDNSServiceFlagsExpiredAnswer) { |
| result_type = dnssd_getaddrinfo_result_type_expired; |
| } else { |
| result_type = dnssd_getaddrinfo_result_type_add; |
| } |
| } else { |
| result_type = dnssd_getaddrinfo_result_type_remove; |
| } |
| if (rtype == kDNSServiceType_A) { |
| require_action_quiet(xpc_data_get_length(rdata) == 4, exit, err = kMalformedErr); |
| } else { |
| require_action_quiet(xpc_data_get_length(rdata) == 16, exit, err = kMalformedErr); |
| } |
| } |
| const int addr_family = (rtype == kDNSServiceType_A) ? AF_INET : AF_INET6; |
| result = _dnssd_getaddrinfo_result_create(result_type, me->hostname, actual_hostname, |
| _dnssd_getaddrinfo_get_cname_array(me, rtype), addr_family, xpc_data_get_bytes_ptr(rdata), if_index, |
| protocol, provider_name, tracker_hostname, tracker_owner, tracker_approved, tracker_can_block_request, |
| negative_reason, validation_data, extended_dns_error, &err); |
| require_noerr_quiet(err, exit); |
| break; |
| } |
| case kDNSServiceType_SVCB: |
| case kDNSServiceType_HTTPS: { |
| if (error != kDNSServiceErr_NoSuchRecord) { |
| require_action_quiet(xpc_data_get_length(rdata) > 0, exit, err = kMalformedErr); |
| } |
| |
| // SVCB type answer |
| result = _dnssd_getaddrinfo_result_create_svcb(me->hostname, actual_hostname, |
| xpc_data_get_bytes_ptr(rdata), xpc_data_get_length(rdata), if_index, protocol, provider_name, |
| tracker_hostname, tracker_owner, tracker_approved, tracker_can_block_request, negative_reason, |
| validation_data, &err); |
| require_noerr_quiet(err, exit); |
| break; |
| } |
| default: |
| err = kTypeErr; |
| goto exit; |
| } |
| if ((flags & kDNSServiceFlagsAdd) && (flags & kDNSServiceFlagAnsweredFromCache)) { |
| result->is_from_cache = true; |
| } |
| |
| exit: |
| if (err) { |
| dnssd_forget(&result); |
| } |
| if (out_error) { |
| *out_error = err; |
| } |
| return result; |
| } |
| |
| static bool |
| _dnssd_extract_result_dict_values(xpc_object_t result, xpc_object_t *out_hostname, DNSServiceErrorType *out_error, |
| DNSServiceFlags *out_flags, uint32_t *out_interface_index, uint16_t *out_type, uint16_t *out_class, |
| xpc_object_t *out_rdata, dnssd_getaddrinfo_result_protocol_t *out_protocol, xpc_object_t *out_provider_name, |
| mdns_xpc_string_t *out_tracker_hostname, mdns_xpc_string_t *out_tracker_owner, bool *out_tracker_approved, |
| bool *out_tracker_can_block_request, dnssd_negative_reason_t *out_negative_reason, |
| xpc_object_t *out_validation_data, mdns_xpc_dictionary_t *out_extended_dns_error) |
| { |
| bool result_is_valid = false; |
| xpc_object_t const hostname = dnssd_xpc_result_get_record_name_object(result); |
| require_quiet(hostname, exit); |
| |
| xpc_object_t const rdata = dnssd_xpc_result_get_record_data_object(result); |
| require_quiet(rdata, exit); |
| |
| if (out_hostname) { |
| *out_hostname = hostname; |
| } |
| if (out_error) { |
| *out_error = dnssd_xpc_result_get_error(result, NULL); |
| } |
| if (out_flags) { |
| *out_flags = dnssd_xpc_result_get_flags(result, NULL); |
| } |
| if (out_interface_index) { |
| *out_interface_index = dnssd_xpc_result_get_interface_index(result, NULL); |
| } |
| if (out_type) { |
| *out_type = dnssd_xpc_result_get_record_type(result, NULL); |
| } |
| if (out_class) { |
| *out_class = dnssd_xpc_result_get_record_class(result, NULL); |
| } |
| if (out_rdata) { |
| *out_rdata = rdata; |
| } |
| if (out_protocol) { |
| *out_protocol = dnssd_xpc_result_get_record_protocol(result, NULL); |
| } |
| if (out_provider_name) { |
| *out_provider_name = dnssd_xpc_result_get_provider_name_object(result); |
| } |
| if (out_tracker_hostname) { |
| *out_tracker_hostname = dnssd_xpc_result_get_tracker_hostname(result); |
| } |
| if (out_tracker_owner) { |
| *out_tracker_owner = dnssd_xpc_result_get_tracker_owner(result); |
| } |
| if (out_tracker_approved) { |
| *out_tracker_approved = dnssd_xpc_result_get_tracker_is_approved(result); |
| } |
| if (out_tracker_can_block_request) { |
| *out_tracker_can_block_request = dnssd_xpc_result_get_tracker_can_block_request(result); |
| } |
| if (out_negative_reason) { |
| *out_negative_reason = dnssd_xpc_result_get_negative_reason(result); |
| } |
| if (out_validation_data) { |
| *out_validation_data = dnssd_xpc_result_get_validation_data_object(result); |
| } |
| if (out_extended_dns_error) { |
| *out_extended_dns_error = dnssd_xpc_result_get_extended_dns_error(result); |
| } |
| result_is_valid = true; |
| |
| exit: |
| return result_is_valid; |
| } |
| |
| static dnssd_getaddrinfo_result_t |
| _dnssd_getaddrinfo_result_create(const dnssd_getaddrinfo_result_type_t type, const xpc_object_t hostname, |
| const xpc_object_t actual_hostname, const dnssd_cname_array_t cnames, const int addr_family, |
| const void * const addr_data, const uint32_t if_index, const dnssd_getaddrinfo_result_protocol_t protocol, |
| const xpc_object_t provider_name, const mdns_xpc_string_t tracker_hostname, const mdns_xpc_string_t tracker_owner, |
| const bool tracker_approved, const bool tracker_can_block_request, const dnssd_negative_reason_t negative_reason, |
| const xpc_object_t validation_data, const mdns_xpc_dictionary_t extended_dns_error, OSStatus * const out_error) |
| { |
| OSStatus err; |
| dnssd_getaddrinfo_result_t result = NULL; |
| dnssd_getaddrinfo_result_t obj = _dnssd_getaddrinfo_result_alloc(); |
| require_action_quiet(obj, exit, err = kNoMemoryErr); |
| |
| switch (type) { |
| case dnssd_getaddrinfo_result_type_add: |
| case dnssd_getaddrinfo_result_type_remove: |
| case dnssd_getaddrinfo_result_type_no_address: |
| case dnssd_getaddrinfo_result_type_expired: |
| break; |
| |
| case dnssd_getaddrinfo_result_type_service_binding: |
| MDNS_COVERED_SWITCH_DEFAULT: |
| err = kTypeErr; |
| goto exit; |
| } |
| obj->type = type; |
| obj->if_index = if_index; |
| obj->protocol = protocol; |
| obj->negative_reason = negative_reason; |
| |
| require_action_quiet(xpc_get_type(hostname) == XPC_TYPE_STRING, exit, err = kTypeErr); |
| |
| obj->hostname = xpc_copy(hostname); |
| require_action_quiet(obj->hostname, exit, err = kNoResourcesErr); |
| |
| require_action_quiet(xpc_get_type(actual_hostname) == XPC_TYPE_STRING, exit, err = kTypeErr); |
| |
| obj->actual_hostname = xpc_copy(actual_hostname); |
| require_action_quiet(obj->actual_hostname, exit, err = kNoResourcesErr); |
| |
| obj->cnames = cnames ? cnames : _dnssd_get_empty_cname_array(); |
| dnssd_retain(obj->cnames); |
| require_action_quiet((addr_family == AF_INET) || (addr_family == AF_INET6), exit, err = kTypeErr); |
| |
| if (addr_family == AF_INET) { |
| obj->addr.sa.sa_family = AF_INET; |
| obj->addr.v4.sin_len = sizeof(struct sockaddr_in); |
| if (obj->type != dnssd_getaddrinfo_result_type_no_address) { |
| memcpy(&obj->addr.v4.sin_addr.s_addr, addr_data, 4); |
| } |
| } else if (addr_family == AF_INET6) { |
| struct sockaddr_in6 * const sin6 = &obj->addr.v6; |
| sin6->sin6_family = AF_INET6; |
| sin6->sin6_len = sizeof(struct sockaddr_in6); |
| if (obj->type != dnssd_getaddrinfo_result_type_no_address) { |
| memcpy(&sin6->sin6_addr.s6_addr, addr_data, 16); |
| if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { |
| sin6->sin6_scope_id = obj->if_index; |
| } |
| } |
| } |
| if (provider_name) { |
| require_action_quiet(xpc_get_type(provider_name) == XPC_TYPE_STRING, exit, err = kTypeErr); |
| |
| obj->provider_name = xpc_copy(provider_name); |
| require_action_quiet(obj->provider_name, exit, err = kNoResourcesErr); |
| } |
| if (tracker_hostname) { |
| obj->tracker_hostname = mdns_xpc_string_retain(tracker_hostname); |
| if (tracker_owner) { |
| obj->tracker_owner = mdns_xpc_string_retain(tracker_owner); |
| } |
| obj->tracker_approved = tracker_approved; |
| obj->tracker_can_block = tracker_can_block_request; |
| } |
| if (validation_data) { |
| require_action_quiet(xpc_get_type(validation_data) == XPC_TYPE_DATA, exit, err = kTypeErr); |
| |
| obj->validation_data = xpc_copy(validation_data); |
| require_action_quiet(obj->validation_data, exit, err = kNoResourcesErr); |
| } |
| if (extended_dns_error) { |
| obj->ede_code = dnssd_xpc_extended_dns_error_get_code(extended_dns_error, NULL); |
| obj->ede_text = dnssd_xpc_extended_dns_error_get_text(extended_dns_error); |
| if (obj->ede_text) { |
| mdns_xpc_string_retain(obj->ede_text); |
| } |
| obj->ede_valid = true; |
| } |
| result = obj; |
| obj = NULL; |
| err = kNoErr; |
| |
| exit: |
| if (out_error) { |
| *out_error = err; |
| } |
| dnssd_release_null_safe(obj); |
| return result; |
| } |
| |
| static dnssd_getaddrinfo_result_t |
| _dnssd_getaddrinfo_result_create_svcb(xpc_object_t hostname, xpc_object_t actual_hostname, const void *svcb_data, |
| size_t svcb_length, uint32_t interface_index, dnssd_getaddrinfo_result_protocol_t protocol, |
| xpc_object_t provider_name, const mdns_xpc_string_t tracker_hostname, const mdns_xpc_string_t tracker_owner, |
| const bool tracker_approved, const bool tracker_can_block_request, const dnssd_negative_reason_t negative_reason, |
| xpc_object_t validation_data, OSStatus *out_error) |
| { |
| OSStatus err; |
| dnssd_getaddrinfo_result_t result = NULL; |
| dnssd_getaddrinfo_result_t obj = _dnssd_getaddrinfo_result_alloc(); |
| require_action_quiet(obj, exit, err = kNoMemoryErr); |
| |
| obj->type = dnssd_getaddrinfo_result_type_service_binding; |
| obj->if_index = interface_index; |
| obj->protocol = protocol; |
| obj->negative_reason = negative_reason; |
| |
| require_action_quiet(xpc_get_type(hostname) == XPC_TYPE_STRING, exit, err = kTypeErr); |
| obj->hostname = xpc_copy(hostname); |
| |
| require_action_quiet(xpc_get_type(actual_hostname) == XPC_TYPE_STRING, exit, err = kTypeErr); |
| |
| obj->actual_hostname = xpc_copy(actual_hostname); |
| require_action_quiet(obj->actual_hostname, exit, err = kNoResourcesErr); |
| |
| if (svcb_data != NULL && svcb_length > 0) { |
| obj->valid_svcb = dnssd_svcb_is_valid(svcb_data, svcb_length); |
| obj->priority = dnssd_svcb_get_priority(svcb_data, svcb_length); |
| obj->port = dnssd_svcb_get_port(svcb_data, svcb_length); |
| |
| char *service_name = dnssd_svcb_copy_service_name_string(svcb_data, svcb_length); |
| if (service_name != NULL) { |
| if (dnssd_svcb_service_name_is_empty(svcb_data, svcb_length)) { |
| // The empty name is an placeholder for the name for the record |
| obj->service_name = xpc_copy(obj->hostname); |
| } else { |
| obj->service_name = xpc_string_create(service_name); |
| } |
| ForgetMem(&service_name); |
| require_action_quiet(obj->service_name, exit, err = kNoResourcesErr); |
| } |
| |
| char *doh_uri = dnssd_svcb_copy_doh_uri(svcb_data, svcb_length); |
| if (doh_uri != NULL) { |
| obj->doh_uri = xpc_string_create(doh_uri); |
| ForgetMem(&doh_uri); |
| require_action_quiet(obj->doh_uri, exit, err = kNoResourcesErr); |
| } |
| |
| char *doh_path = dnssd_svcb_copy_doh_path(svcb_data, svcb_length); |
| if (doh_path != NULL) { |
| obj->doh_path = xpc_string_create(doh_path); |
| ForgetMem(&doh_path); |
| require_action_quiet(obj->doh_path, exit, err = kNoResourcesErr); |
| } |
| |
| size_t ech_config_length = 0; |
| uint8_t *ech_config = dnssd_svcb_copy_ech_config(svcb_data, svcb_length, &ech_config_length); |
| if (ech_config != NULL) { |
| obj->ech_config = xpc_data_create(ech_config, ech_config_length); |
| ForgetMem(&ech_config); |
| require_action_quiet(obj->ech_config, exit, err = kNoResourcesErr); |
| } |
| |
| size_t odoh_config_length = 0; |
| uint8_t *odoh_config = dnssd_svcb_copy_odoh_config(svcb_data, svcb_length, &odoh_config_length); |
| if (odoh_config != NULL) { |
| obj->odoh_config = xpc_data_create(odoh_config, odoh_config_length); |
| ForgetMem(&odoh_config); |
| require_action_quiet(obj->odoh_config, exit, err = kNoResourcesErr); |
| } |
| |
| dnssd_svcb_access_alpn_values(svcb_data, svcb_length, ^bool(const char *alpn) { |
| xpc_object_t alpn_string = xpc_string_create(alpn); |
| if (obj->alpn_values == NULL) { |
| obj->alpn_values = xpc_array_create(NULL, 0); |
| } |
| xpc_array_append_value(obj->alpn_values, alpn_string); |
| xpc_forget(&alpn_string); |
| return true; |
| }); |
| |
| dnssd_svcb_access_address_hints(svcb_data, svcb_length, ^bool(const struct sockaddr *address) { |
| xpc_object_t address_hint = xpc_data_create(address, address->sa_len); |
| if (obj->address_hints == NULL) { |
| obj->address_hints = xpc_array_create(NULL, 0); |
| } |
| xpc_array_append_value(obj->address_hints, address_hint); |
| xpc_forget(&address_hint); |
| return true; |
| }); |
| } else { |
| obj->valid_svcb = false; |
| } |
| |
| if (provider_name) { |
| require_action_quiet(xpc_get_type(provider_name) == XPC_TYPE_STRING, exit, err = kTypeErr); |
| |
| obj->provider_name = xpc_copy(provider_name); |
| require_action_quiet(obj->provider_name, exit, err = kNoResourcesErr); |
| } |
| |
| if (tracker_hostname) { |
| obj->tracker_hostname = mdns_xpc_string_retain(tracker_hostname); |
| if (tracker_owner) { |
| obj->tracker_owner = mdns_xpc_string_retain(tracker_owner); |
| } |
| obj->tracker_approved = tracker_approved; |
| obj->tracker_can_block = tracker_can_block_request; |
| } |
| if (validation_data) { |
| require_action_quiet(xpc_get_type(validation_data) == XPC_TYPE_DATA, exit, err = kTypeErr); |
| |
| obj->validation_data = xpc_copy(validation_data); |
| require_action_quiet(obj->validation_data, exit, err = kNoResourcesErr); |
| } |
| result = obj; |
| obj = NULL; |
| err = kNoErr; |
| |
| exit: |
| if (out_error) { |
| *out_error = err; |
| } |
| dnssd_release_null_safe(obj); |
| return result; |
| } |
| |
| //====================================================================================================================== |
| // MARK: - Misc. Helpers |
| |
| static dnssd_cname_array_t |
| _dnssd_get_empty_cname_array(void) |
| { |
| static dispatch_once_t s_once = 0; |
| static dnssd_cname_array_t s_empty_cname_array = NULL; |
| dispatch_once(&s_once, |
| ^{ |
| s_empty_cname_array = _dnssd_cname_array_create(NULL, NULL); |
| s_empty_cname_array->base._os_obj_refcnt = _OS_OBJECT_GLOBAL_REFCNT; |
| s_empty_cname_array->base._os_obj_xref_cnt = _OS_OBJECT_GLOBAL_REFCNT; |
| }); |
| return s_empty_cname_array; |
| } |
| |
| //====================================================================================================================== |
| |
| static DNSServiceErrorType |
| _dnssd_osstatus_to_dns_service_error(OSStatus error) |
| { |
| switch (error) { |
| case kNoMemoryErr: |
| case kNoResourcesErr: |
| error = kDNSServiceErr_NoMemory; |
| break; |
| |
| case kParamErr: |
| error = kDNSServiceErr_BadParam; |
| break; |
| |
| default: |
| if ((error >= kGenericErrorBase) && (error <= kGenericErrorEnd)) { |
| error = kDNSServiceErr_Unknown; |
| } |
| break; |
| } |
| return error; |
| } |
| |
| //====================================================================================================================== |
| |
| static int |
| _dnssd_snprintf(char ** const dst, const char * const end, const char * const format, ...) |
| { |
| char * const ptr = *dst; |
| const size_t len = (size_t)(end - ptr); |
| va_list args; |
| va_start(args, format); |
| CUClangWarningIgnoreBegin(-Wformat-nonliteral); |
| const int n = vsnprintf(ptr, len, format, args); |
| CUClangWarningIgnoreEnd(); |
| va_end(args); |
| if (n >= 0) { |
| *dst = ptr + Min((size_t)n, len); |
| } |
| return n; |
| } |