| /* |
| * 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_server.h" |
| |
| #include "ClientRequests.h" |
| #include "DNSCommon.h" |
| #include "dnssd_xpc.h" |
| #include "dnssd_svcb.h" |
| #include "dnssd_private.h" |
| #include "gai_options.h" |
| #include "mDNSMacOSX.h" |
| #include "termination_reason.h" |
| |
| #include <CoreUtils/CommonServices.h> |
| #include <CoreUtils/DebugServices.h> |
| #include <mach/mach_time.h> |
| #include <mdns/alloc.h> |
| #include <mdns/audit_token.h> |
| #include <mdns/mortality.h> |
| #include <mdns/resource_record.h> |
| #include <mdns/system.h> |
| #include <mdns/ticks.h> |
| #include <mdns/xpc.h> |
| #include <os/lock.h> |
| #include <stdatomic.h> |
| #include <xpc/private.h> |
| |
| #include "helpers.h" |
| |
| #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER) |
| #include "QuerierSupport.h" |
| #endif |
| |
| #if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT) |
| #include "mdns_trust.h" |
| #include <os/feature_private.h> |
| #endif |
| |
| #if MDNSRESPONDER_SUPPORTS(APPLE, TRACKER_STATE) |
| #include "resolved_cache.h" |
| #endif |
| |
| #if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS) |
| #include <mdns/signed_result.h> |
| #endif |
| |
| #if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS) |
| #include "uds_daemon.h" |
| #include <mdns/dispatch.h> |
| #include <mdns/powerlog.h> |
| #endif |
| |
| #include "mdns_strict.h" |
| |
| //====================================================================================================================== |
| // MARK: - Kind Declarations |
| |
| #define DX_STRUCT(NAME) struct dx_ ## NAME ## _s |
| #define DX_KIND_DECLARE_ABSTRACT(NAME) typedef DX_STRUCT(NAME) * dx_ ## NAME ## _t |
| #define DX_KIND_DECLARE(NAME) \ |
| DX_KIND_DECLARE_ABSTRACT(NAME) |
| |
| // 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 DX_BASE_CHECK(NAME, SUPER) \ |
| check_compile_time(offsetof(DX_STRUCT(NAME), base) == 0); \ |
| check_compile_time(sizeof_field(DX_STRUCT(NAME), base) == sizeof(DX_STRUCT(SUPER))); \ |
| extern int _dx_base_type_check[sizeof(&(((dx_ ## NAME ## _t)0)->base) == ((dx_ ## SUPER ## _t)0))] |
| |
| #define DX_SUBKIND_DEFINE_ABSTRACT(NAME, SUPER, ...) \ |
| static const struct dx_kind_s _dx_ ## NAME ## _kind = { \ |
| MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN() \ |
| .superkind = &_dx_ ## SUPER ##_kind, \ |
| __VA_ARGS__ \ |
| MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_END() \ |
| }; \ |
| DX_BASE_CHECK(NAME, SUPER) |
| |
| #define DX_SUBKIND_DEFINE(NAME, SUPER, ...) \ |
| DX_SUBKIND_DEFINE_ABSTRACT(NAME, SUPER, __VA_ARGS__); \ |
| \ |
| static dx_ ## NAME ## _t \ |
| _dx_ ## NAME ## _new(void) \ |
| { \ |
| const dx_ ## NAME ## _t obj = (dx_ ## NAME ## _t)mdns_calloc(1, sizeof(*obj)); \ |
| require_return_value(obj, NULL); \ |
| \ |
| _dx_object_init(obj, &_dx_ ## NAME ## _kind); \ |
| return obj; \ |
| } \ |
| extern int _dx_dummy_variable |
| |
| #define DX_OBJECT_SUBKIND_DEFINE_ABSTRACT(NAME, ...) DX_SUBKIND_DEFINE_ABSTRACT(NAME, object, __VA_ARGS__) |
| #define DX_OBJECT_SUBKIND_DEFINE(NAME, ...) DX_SUBKIND_DEFINE(NAME, object, __VA_ARGS__) |
| |
| DX_KIND_DECLARE_ABSTRACT(object); |
| DX_KIND_DECLARE(session); |
| DX_KIND_DECLARE_ABSTRACT(request); |
| DX_KIND_DECLARE(gai_request); |
| DX_KIND_DECLARE(gai_result); |
| |
| #define DX_TRANSPARENT_UNION_MEMBER(NAME) DX_STRUCT(NAME) * NAME |
| |
| typedef union { |
| DX_TRANSPARENT_UNION_MEMBER(object); |
| DX_TRANSPARENT_UNION_MEMBER(session); |
| DX_TRANSPARENT_UNION_MEMBER(request); |
| DX_TRANSPARENT_UNION_MEMBER(gai_request); |
| DX_TRANSPARENT_UNION_MEMBER(gai_result); |
| } dx_any_t __attribute__((__transparent_union__)); |
| |
| typedef void |
| (*dx_init_f)(dx_any_t object); |
| |
| typedef void |
| (*dx_invalidate_f)(dx_any_t object); |
| |
| typedef void |
| (*dx_finalize_f)(dx_any_t object); |
| |
| typedef const struct dx_kind_s * dx_kind_t; |
| struct dx_kind_s { |
| dx_kind_t superkind; // This kind's superkind. All kinds have a superkind, except the base kind. |
| dx_init_f init; // Initializes an object. |
| dx_invalidate_f invalidate; // Stops an object's outstanding operations, if any. |
| dx_finalize_f finalize; // Releases object's resources right before the object is freed. |
| }; |
| |
| //====================================================================================================================== |
| // MARK: - Object Kind Definition |
| |
| struct dx_object_s { |
| dx_kind_t kind; // The object's kind. |
| _Atomic(int32_t) ref_count; // Reference count. |
| }; |
| |
| static void |
| _dx_object_init(dx_any_t object, dx_kind_t kind); |
| |
| static void |
| _dx_retain(dx_any_t object); |
| |
| static void |
| _dx_release(dx_any_t object); |
| #define _dx_forget(X) ForgetCustom(X, _dx_release) |
| #define _dx_replace(PTR, OBJ) \ |
| do { \ |
| if (OBJ) { \ |
| _dx_retain(OBJ); \ |
| } \ |
| if (*(PTR)) { \ |
| _dx_release(*(PTR)); \ |
| } \ |
| *(PTR) = (OBJ); \ |
| } while(0) |
| |
| static void |
| _dx_invalidate(dx_any_t object); |
| |
| static const struct dx_kind_s _dx_object_kind = { |
| .superkind = NULL, |
| .init = NULL, |
| .invalidate = NULL, |
| .finalize = NULL |
| }; |
| |
| //====================================================================================================================== |
| // MARK: - Session Kind Definition |
| |
| struct dx_session_s { |
| struct dx_object_s base; // Object base; |
| dx_session_t next; // Next session in list. |
| dx_request_t request_list; // List of outstanding requests. |
| xpc_connection_t connection; // Underlying XPC connection. |
| dispatch_source_t idle_timer; // Timer for detecting idleness. |
| dispatch_source_t keepalive_reply_timer; // Timer for enforcing a time limit on keepalive replies. |
| uint64_t pending_send_start_ticks; // Start time in mach ticks of the current pending send condition. |
| #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN) |
| mdns_audit_token_t peer_token; // Client's audit token. |
| #endif |
| uid_t client_euid; // Client's EUID. |
| pid_t client_pid; // Client's PID. |
| uint32_t pending_send_count; // Count of sent messages that still haven't been processed. |
| char client_name[MAXCOMLEN]; // Client's process name. |
| bool terminated; // True if the session was prematurely ended due to a fatal error. |
| bool log_pending_send_counts; // True if pending send counts should be logged. |
| }; |
| mdns_compile_time_max_size_check(struct dx_session_s, 104); |
| |
| static void |
| _dx_session_invalidate(dx_session_t session); |
| |
| static void |
| _dx_session_finalize(dx_session_t session); |
| |
| DX_OBJECT_SUBKIND_DEFINE(session, |
| .invalidate = _dx_session_invalidate, |
| .finalize = _dx_session_finalize |
| ); |
| |
| typedef union { |
| DX_TRANSPARENT_UNION_MEMBER(request); |
| DX_TRANSPARENT_UNION_MEMBER(gai_request); |
| } dx_any_request_t __attribute__((__transparent_union__)); |
| |
| //====================================================================================================================== |
| // MARK: - Request Kind Definition |
| |
| struct dx_request_s { |
| struct dx_object_s base; // Object base. |
| dx_request_t next; // Next request in list. |
| dx_session_t session; // Back pointer to parent session. |
| xpc_object_t results; // Array of pending results. |
| uint64_t command_id; // ID to distinquish multiple commands during a session. |
| uint32_t request_id; // Request ID, used for logging purposes. |
| DNSServiceErrorType error; // Pending error. |
| os_unfair_lock lock; // Lock for pending error and results array. |
| bool sent_error; // True if the pending error has been sent to client. |
| }; |
| |
| static void |
| _dx_request_init(dx_request_t request); |
| |
| static void |
| _dx_request_finalize(dx_request_t request); |
| |
| DX_OBJECT_SUBKIND_DEFINE_ABSTRACT(request, |
| .init = _dx_request_init, |
| .finalize = _dx_request_finalize |
| ); |
| |
| //====================================================================================================================== |
| // MARK: - GetAddrInfo Request Kind Definition |
| |
| OS_CLOSED_OPTIONS(dx_gai_state, uint8_t, |
| dx_gai_state_null = 0, // Default null state. |
| dx_gai_state_waiting_for_a = (1U << 0), // Currently waiting for an A result. [1] |
| dx_gai_state_waiting_for_aaaa = (1U << 1), // Currently waiting for a AAAA result. [1] |
| dx_gai_state_service_allowed_failover = (1U << 2), // Got a result from a DNS service that allows failover. |
| dx_gai_state_failover_mode = (1U << 3), // Currently avoiding DNS services that allow failover. |
| dx_gai_state_avoid_suppressed_a_result = (1U << 4), // Avoiding negative results for suppressed A queries. [2] |
| ); |
| |
| #define DX_GAI_STATE_WAITING_FOR_RESULTS (dx_gai_state_waiting_for_a | dx_gai_state_waiting_for_aaaa) |
| |
| MDNS_CLANG_TREAT_WARNING_AS_ERROR_BEGIN(-Wpadded) |
| struct dx_gai_request_s { |
| struct dx_request_s base; // Request object base. |
| mdns_dns_service_id_t custom_service_id; // ID for this request's custom DNS service. |
| #if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS) |
| uint64_t powerlog_start_time; // If non-zero, time when mDNS client request started. |
| #endif |
| GetAddrInfoClientRequest * gai; // Underlying GAI request. |
| QueryRecordClientRequest * query; // Underlying SVCB/HTTPS query request. |
| dx_gai_result_t results; // List of pending results. |
| char * hostname; // Hostname C string to be resolved for getaddrinfo request. |
| mdns_domain_name_t last_domain_name; // Domain name of the most recent result. |
| mdns_xpc_string_t last_tracker_hostname; // Tracker hostname of the most recent result (XPC string). |
| mdns_xpc_string_t last_tracker_owner; // Tracker owner of the most recent result (XPC string). |
| const char * svcb_name; // If non-NULL, name of the SVCB/HTTPS record to query for. |
| uuid_t * resolver_uuid; // The resolver UUID to use for UUID-scoped requests. |
| xpc_object_t cnames_a; // Hostname's canonical names for A records (XPC array). |
| xpc_object_t cnames_aaaa; // Hostname's canonical names for AAAA records (XPC array). |
| mdns_audit_token_t delegator_token; // The delegator's audit token. |
| xpc_object_t fallback_dns_config; // Fallback DNS configuration. |
| #if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT) |
| mdns_trust_t trust; // Trust instance if status is mdns_trust_status_pending |
| #endif |
| #if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS) |
| mdns_signed_resolve_result_t signed_resolve; // Signed resolve result with which to sign results. |
| #endif |
| char * svcb_name_memory; // Memory that was allocated for svcb_name. |
| dx_gai_result_t pending_suppresed_a; // Pending negative result for a suppressed A query. [2] |
| DNSServiceFlags flags; // The request's flags parameter. |
| uint32_t ifindex; // The interface index to use for interface-scoped requests. |
| DNSServiceProtocol protocols; // Used to specify IPv4, IPv6, or any IP address types. |
| pid_t effective_pid; // Effective client PID. |
| uint16_t svcb_type; // If svcb_name is non-NULL, the type for SVCB/HTTPS query. |
| uuid_t effective_uuid; // Effective client UUID. |
| dx_gai_state_t state; // Collection of state bits. |
| mdns_gai_options_t options; // Additional request options. |
| #if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL) |
| dnssd_log_privacy_level_t log_privacy_level; // The log privacy level of this request. |
| #endif |
| bool cnames_a_changed; // True if cnames_a has changed. |
| bool cnames_aaaa_changed; // True if cnames_aaaa has changed. |
| MDNS_STRUCT_PAD_64_32(1, 1); |
| }; |
| MDNS_CLANG_TREAT_WARNING_AS_ERROR_END() |
| mdns_compile_time_max_size_check(struct dx_gai_request_s, 256); |
| |
| // Notes: |
| // 1. If a client request specifies that DNS services that allow failover be avoided if they're unable to provide |
| // at least one positive response to either the A, AAAA, or HTTPS query, then we have to wait until at least one |
| // positive result is received or until all of the underlying DNSQuestions get negative results before deciding |
| // whether to start providing results to the client (former case), or discarding the current list of pending |
| // results and restarting the underlying DNSQuestions in failover mode (latter case). |
| // |
| // 2. There are cases where an IPv6-only network is the primary network and will inadvertently prevent the |
| // resolution of IPv4-only hostnames that are in fact resolvable via a VPN's DNS service. |
| // |
| // On IPv6-only networks it usually makes no sense to bother sending A record queries to the network's DNS |
| // service because IPv4 addresses are typically unusable on such networks, or it's at least preferable to |
| // connect directly to an IPv6 address instead of to an IPv4 address through an IPv4 translation mechanism, such |
| // as 464XLAT. So sending A queries is wasteful. Therefore, as a matter of policy, DNS services associated with |
| // IPv6-only networks are typically configured to advise against sending A queries. |
| // |
| // As a DNSQuestion traverses the original QNAME's CNAME chain, it may end up switching over to a different DNS |
| // service depending on its current QNAME and the most appropriate DNS service for that QNAME according to the |
| // current DNS configuration. For example, consider a DNSQuestion that is originally for www.example.com. If |
| // there's a split-tunnel corporate VPN DNS service that's responsible for resolving everything in the |
| // example.com domain. If the VPN DNS service responds to a query for www.example.com with a CNAME mapping |
| // www.example.com to cdn.example.net, then when the DNSQuestion is restarted for cdn.example.net, it may be |
| // assigned a different DNS service, specifically if the VPN is not responsible for cdn.example.net. |
| // |
| // Suppose that the next DNS service is for an IPv6-only network not associated with the VPN. Suppose that in |
| // the public DNS there's a cdn.example.net CNAME record that points to server.example.com. Also suppose that |
| // the VPN supports IPv4 and that server.example.com is an IPv4-only host. Because the IPv6-only network's DNS |
| // service advises against A queries, the DNSQuestion is stuck at cdn.example.net if the DNSQuestion is |
| // configured to suppress queries for unusable addresses, as is usually the case. |
| // |
| // When a GAI request involves parallel A and AAAA DNSQuestions, the CNAME records placed in the cache as a |
| // result of the unsuppressed AAAA query can help the A DNSQuestion advance to the next name in the CNAME chain |
| // so that it can advance to the VPN DNS service for server.example.com for its unsuppressed A query. This is |
| // only possible if the persistWhenARecordsUnusable option is set. |
| // |
| // A caveat with the persistWhenARecordsUnusable option is that negative results that indicate that an A query |
| // was suppressed will still be generated when the A DNSQuestion is parked on a DNS service that advises against |
| // A queries. We want to avoid a client seeing a negative AAAA result along with a negative suppressed A result |
| // as the first pair of A+AAAA results because this could lead the client to give up upon getting such a |
| // negative pair when it could be the case that a more definitive A result is on the way, e.g., a positive |
| // result with an IPv4 address or some other negative result, as a consequence of the A DNSQuestion advancing to |
| // a DNS service for which A queries are not suppressed. |
| |
| typedef xpc_object_t |
| (*dx_request_take_results_f)(dx_any_request_t request); |
| |
| typedef void |
| (*dx_request_report_powerlog_progress_f)(dx_any_request_t request); |
| |
| typedef const struct dx_request_kind_s * dx_request_kind_t; |
| struct dx_request_kind_s { |
| struct dx_kind_s base; |
| dx_request_take_results_f take_results; |
| }; |
| |
| #define DX_REQUEST_SUBKIND_DEFINE(NAME, ...) \ |
| static void \ |
| _dx_ ## NAME ## _request_invalidate(dx_ ## NAME ## _request_t request); \ |
| \ |
| static void \ |
| _dx_ ## NAME ## _request_finalize(dx_ ## NAME ## _request_t request); \ |
| \ |
| static const struct dx_request_kind_s _dx_ ## NAME ## _request_kind = { \ |
| MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN() \ |
| .base = { \ |
| .superkind = &_dx_request_kind, \ |
| .invalidate = _dx_ ## NAME ## _request_invalidate, \ |
| .finalize = _dx_ ## NAME ## _request_finalize \ |
| }, \ |
| __VA_ARGS__ \ |
| MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_END() \ |
| }; \ |
| \ |
| static dx_ ## NAME ## _request_t \ |
| _dx_ ## NAME ## _request_new(void) \ |
| { \ |
| dx_ ## NAME ## _request_t obj = (dx_ ## NAME ## _request_t)mdns_calloc(1, sizeof(*obj)); \ |
| require_return_value(obj, NULL); \ |
| \ |
| _dx_object_init(obj, &_dx_ ## NAME ## _request_kind.base); \ |
| return obj; \ |
| } \ |
| DX_BASE_CHECK(NAME ## _request, request) |
| |
| static xpc_object_t |
| _dx_gai_request_take_results(dx_gai_request_t request); |
| |
| DX_REQUEST_SUBKIND_DEFINE(gai, |
| .take_results = _dx_gai_request_take_results, |
| ); |
| |
| //====================================================================================================================== |
| // MARK: - Result Kind Definition |
| |
| struct dx_gai_result_s { |
| struct dx_object_s base; // Object base. |
| dx_gai_result_t next; // Next result in list. |
| mdns_resource_record_t record; // Result's resource record. |
| mdns_xpc_string_t provider_name; // The DNS service's provider name, if any. |
| xpc_object_t cname_update; // If non-NULL, XPC array to use for a CNAME chain update. |
| mdns_xpc_string_t tracker_hostname; // If non-NULL, tracker hostname as an XPC string. |
| mdns_xpc_string_t tracker_owner; // If non-NULL, owner of the tracker hostname as an XPC string. |
| #if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS) |
| mdns_signed_hostname_result_t signed_hostname; // Signed hostname result. |
| #endif |
| mdns_extended_dns_error_t extended_dns_error; // Extended DNS Error, if any. |
| DNSServiceFlags flags; // The result's flags. |
| dnssd_negative_reason_t negative_reason; // Reason code for negative results. |
| DNSServiceErrorType error; // Error returned by mDNS core. |
| uint32_t ifindex; // The interface index associated with the result. |
| mdns_resolver_type_t protocol; // The transport protocol used to obtain the record. |
| uint16_t question_id; // ID of DNSQuestion used to get result. Used for logging. |
| unsigned tracker_is_approved : 1;// True if the associated tracker is approved for the client. |
| #if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL) |
| unsigned sensitive_logging : 1; // True if hostnames and rdata should be logged sensitively. |
| #endif |
| unsigned tracker_can_block : 1; // True if a request to this known tracker can be blocked. |
| unsigned __pad_bits : 5; |
| }; |
| mdns_compile_time_max_size_check(struct dx_gai_result_s, 104); |
| |
| static void |
| _dx_gai_result_finalize(dx_gai_result_t result); |
| |
| DX_OBJECT_SUBKIND_DEFINE(gai_result, |
| .finalize = _dx_gai_result_finalize |
| ); |
| |
| //====================================================================================================================== |
| // MARK: - Local Prototypes |
| |
| static dispatch_queue_t |
| _dx_server_queue(void); |
| |
| static void |
| _dx_server_handle_new_connection(xpc_connection_t connection); |
| |
| static void |
| _dx_server_register_session(dx_session_t session); |
| |
| static void |
| _dx_server_deregister_session(dx_session_t session); |
| |
| static void |
| _dx_server_check_sessions(void); |
| |
| static dx_session_t |
| _dx_session_create(xpc_connection_t connection); |
| |
| static void |
| _dx_session_activate(dx_session_t session); |
| |
| static void |
| _dx_session_handle_message(dx_session_t session, xpc_object_t msg); |
| |
| static DNSServiceErrorType |
| _dx_session_handle_getaddrinfo_command(dx_session_t session, xpc_object_t msg); |
| |
| static DNSServiceErrorType |
| _dx_session_handle_stop_command(dx_session_t session, xpc_object_t msg); |
| |
| static void |
| _dx_session_append_request(dx_session_t session, dx_any_request_t any); |
| |
| static void |
| _dx_session_check(dx_session_t session, uint64_t now_ticks); |
| |
| static void |
| _dx_session_send_message(dx_session_t session, xpc_object_t msg); |
| |
| static void |
| _dx_session_terminate(dx_session_t session, mdns_termination_reason_t reason); |
| |
| static void |
| _dx_session_reset_idle_timer(dx_session_t session); |
| |
| static void |
| _dx_session_log_error(dx_session_t session, DNSServiceErrorType error); |
| |
| static void |
| _dx_session_log_pending_send_count_increase(dx_session_t session); |
| |
| static void |
| _dx_session_log_pending_send_count_decrease(dx_session_t session); |
| |
| static void |
| _dx_session_log_termination(dx_session_t session, mdns_termination_reason_t reason); |
| |
| static xpc_object_t |
| _dx_request_take_results(dx_request_t request); |
| |
| typedef void (^dx_block_t)(void); |
| |
| static void |
| _dx_request_locked(dx_any_request_t request, dx_block_t block); |
| |
| static DNSServiceErrorType |
| _dx_request_get_error(dx_any_request_t request); |
| |
| static bool |
| _dx_request_set_error(dx_any_request_t request, DNSServiceErrorType error); |
| |
| static bool |
| _dx_request_send_pending_error(dx_any_request_t request); |
| |
| static dx_gai_request_t |
| _dx_gai_request_create(uint64_t command_id, dx_session_t session); |
| |
| static DNSServiceErrorType |
| _dx_gai_request_activate(dx_gai_request_t request); |
| |
| static DNSServiceErrorType |
| _dx_gai_request_start_client_requests(dx_gai_request_t request, bool need_lock); |
| |
| static DNSServiceErrorType |
| _dx_gai_request_parse_params(dx_gai_request_t request, xpc_object_t params); |
| |
| static DNSServiceErrorType |
| _dx_gai_request_start_client_requests_internal(dx_gai_request_t request, GetAddrInfoClientRequestParams *gai_params, |
| QueryRecordClientRequestParams *query_params, bool need_lock); |
| |
| static void |
| _dx_gai_request_stop_client_requests(dx_gai_request_t request, bool need_lock); |
| |
| static void |
| _dx_gai_request_restart_client_requests_in_failover_mode(dx_gai_request_t request); |
| |
| static void |
| _dx_gai_request_append_cname(dx_gai_request_t request, int qtype, const domainname *cname, bool expired); |
| |
| static xpc_object_t |
| _dx_gai_request_copy_cname_update(dx_gai_request_t request, int qtype); |
| |
| static void |
| _dx_gai_request_gai_result_handler(mDNS *m, DNSQuestion *q, const ResourceRecord *answer, mDNSBool expired, |
| QC_result qc_result, DNSServiceErrorType error, void *context); |
| |
| static void |
| _dx_gai_request_query_result_handler(mDNS *m, DNSQuestion *q, const ResourceRecord *answer, mDNSBool expired, |
| QC_result qc_result, DNSServiceErrorType error, void *context); |
| |
| static void |
| _dx_gai_request_enqueue_result(dx_gai_request_t request, QC_result qc_result, const ResourceRecord *answer, |
| bool answer_is_expired, const uint8_t *rdata_ptr, uint16_t rdata_len, DNSServiceErrorType error, |
| const DNSQuestion *q); |
| |
| #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER) |
| static const uint8_t * |
| _dx_gai_request_get_resolver_uuid(xpc_object_t params); |
| #endif |
| |
| #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN) |
| static bool |
| _dx_gai_request_is_for_in_app_browser(xpc_object_t params); |
| #endif |
| |
| static void |
| _dx_gai_request_log_start(dx_gai_request_t request, pid_t delegator_pid, const uuid_t delegator_uuid); |
| |
| static void |
| _dx_gai_request_log_stop(dx_gai_request_t request); |
| |
| static void |
| _dx_gai_request_log_error(dx_gai_request_t request, DNSServiceErrorType error); |
| |
| static bool |
| _dx_gai_request_check_for_failover_restart(dx_gai_request_t request, const ResourceRecord *answer, |
| bool answer_is_expired, bool answer_is_positive); |
| |
| static void |
| _dx_gai_result_list_forget(dx_gai_result_t *list_ptr); |
| |
| static xpc_object_t |
| _dx_gai_result_to_dictionary(dx_gai_result_t result); |
| |
| static void |
| _dx_gai_result_log(dx_gai_result_t result, uint32_t request_id); |
| |
| static void |
| _dx_kqueue_locked(const char *description, bool need_lock, dx_block_t block); |
| |
| static void |
| _dx_replace_domain_name(mdns_domain_name_t *ptr, const domainname *name); |
| |
| static bool |
| _dx_qc_result_is_add(QC_result qc_result); |
| |
| static bool |
| _dx_qc_result_is_suppressed(QC_result qc_result); |
| |
| static QueryRecordClientRequest * |
| _dx_query_record_client_request_start(const QueryRecordClientRequestParams *params, |
| QueryRecordResultHandler handler, void *context, DNSServiceErrorType *out_error); |
| |
| static void |
| _dx_query_record_client_request_forget(QueryRecordClientRequest **request_ptr); |
| |
| static GetAddrInfoClientRequest * |
| _dx_get_addr_info_client_request_start(const GetAddrInfoClientRequestParams *params, |
| QueryRecordResultHandler handler, void *context, DNSServiceErrorType *out_error); |
| |
| static void |
| _dx_get_addr_info_client_request_forget(GetAddrInfoClientRequest **request_ptr); |
| |
| //====================================================================================================================== |
| // MARK: - Logging |
| |
| MDNS_LOG_CATEGORY_DEFINE(server, "dnssd_server"); |
| |
| //====================================================================================================================== |
| // MARK: - Globals |
| |
| static dx_session_t g_session_list = NULL; |
| |
| //====================================================================================================================== |
| // MARK: - Server Functions |
| |
| mDNSexport void |
| dnssd_server_init(void) |
| { |
| static dispatch_once_t s_once = 0; |
| static xpc_connection_t s_listener = NULL; |
| #if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS) |
| static dispatch_source_t s_powerlog_progress_timer = NULL; |
| #endif |
| dispatch_once(&s_once, |
| ^{ |
| s_listener = xpc_connection_create_mach_service(DNSSD_MACH_SERVICE_NAME, _dx_server_queue(), |
| XPC_CONNECTION_MACH_SERVICE_LISTENER); |
| xpc_connection_set_event_handler(s_listener, |
| ^(xpc_object_t event) |
| { |
| if (xpc_get_type(event) == XPC_TYPE_CONNECTION) { |
| _dx_server_handle_new_connection((xpc_connection_t)event); |
| } |
| }); |
| xpc_connection_activate(s_listener); |
| #if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS) |
| const uint32_t interval_ms = 30 * MDNS_MILLISECONDS_PER_MINUTE; |
| s_powerlog_progress_timer = mdns_dispatch_create_periodic_monotonic_timer(interval_ms, 5, _dx_server_queue()); |
| if (s_powerlog_progress_timer) { |
| dispatch_source_set_event_handler(s_powerlog_progress_timer, |
| ^{ |
| os_log_debug(_mdns_server_log(), "periodic powerlog report timer fired"); |
| _dx_kqueue_locked("dnssd_server: submitting client summary to powerlog", true, |
| ^{ |
| mdns_powerlog_submit_client_summary(); |
| }); |
| }); |
| dispatch_activate(s_powerlog_progress_timer); |
| } else { |
| os_log_fault(_mdns_server_log(), "Failed to create periodic powerlog report timer"); |
| } |
| #endif |
| }); |
| } |
| |
| //====================================================================================================================== |
| |
| mDNSexport void |
| dnssd_server_idle(void) |
| { |
| static dispatch_once_t s_once = 0; |
| static dispatch_source_t s_source = NULL; |
| dispatch_once(&s_once, |
| ^{ |
| s_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0, _dx_server_queue()); |
| dispatch_source_set_event_handler(s_source, |
| ^{ |
| _dx_server_check_sessions(); |
| }); |
| dispatch_activate(s_source); |
| }); |
| dispatch_source_merge_data(s_source, 1); |
| } |
| |
| //====================================================================================================================== |
| |
| uint32_t |
| dnssd_server_get_new_request_id(void) |
| { |
| static _Atomic(uint32_t) s_next_id = 1; |
| return atomic_fetch_add(&s_next_id, 1); |
| } |
| |
| //====================================================================================================================== |
| |
| static dispatch_queue_t |
| _dx_server_queue(void) |
| { |
| static dispatch_once_t once = 0; |
| static dispatch_queue_t queue = NULL; |
| |
| dispatch_once(&once, |
| ^{ |
| const dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, |
| QOS_CLASS_USER_INITIATED, 0); |
| queue = dispatch_queue_create("com.apple.dnssd.server", attr); |
| }); |
| return queue; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_server_handle_new_connection(const xpc_connection_t connection) |
| { |
| dx_session_t session = _dx_session_create(connection); |
| if (session) { |
| _dx_session_activate(session); |
| _dx_server_register_session(session); |
| _dx_forget(&session); |
| } else { |
| const pid_t client_pid = xpc_connection_get_pid(connection); |
| char client_name[MAXCOMLEN]; |
| client_name[0] = '\0'; |
| mdns_system_pid_to_name(client_pid, client_name); |
| os_log_fault(_mdns_server_log(), |
| "Failed to create session for connection -- client pid: %d (%{public}s)", client_pid, client_name); |
| xpc_connection_cancel(connection); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_server_register_session(dx_session_t session) |
| { |
| dx_session_t *ptr = &g_session_list; |
| while (*ptr) { |
| ptr = &(*ptr)->next; |
| } |
| session->next = NULL; |
| *ptr = session; |
| _dx_retain(*ptr); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_server_deregister_session(dx_session_t session) |
| { |
| dx_session_t *ptr = &g_session_list; |
| while (*ptr && (*ptr != session)) { |
| ptr = &(*ptr)->next; |
| } |
| if (*ptr) { |
| *ptr = session->next; |
| session->next = NULL; |
| _dx_forget(&session); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_server_check_sessions(void) |
| { |
| if (g_session_list) { |
| const uint64_t now_ticks = mach_absolute_time(); |
| for (dx_session_t session = g_session_list; session; session = session->next) { |
| _dx_session_check(session, now_ticks); |
| } |
| } |
| } |
| |
| //====================================================================================================================== |
| // MARK: - Object Methods |
| |
| static void |
| _dx_recursive_init(dx_object_t object, dx_kind_t kind); |
| |
| static void |
| _dx_object_init(const dx_object_t me, const dx_kind_t kind) |
| { |
| me->kind = kind; |
| atomic_store_explicit(&me->ref_count, 1, memory_order_relaxed); |
| _dx_recursive_init(me, me->kind); |
| } |
| |
| static void |
| _dx_recursive_init(const dx_object_t me, const dx_kind_t kind) |
| { |
| if (kind->superkind) { |
| _dx_recursive_init(me, kind->superkind); |
| } |
| if (kind->init) { |
| kind->init(me); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_retain(const dx_any_t any) |
| { |
| const dx_object_t me = any.object; |
| atomic_fetch_add(&me->ref_count, 1); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_finalize(dx_object_t object); |
| |
| static void |
| _dx_release(const dx_any_t any) |
| { |
| dx_object_t me = any.object; |
| if (atomic_fetch_sub(&me->ref_count, 1) == 1) { |
| _dx_finalize(me); |
| ForgetMem(&me); |
| } |
| } |
| |
| static void |
| _dx_finalize(const dx_object_t me) |
| { |
| for (dx_kind_t kind = me->kind; kind; kind = kind->superkind) { |
| if (kind->finalize) { |
| kind->finalize(me); |
| } |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_invalidate(const dx_any_t any) |
| { |
| const dx_object_t me = any.object; |
| for (dx_kind_t kind = me->kind; kind; kind = kind->superkind) { |
| if (kind->invalidate) { |
| kind->invalidate(me); |
| return; |
| } |
| } |
| } |
| |
| //====================================================================================================================== |
| // MARK: - Session Methods |
| |
| static dx_session_t |
| _dx_session_create(const xpc_connection_t connection) |
| { |
| dx_session_t session = NULL; |
| dx_session_t obj = _dx_session_new(); |
| require_quiet(obj, exit); |
| |
| obj->connection = connection; |
| xpc_retain(obj->connection); |
| #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN) |
| audit_token_t token; |
| memset(&token, 0, sizeof(token)); |
| xpc_connection_get_audit_token(obj->connection, &token); |
| obj->peer_token = mdns_audit_token_create(&token); |
| require_quiet(obj->peer_token, exit); |
| #endif |
| |
| obj->client_pid = xpc_connection_get_pid(obj->connection); |
| obj->client_euid = xpc_connection_get_euid(obj->connection); |
| mdns_system_pid_to_name(obj->client_pid, obj->client_name); |
| session = obj; |
| obj = NULL; |
| |
| exit: |
| static_analyzer_malloc_freed(obj); // Analyzer isn't aware that obj will be freed if non-NULL. |
| _dx_forget(&obj); |
| return session; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_session_activate(const dx_session_t me) |
| { |
| _dx_retain(me); |
| xpc_connection_set_target_queue(me->connection, _dx_server_queue()); |
| xpc_connection_set_event_handler(me->connection, |
| ^(const xpc_object_t event) { |
| const xpc_type_t type = xpc_get_type(event); |
| if (type == XPC_TYPE_DICTIONARY) { |
| if (me->connection) { |
| _dx_session_handle_message(me, event); |
| } |
| } else if (event == XPC_ERROR_CONNECTION_INVALID) { |
| _dx_server_deregister_session(me); |
| _dx_session_invalidate(me); |
| _dx_release(me); |
| } else { |
| xpc_connection_forget(&me->connection); |
| } |
| }); |
| xpc_connection_activate(me->connection); |
| _dx_session_reset_idle_timer(me); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_session_invalidate(const dx_session_t me) |
| { |
| xpc_connection_forget(&me->connection); |
| dispatch_source_forget(&me->idle_timer); |
| dispatch_source_forget(&me->keepalive_reply_timer); |
| dx_request_t req; |
| while ((req = me->request_list) != NULL) |
| { |
| me->request_list = req->next; |
| _dx_invalidate(req); |
| _dx_release(req); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_session_finalize(const dx_session_t me) |
| { |
| xpc_forget(&me->connection); |
| #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN) |
| mdns_forget(&me->peer_token); |
| #endif |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_session_handle_message(const dx_session_t me, const xpc_object_t msg) |
| { |
| DNSServiceErrorType err; |
| const char * const command = dnssd_xpc_message_get_command(msg); |
| require_action_quiet(command, exit, err = kDNSServiceErr_BadParam); |
| |
| if (strcmp(command, DNSSD_COMMAND_GETADDRINFO) == 0) { |
| err = _dx_session_handle_getaddrinfo_command(me, msg); |
| } else if (strcmp(command, DNSSD_COMMAND_STOP) == 0) { |
| err = _dx_session_handle_stop_command(me, msg); |
| } else { |
| err = kDNSServiceErr_BadParam; |
| } |
| |
| exit: |
| _dx_session_reset_idle_timer(me); |
| xpc_object_t reply = xpc_dictionary_create_reply(msg); |
| if (likely(reply)) { |
| dnssd_xpc_message_set_error(reply, err); |
| _dx_session_send_message(me, reply); |
| xpc_forget(&reply); |
| } else { |
| _dx_session_terminate(me, mdns_termination_reason_client_error); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static DNSServiceErrorType |
| _dx_session_handle_getaddrinfo_command(const dx_session_t me, const xpc_object_t msg) |
| { |
| dx_gai_request_t req = NULL; |
| bool valid; |
| DNSServiceErrorType err; |
| const uint64_t command_id = dnssd_xpc_message_get_id(msg, &valid); |
| require_action_quiet(valid, exit, err = kDNSServiceErr_BadParam); |
| |
| const xpc_object_t params = dnssd_xpc_message_get_parameters(msg); |
| require_action_quiet(params, exit, err = kDNSServiceErr_BadParam); |
| |
| req = _dx_gai_request_create(command_id, me); |
| require_action_quiet(req, exit, err = kDNSServiceErr_NoMemory); |
| |
| err = _dx_gai_request_parse_params(req, params); |
| require_noerr_quiet(err, exit); |
| |
| err = _dx_gai_request_activate(req); |
| require_noerr_quiet(err, exit); |
| |
| _dx_session_append_request(me, req); |
| |
| exit: |
| if (err) { |
| if (req) { |
| _dx_gai_request_log_error(req, err); |
| } else { |
| _dx_session_log_error(me, err); |
| } |
| } |
| _dx_forget(&req); |
| return err; |
| } |
| |
| //====================================================================================================================== |
| |
| static DNSServiceErrorType |
| _dx_session_handle_stop_command(const dx_session_t me, const xpc_object_t msg) |
| { |
| bool valid; |
| DNSServiceErrorType err; |
| const uint64_t command_id = dnssd_xpc_message_get_id(msg, &valid); |
| require_action_quiet(valid, exit, err = kDNSServiceErr_BadParam); |
| |
| dx_request_t *ptr; |
| dx_request_t req; |
| for (ptr = &me->request_list; (req = *ptr) != NULL; ptr = &req->next) { |
| if (req->command_id == command_id) { |
| break; |
| } |
| } |
| require_action_quiet(req, exit, err = kDNSServiceErr_BadReference); |
| |
| *ptr = req->next; |
| req->next = NULL; |
| _dx_invalidate(req); |
| _dx_forget(&req); |
| err = kDNSServiceErr_NoError; |
| |
| exit: |
| return err; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_session_append_request(const dx_session_t me, const dx_any_request_t any) |
| { |
| dx_request_t *ptr = &me->request_list; |
| while (*ptr) { |
| ptr = &(*ptr)->next; |
| } |
| const dx_request_t req = any.request; |
| req->next = NULL; |
| *ptr = req; |
| _dx_retain(*ptr); |
| } |
| |
| //====================================================================================================================== |
| |
| #define DX_SESSION_BACK_PRESSURE_TIMEOUT_SECS 5 |
| |
| static void |
| _dx_session_check(const dx_session_t me, const uint64_t now_ticks) |
| { |
| require_return(me->connection); |
| |
| xpc_object_t results = NULL; |
| mdns_termination_reason_t terminate_reason; |
| if (me->pending_send_count > 0) { |
| const uint64_t elapsed_secs = (now_ticks - me->pending_send_start_ticks) / mdns_mach_ticks_per_second(); |
| require_action_quiet(elapsed_secs < DX_SESSION_BACK_PRESSURE_TIMEOUT_SECS, exit, |
| terminate_reason = mdns_termination_reason_back_pressure); |
| } |
| for (dx_request_t req = me->request_list; req; req = req->next) { |
| results = _dx_request_take_results(req); |
| if (results) { |
| xpc_object_t msg = xpc_dictionary_create(NULL, NULL, 0); |
| require_action_quiet(msg, exit, terminate_reason = mdns_termination_reason_server_error); |
| |
| dnssd_xpc_message_set_id(msg, req->command_id); |
| dnssd_xpc_message_set_error(msg, kDNSServiceErr_NoError); |
| dnssd_xpc_message_set_results(msg, results); |
| xpc_forget(&results); |
| _dx_session_send_message(me, msg); |
| xpc_forget(&msg); |
| } |
| const bool ok = _dx_request_send_pending_error(req); |
| require_action_quiet(ok, exit, terminate_reason = mdns_termination_reason_server_error); |
| } |
| terminate_reason = mdns_termination_reason_none; |
| |
| exit: |
| if (terminate_reason != mdns_termination_reason_none) { |
| _dx_session_terminate(me, terminate_reason); |
| } |
| xpc_forget(&results); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_session_send_message(const dx_session_t me, const xpc_object_t msg) |
| { |
| require_return(me->connection); |
| |
| xpc_connection_send_message(me->connection, msg); |
| ++me->pending_send_count; |
| if (me->pending_send_count == 1) { |
| me->pending_send_start_ticks = mach_absolute_time(); |
| } else { |
| if (me->pending_send_count == 2) { |
| me->log_pending_send_counts = true; |
| } |
| _dx_session_log_pending_send_count_increase(me); |
| } |
| _dx_retain(me); |
| xpc_connection_send_barrier(me->connection, |
| ^{ |
| --me->pending_send_count; |
| if (me->log_pending_send_counts) { |
| _dx_session_log_pending_send_count_decrease(me); |
| } |
| if (me->pending_send_count == 0) { |
| me->log_pending_send_counts = false; |
| } |
| _dx_release(me); |
| }); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_session_terminate(const dx_session_t me, const mdns_termination_reason_t reason) |
| { |
| if (!me->terminated) { |
| _dx_session_log_termination(me, reason); |
| xpc_connection_forget(&me->connection); |
| me->terminated = true; |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static dispatch_source_t |
| _dx_create_oneshot_timer(const uint32_t interval_ms, const unsigned int leeway_percent_numerator) |
| { |
| const dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _dx_server_queue()); |
| require_quiet(timer, exit); |
| |
| const unsigned int numerator = Min(leeway_percent_numerator, 100); |
| const uint64_t leeway_ns = interval_ms * (numerator * (UINT64_C_safe(kNanosecondsPerMillisecond) / 100)); |
| dispatch_source_set_timer(timer, dispatch_time_milliseconds(interval_ms), DISPATCH_TIME_FOREVER, leeway_ns); |
| |
| exit: |
| return timer; |
| } |
| |
| //====================================================================================================================== |
| |
| #define DX_SESSION_KEEPALIVE_TIMEOUT_MS (5 * kMillisecondsPerSecond) |
| |
| static void |
| _dx_session_send_keepalive_message(const dx_session_t me) |
| { |
| require_return(me->connection && !me->keepalive_reply_timer); |
| |
| static xpc_object_t s_keepalive_msg = NULL; |
| if (!s_keepalive_msg) { |
| s_keepalive_msg = xpc_dictionary_create_empty(); |
| require_return(s_keepalive_msg); |
| |
| dnssd_xpc_message_set_command(s_keepalive_msg, DNSSD_COMMAND_KEEPALIVE); |
| } |
| me->keepalive_reply_timer = _dx_create_oneshot_timer(DX_SESSION_KEEPALIVE_TIMEOUT_MS, 5); |
| require_return(me->keepalive_reply_timer); |
| |
| _dx_retain(me); |
| xpc_connection_send_message_with_reply(me->connection, s_keepalive_msg, _dx_server_queue(), |
| ^(xpc_object_t reply) |
| { |
| if (me->connection && (xpc_get_type(reply) == XPC_TYPE_DICTIONARY)) { |
| dispatch_source_forget(&me->keepalive_reply_timer); |
| _dx_session_reset_idle_timer(me); |
| } |
| _dx_release(me); |
| }); |
| dispatch_source_set_event_handler(me->keepalive_reply_timer, |
| ^{ |
| dispatch_source_forget(&me->keepalive_reply_timer); |
| _dx_session_terminate(me, mdns_termination_reason_keepalive_timeout); |
| }); |
| dispatch_activate(me->keepalive_reply_timer); |
| } |
| |
| //====================================================================================================================== |
| |
| #define DX_SESSION_IDLE_TIMEOUT_WITH_REQUESTS_MS (1 * kSecondsPerMinute * kMillisecondsPerSecond) |
| #define DX_SESSION_IDLE_TIMEOUT_WITHOUT_REQUESTS_MS (5 * kSecondsPerMinute * kMillisecondsPerSecond) |
| |
| static void |
| _dx_session_reset_idle_timer(const dx_session_t me) |
| { |
| require_return(!me->keepalive_reply_timer); |
| |
| dispatch_source_forget(&me->idle_timer); |
| uint32_t idle_interval_ms; |
| if (me->request_list) { |
| idle_interval_ms = DX_SESSION_IDLE_TIMEOUT_WITH_REQUESTS_MS; |
| } else { |
| idle_interval_ms = DX_SESSION_IDLE_TIMEOUT_WITHOUT_REQUESTS_MS; |
| } |
| me->idle_timer = _dx_create_oneshot_timer(idle_interval_ms, 5); |
| require_return(me->idle_timer); |
| |
| dispatch_source_set_event_handler(me->idle_timer, |
| ^{ |
| dispatch_source_forget(&me->idle_timer); |
| if (me->request_list) { |
| _dx_session_send_keepalive_message(me); |
| } else { |
| _dx_session_terminate(me, mdns_termination_reason_idle); |
| } |
| }); |
| dispatch_activate(me->idle_timer); |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| _dx_session_has_entitlement(const dx_session_t me, const char * const entitlement) |
| { |
| bool entitled = false; |
| if (me->connection) { |
| entitled = mdns_xpc_connection_is_entitled(me->connection, entitlement); |
| } |
| return entitled; |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| _dx_session_has_delegate_entitlement(const dx_session_t me) |
| { |
| return _dx_session_has_entitlement(me, "com.apple.private.network.socket-delegate"); |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| _dx_session_has_prohibit_encrypted_dns_entitlement(const dx_session_t me) |
| { |
| return _dx_session_has_entitlement(me, "com.apple.private.dnssd.prohibit-encrypted-dns"); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_session_log_error(const dx_session_t me, const DNSServiceErrorType error) |
| { |
| os_log_error(_mdns_server_log(), |
| "XPC session error -- error: %{mdns:err}ld, client pid: %lld (%{public}s)", |
| (long)error, (long long)me->client_pid, me->client_name); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_session_log_pending_send_count_increase(const dx_session_t me) |
| { |
| os_log_debug(_mdns_server_log(), |
| "XPC session to client with pid %lld (%{public}s) pending send count increased to %d", |
| (long long)me->client_pid, me->client_name, me->pending_send_count); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_session_log_pending_send_count_decrease(const dx_session_t me) |
| { |
| os_log_debug(_mdns_server_log(), |
| "XPC session to client with pid %lld (%{public}s) pending send count decreased to %d", |
| (long long)me->client_pid, me->client_name, me->pending_send_count); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_session_log_termination(const dx_session_t me, const mdns_termination_reason_t reason) |
| { |
| os_log_with_type(_mdns_server_log(), |
| (reason == mdns_termination_reason_idle) ? OS_LOG_TYPE_INFO : OS_LOG_TYPE_DEFAULT, |
| "Session terminated -- reason: %{mdns:termination_reason}d, pending send count: %u, client pid: %lld " |
| "(%{public}s)", |
| reason, me->pending_send_count, (long long)me->client_pid, me->client_name); |
| } |
| |
| //====================================================================================================================== |
| // MARK: - Request Methods |
| |
| static void |
| _dx_request_init(const dx_request_t me) |
| { |
| me->request_id = dnssd_server_get_new_request_id(); |
| me->lock = OS_UNFAIR_LOCK_INIT; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_request_finalize(const dx_request_t me) |
| { |
| _dx_forget(&me->session); |
| xpc_forget(&me->results); |
| } |
| |
| //====================================================================================================================== |
| |
| static xpc_object_t |
| _dx_request_take_results(const dx_request_t me) |
| { |
| const dx_request_kind_t kind = (dx_request_kind_t)me->base.kind; |
| if (kind->take_results) { |
| return kind->take_results(me); |
| } else { |
| return NULL; |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_request_locked(const dx_any_request_t any, const dx_block_t block) |
| { |
| const dx_request_t me = any.request; |
| os_unfair_lock_lock(&me->lock); |
| block(); |
| os_unfair_lock_unlock(&me->lock); |
| } |
| |
| //====================================================================================================================== |
| |
| static DNSServiceErrorType |
| _dx_request_get_error(const dx_any_request_t any) |
| { |
| const dx_request_t me = any.request; |
| __block DNSServiceErrorType error; |
| _dx_request_locked(me, |
| ^{ |
| error = me->error; |
| }); |
| return error; |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| _dx_request_set_error(const dx_any_request_t any, const DNSServiceErrorType error) |
| { |
| __block bool did_set = false; |
| if (error) { |
| const dx_request_t me = any.request; |
| _dx_request_locked(me, |
| ^{ |
| if (!me->error) { |
| me->error = error; |
| did_set = true; |
| } |
| }); |
| } |
| return did_set; |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| _dx_request_send_pending_error(const dx_any_request_t any) |
| { |
| bool ok = false; |
| const dx_request_t me = any.request; |
| const DNSServiceErrorType error = _dx_request_get_error(me); |
| if (error && !me->sent_error) { |
| xpc_object_t msg = xpc_dictionary_create(NULL, NULL, 0); |
| require_quiet(msg, exit); |
| |
| dnssd_xpc_message_set_id(msg, me->command_id); |
| dnssd_xpc_message_set_error(msg, error); |
| _dx_session_send_message(me->session, msg); |
| xpc_forget(&msg); |
| me->sent_error = true; |
| } |
| ok = true; |
| |
| exit: |
| return ok; |
| } |
| |
| //====================================================================================================================== |
| // MARK: - GetAddrInfo Request Methods |
| |
| static dx_gai_request_t |
| _dx_gai_request_create(const uint64_t command_id, const dx_session_t session) |
| { |
| dx_gai_request_t obj = _dx_gai_request_new(); |
| require_quiet(obj, exit); |
| |
| obj->base.command_id = command_id; |
| obj->base.session = session; |
| _dx_retain(obj->base.session); |
| |
| exit: |
| return obj; |
| } |
| |
| //====================================================================================================================== |
| |
| #if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT) |
| static DNSServiceErrorType |
| _dx_gai_request_trust_check(dx_gai_request_t request, bool *out_defer_start); |
| #endif |
| |
| static DNSServiceErrorType |
| _dx_gai_request_activate(const dx_gai_request_t me) |
| { |
| DNSServiceErrorType err; |
| bool defer_start = false; |
| #if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT) |
| bool privacy_check_done = false; |
| #if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS) |
| privacy_check_done = (me->signed_resolve != NULL); |
| #endif |
| if (!privacy_check_done && os_feature_enabled(mDNSResponder, bonjour_privacy)) { |
| err = _dx_gai_request_trust_check(me, &defer_start); |
| require_noerr_quiet(err, exit); |
| } |
| #endif |
| if (!defer_start) { |
| err = _dx_gai_request_start_client_requests(me, true); |
| require_noerr_quiet(err, exit); |
| } |
| err = kDNSServiceErr_NoError; |
| |
| exit: |
| return err; |
| } |
| |
| #if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT) |
| static DNSServiceErrorType |
| _dx_gai_request_trust_check(const dx_gai_request_t me, bool * const out_defer_start) |
| { |
| DNSServiceErrorType err; |
| bool defer_start = false; |
| const dx_session_t session = me->base.session; |
| const audit_token_t *const token = mdns_audit_token_get_token(session->peer_token); |
| mdns_trust_flags_t flags = mdns_trust_flags_none; |
| const mdns_trust_status_t status = mdns_trust_check_getaddrinfo(*token, me->hostname, &flags); |
| switch (status) { |
| case mdns_trust_status_granted: |
| err = kDNSServiceErr_NoError; |
| break; |
| |
| case mdns_trust_status_denied: |
| case mdns_trust_status_pending: |
| me->trust = mdns_trust_create(*token, NULL, flags); |
| require_action_quiet(me->trust, exit, err = kDNSServiceErr_NoMemory); |
| |
| _dx_retain(me); |
| mdns_trust_set_queue(me->trust, _dx_server_queue()); |
| mdns_trust_set_event_handler(me->trust, |
| ^(const mdns_trust_event_t event, const mdns_trust_status_t update) |
| { |
| if (me->trust && (event == mdns_trust_event_result)) { |
| DNSServiceErrorType handler_err; |
| if (update == mdns_trust_status_granted) { |
| handler_err = _dx_gai_request_start_client_requests(me, true); |
| } else { |
| handler_err = kDNSServiceErr_PolicyDenied; |
| } |
| if (handler_err && _dx_request_set_error(me, handler_err)) { |
| _dx_gai_request_log_error(me, handler_err); |
| _dx_request_send_pending_error(me); |
| } |
| } |
| mdns_forget(&me->trust); |
| _dx_release(me); |
| }); |
| mdns_trust_activate(me->trust); |
| defer_start = true; |
| err = kDNSServiceErr_NoError; |
| break; |
| |
| case mdns_trust_status_no_entitlement: |
| err = kDNSServiceErr_NoAuth; |
| break; |
| |
| MDNS_COVERED_SWITCH_DEFAULT: |
| err = kDNSServiceErr_Unknown; |
| break; |
| } |
| |
| exit: |
| if (out_defer_start) { |
| *out_defer_start = defer_start; |
| } |
| return err; |
| } |
| #endif |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_gai_request_invalidate(const dx_gai_request_t me) |
| { |
| _dx_gai_request_log_stop(me); |
| _dx_gai_request_stop_client_requests(me, true); |
| #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER) |
| if (me->custom_service_id != MDNS_DNS_SERVICE_INVALID_ID) { |
| Querier_DeregisterCustomDNSService(me->custom_service_id); |
| me->custom_service_id = MDNS_DNS_SERVICE_INVALID_ID; |
| } |
| #endif |
| #if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT) |
| mdns_trust_forget_with_invalidation(&me->trust); |
| #endif |
| #if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS) |
| mdns_forget(&me->signed_resolve); |
| #endif |
| mdns_forget(&me->last_domain_name); |
| mdns_xpc_string_forget(&me->last_tracker_hostname); |
| mdns_xpc_string_forget(&me->last_tracker_owner); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_gai_request_finalize(const dx_gai_request_t me) |
| { |
| _dx_gai_result_list_forget(&me->results); |
| ForgetMem(&me->hostname); |
| ForgetMem(&me->svcb_name_memory); |
| xpc_forget(&me->cnames_a); |
| xpc_forget(&me->cnames_aaaa); |
| mdns_forget(&me->delegator_token); |
| xpc_forget(&me->fallback_dns_config); |
| ForgetMem(&me->resolver_uuid); |
| _dx_forget(&me->pending_suppresed_a); |
| } |
| |
| //====================================================================================================================== |
| |
| static DNSServiceErrorType |
| _dx_gai_request_parse_params(const dx_gai_request_t me, const xpc_object_t params) |
| { |
| DNSServiceErrorType err; |
| #if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS) |
| mdns_signed_resolve_result_t signed_resolve = NULL; |
| #endif |
| const char * const hostname = dnssd_xpc_parameters_get_hostname(params); |
| require_action_quiet(hostname, exit, err = kDNSServiceErr_BadParam); |
| |
| me->hostname = mdns_strdup(hostname); |
| require_action_quiet(me->hostname, exit, err = kDNSServiceErr_NoMemory); |
| |
| bool valid; |
| me->flags = dnssd_xpc_parameters_get_flags(params, &valid); |
| require_action_quiet(valid, exit, err = kDNSServiceErr_BadParam); |
| |
| me->ifindex = dnssd_xpc_parameters_get_interface_index(params, &valid); |
| require_action_quiet(valid, exit, err = kDNSServiceErr_BadParam); |
| |
| me->protocols = dnssd_xpc_parameters_get_protocols(params, &valid); |
| require_action_quiet(valid, exit, err = kDNSServiceErr_BadParam); |
| |
| const dx_session_t session = me->base.session; |
| |
| // Get delegator IDs. |
| pid_t delegator_pid; |
| const uint8_t *delegator_uuid; |
| audit_token_t storage; |
| const audit_token_t * const delegator_token = dnssd_xpc_parameters_get_delegate_audit_token(params, &storage); |
| if (delegator_token) { |
| me->delegator_token = mdns_audit_token_create(delegator_token); |
| require_action_quiet(me->delegator_token, exit, err = kDNSServiceErr_NoMemory); |
| } |
| if (me->delegator_token) { |
| delegator_pid = mdns_audit_token_get_pid(me->delegator_token); |
| delegator_uuid = NULL; |
| } else { |
| delegator_uuid = dnssd_xpc_parameters_get_delegate_uuid(params); |
| if (delegator_uuid) { |
| delegator_pid = 0; |
| } else { |
| delegator_pid = dnssd_xpc_parameters_get_delegate_pid(params, NULL); |
| } |
| } |
| if (me->delegator_token || delegator_uuid || (delegator_pid != 0)) { |
| const bool entitled = _dx_session_has_delegate_entitlement(session); |
| require_action_quiet(entitled, exit, err = kDNSServiceErr_NoAuth); |
| } |
| |
| // Determine effective IDs. |
| // Note: The mDNS core requires that the effective PID be set to zero if the effective UUID is the primary ID. |
| if (delegator_uuid) { |
| uuid_copy(me->effective_uuid, delegator_uuid); |
| me->effective_pid = 0; |
| } else { |
| uuid_clear(me->effective_uuid); |
| me->effective_pid = (delegator_pid != 0) ? delegator_pid : session->client_pid; |
| } |
| |
| // Determine if an SVCB or HTTPS query is necessary. |
| const char * const service_scheme = dnssd_xpc_parameters_get_service_scheme(params); |
| if (service_scheme) { |
| if (strcasecmp(service_scheme, "_443._https") == 0) { |
| me->svcb_name = me->hostname; |
| me->svcb_type = kDNSType_HTTPS; |
| } else { |
| asprintf(&me->svcb_name_memory, "%s.%s", service_scheme, me->hostname); |
| require_action_quiet(me->svcb_name_memory, exit, err = kDNSServiceErr_NoMemory); |
| |
| me->svcb_name = me->svcb_name_memory; |
| me->svcb_type = kDNSType_SVCB; |
| } |
| } |
| me->fallback_dns_config = dnssd_xpc_parameters_get_fallback_config(params); |
| if (me->fallback_dns_config) { |
| xpc_retain(me->fallback_dns_config); |
| } |
| const uint8_t * const resolver_uuid = _dx_gai_request_get_resolver_uuid(params); |
| if (resolver_uuid) { |
| me->resolver_uuid = (uuid_t *)mdns_memdup(resolver_uuid, sizeof(*me->resolver_uuid)); |
| require_action_quiet(me->resolver_uuid, exit, err = kDNSServiceErr_NoMemory); |
| } |
| if (dnssd_xpc_parameters_get_need_encrypted_query(params)) { |
| me->options |= mdns_gai_option_need_encryption; |
| } |
| #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN) |
| if (_dx_gai_request_is_for_in_app_browser(params)) { |
| me->options |= mdns_gai_option_in_app_browser; |
| } |
| #endif |
| if (dnssd_xpc_parameters_get_use_failover(params)) { |
| me->options |= mdns_gai_option_use_failover; |
| } |
| if (me->options & mdns_gai_option_use_failover) { |
| if ((me->protocols & kDNSServiceProtocol_IPv4) || !(me->protocols & kDNSServiceProtocol_IPv6)) { |
| me->state |= dx_gai_state_waiting_for_a; |
| } |
| if ((me->protocols & kDNSServiceProtocol_IPv6) || !(me->protocols & kDNSServiceProtocol_IPv4)) { |
| me->state |= dx_gai_state_waiting_for_aaaa; |
| } |
| } |
| #if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL) |
| me->log_privacy_level = dnssd_xpc_parameters_get_log_privacy_level(params); |
| #endif |
| if (dnssd_xpc_parameters_get_prohibit_encrypted_dns(params)) { |
| const bool entitled = _dx_session_has_prohibit_encrypted_dns_entitlement(session); |
| require_action_quiet(entitled, exit, err = kDNSServiceErr_NoAuth); |
| |
| me->options |= mdns_gai_option_prohibit_encrypted_dns; |
| } |
| #if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS) |
| size_t signed_data_len; |
| const uint8_t * const signed_data = dnssd_xpc_parameters_get_validation_data(params, &signed_data_len); |
| if (signed_data) { |
| // Get signed_result data and validate |
| OSStatus create_err; |
| signed_resolve = mdns_signed_resolve_result_create_from_data(signed_data, signed_data_len, &create_err); |
| require_action_quiet(signed_resolve, exit, err = kDNSServiceErr_Invalid; os_log_error(_mdns_server_log(), |
| "[R%u] Failed to create signed resolve result from data: %{mdns:err}ld", |
| me->base.request_id, (long)create_err)); |
| |
| // Use signed result to verify params otherwise don't set the signed result instance to fallback to trust |
| if (mdns_signed_resolve_result_contains(signed_resolve, me->hostname, me->ifindex)) { |
| const bool allowed = mdns_system_is_signed_result_uuid_valid(mdns_signed_result_get_uuid(signed_resolve)); |
| require_action_quiet(allowed, exit, err = kDNSServiceErr_PolicyDenied; os_log_error(_mdns_server_log(), |
| "[R%u] Signed result UUID revoked.", me->base.request_id)); |
| |
| os_log_debug(_mdns_server_log(), "[R%u] Allowing signed result", me->base.request_id); |
| me->signed_resolve = signed_resolve; |
| signed_resolve = NULL; |
| } else { |
| os_log_error(_mdns_server_log(), |
| "[R%u] Signed resolve result does not cover request -- hostname: %{private,mask.hash}s, ifindex: %u", |
| me->base.request_id, me->hostname, me->ifindex); |
| } |
| } |
| #endif |
| _dx_gai_request_log_start(me, delegator_pid, delegator_uuid); |
| err = kDNSServiceErr_NoError; |
| |
| exit: |
| #if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS) |
| mdns_forget(&signed_resolve); |
| #endif |
| return err; |
| } |
| |
| //====================================================================================================================== |
| |
| static dx_gai_result_t |
| _dx_gai_request_take_expired_results(dx_gai_request_t request); |
| |
| static xpc_object_t |
| _dx_gai_request_take_results(const dx_gai_request_t me) |
| { |
| DNSServiceErrorType err; |
| xpc_object_t result_array = NULL; |
| __block dx_gai_result_t result_list = NULL; |
| _dx_request_locked(me, |
| ^{ |
| if (me->state & DX_GAI_STATE_WAITING_FOR_RESULTS) { |
| result_list = _dx_gai_request_take_expired_results(me); |
| } else { |
| result_list = me->results; |
| me->results = NULL; |
| } |
| }); |
| if (result_list) { |
| result_array = xpc_array_create(NULL, 0); |
| require_action_quiet(result_array, exit, err = kDNSServiceErr_NoMemory); |
| |
| dx_gai_result_t result; |
| while ((result = result_list) != NULL) { |
| xpc_object_t result_dict = _dx_gai_result_to_dictionary(result); |
| require_action_quiet(result_dict, exit, err = kDNSServiceErr_NoMemory); |
| |
| _dx_gai_result_log(result, me->base.request_id); |
| result_list = result->next; |
| _dx_forget(&result); |
| |
| xpc_array_append_value(result_array, result_dict); |
| xpc_forget(&result_dict); |
| } |
| } |
| err = kDNSServiceErr_NoError; |
| |
| exit: |
| _dx_gai_result_list_forget(&result_list); |
| if (err) { |
| _dx_request_set_error(me, err); |
| } |
| return result_array; |
| } |
| |
| static dx_gai_result_t |
| _dx_gai_request_take_expired_results(const dx_gai_request_t me) |
| { |
| dx_gai_result_t expired_results = NULL; |
| dx_gai_result_t *expired_ptr = &expired_results; |
| dx_gai_result_t result; |
| dx_gai_result_t *ptr = &me->results; |
| while ((result = *ptr) != NULL) { |
| if (result->flags & kDNSServiceFlagsExpiredAnswer) { |
| *ptr = result->next; |
| result->next = NULL; |
| *expired_ptr = result; |
| expired_ptr = &result->next; |
| } else { |
| ptr = &result->next; |
| } |
| } |
| return expired_results; |
| } |
| |
| //====================================================================================================================== |
| |
| static DNSServiceErrorType |
| _dx_gai_request_start_client_requests_internal(const dx_gai_request_t me, |
| GetAddrInfoClientRequestParams * const gai_params, QueryRecordClientRequestParams * const query_params, |
| const bool need_lock) |
| { |
| __block DNSServiceErrorType err = kDNSServiceErr_NoError; |
| _dx_kqueue_locked("dx_gai_request: starting client requests", need_lock, |
| ^{ |
| #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER) |
| if (me->resolver_uuid && !uuid_is_null(*me->resolver_uuid)) { |
| Querier_RegisterPathResolver(*me->resolver_uuid); |
| } |
| if ((me->custom_service_id == MDNS_DNS_SERVICE_INVALID_ID) && me->fallback_dns_config) { |
| me->custom_service_id = Querier_RegisterCustomDNSService(me->fallback_dns_config); |
| } |
| if (gai_params) { |
| gai_params->resolverUUID = *me->resolver_uuid; |
| gai_params->customID = me->custom_service_id; |
| } |
| if (query_params) { |
| query_params->resolverUUID = *me->resolver_uuid; |
| query_params->customID = me->custom_service_id; |
| } |
| #endif |
| // If present, run the query for SVCB/HTTPS first, in case the ALPN and address hints come back first. |
| if (query_params && !me->query) { |
| me->query = _dx_query_record_client_request_start(query_params, _dx_gai_request_query_result_handler, me, |
| &err); |
| require_noerr_return(err); |
| } |
| // Run the A/AAAA lookup. |
| if (gai_params && !me->gai) { |
| me->gai = _dx_get_addr_info_client_request_start(gai_params, _dx_gai_request_gai_result_handler, me, &err); |
| require_noerr_return(err); |
| #if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS) |
| if (me->gai) { |
| const domainname *const qname = GetAddrInfoClientRequestGetQName(me->gai); |
| if ((me->ifindex != kDNSServiceInterfaceIndexLocalOnly) && IsLocalDomain(qname)) { |
| const char * const client_name = me->base.session->client_name; |
| const mDNSBool uses_awdl = ClientRequestUsesAWDL(me->ifindex, me->flags); |
| me->powerlog_start_time = mdns_powerlog_getaddrinfo_start(client_name, uses_awdl); |
| } |
| } |
| #endif |
| } |
| }); |
| if (err) { |
| _dx_gai_request_stop_client_requests(me, need_lock); |
| } |
| return err; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_gai_request_stop_client_requests(const dx_gai_request_t me, const bool need_lock) |
| { |
| _dx_kqueue_locked("dx_gai_request: stopping client requests", need_lock, |
| ^{ |
| _dx_get_addr_info_client_request_forget(&me->gai); |
| #if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS) |
| if (me->powerlog_start_time != 0) { |
| const char * const client_name = me->base.session->client_name; |
| const mDNSBool uses_awdl = ClientRequestUsesAWDL(me->ifindex, me->flags); |
| mdns_powerlog_getaddrinfo_stop(client_name, me->powerlog_start_time, uses_awdl); |
| me->powerlog_start_time = 0; |
| } |
| #endif |
| _dx_query_record_client_request_forget(&me->query); |
| }); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_gai_request_restart_client_requests_in_failover_mode(const dx_gai_request_t me) |
| { |
| if (!(me->state & dx_gai_state_failover_mode)) { |
| _dx_gai_request_stop_client_requests(me, false); |
| os_log(_mdns_server_log(), "[R%u] getaddrinfo failover restart", me->base.request_id); |
| me->state |= dx_gai_state_failover_mode; |
| _dx_gai_request_start_client_requests(me, false); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static xpc_object_t * |
| _dx_gai_request_get_cnames_ptr(const dx_gai_request_t me, const int qtype, bool ** const out_changed_ptr) |
| { |
| xpc_object_t *cnames_ptr; |
| bool *changed_ptr; |
| switch (qtype) { |
| case kDNSServiceType_A: |
| cnames_ptr = &me->cnames_a; |
| changed_ptr = &me->cnames_a_changed; |
| break; |
| |
| case kDNSServiceType_AAAA: |
| cnames_ptr = &me->cnames_aaaa; |
| changed_ptr = &me->cnames_aaaa_changed; |
| break; |
| |
| default: |
| cnames_ptr = NULL; |
| changed_ptr = NULL; |
| break; |
| } |
| if (out_changed_ptr) { |
| *out_changed_ptr = changed_ptr; |
| } |
| return cnames_ptr; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_gai_request_reset_cnames(const dx_gai_request_t me, const int qtype) |
| { |
| bool *changed_ptr; |
| xpc_object_t * const cnames_ptr = _dx_gai_request_get_cnames_ptr(me, qtype, &changed_ptr); |
| require_quiet(cnames_ptr, exit); |
| |
| xpc_forget(cnames_ptr); |
| *cnames_ptr = xpc_array_create_empty(); |
| *changed_ptr = true; |
| |
| exit: |
| return; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_gai_request_append_cname(const dx_gai_request_t me, const int qtype, const domainname * const cname, |
| const bool expired) |
| { |
| bool *changed_ptr; |
| xpc_object_t * const cnames_ptr = _dx_gai_request_get_cnames_ptr(me, qtype, &changed_ptr); |
| require_quiet(cnames_ptr, exit); |
| |
| const char *cname_str = NULL; |
| char cname_buf[MAX_ESCAPED_DOMAIN_NAME]; |
| if (cname) { |
| if (!ConvertDomainNameToCString(cname, cname_buf)) { |
| cname_buf[0] = '\0'; |
| } |
| cname_str = cname_buf; |
| } |
| _dx_request_locked(me, |
| ^{ |
| if (cname_str) { |
| xpc_object_t cnames = *cnames_ptr; |
| if (!cnames) { |
| cnames = xpc_array_create(NULL, 0); |
| *cnames_ptr = cnames; |
| } |
| if (cnames) { |
| xpc_array_set_string(cnames, XPC_ARRAY_APPEND, cname_str); |
| *changed_ptr = true; |
| } |
| } |
| }); |
| if ((me->state & dx_gai_state_avoid_suppressed_a_result) && !expired) { |
| // An intermediate CNAME result for the A DNSQuestion means that the last pending negative result is no |
| // longer relevant, so it can be dropped. |
| if (cname && (qtype == kDNSType_A)) { |
| _dx_forget(&me->pending_suppresed_a); |
| } |
| } |
| |
| exit: |
| return; |
| } |
| |
| //====================================================================================================================== |
| |
| static xpc_object_t |
| _dx_gai_request_copy_cname_update(const dx_gai_request_t me, const int qtype) |
| { |
| __block xpc_object_t result = NULL; |
| bool *changed_ptr; |
| xpc_object_t * const cnames_ptr = _dx_gai_request_get_cnames_ptr(me, qtype, &changed_ptr); |
| require_quiet(cnames_ptr, exit); |
| |
| _dx_request_locked(me, |
| ^{ |
| if (*changed_ptr) { |
| const xpc_object_t cnames = *cnames_ptr; |
| if (cnames) { |
| result = xpc_copy(cnames); |
| } |
| *changed_ptr = false; |
| } |
| }); |
| |
| exit: |
| return result; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_gai_request_failover_check_gai_answer(dx_gai_request_t request, const ResourceRecord *answer); |
| |
| static void |
| _dx_gai_request_gai_result_handler(mDNS * const m, DNSQuestion * const q, const ResourceRecord * const answer, |
| const mDNSBool expired, const QC_result qc_result, const DNSServiceErrorType error, void * const context) |
| { |
| (void)m; |
| bool failover_restart = false; |
| const dx_gai_request_t me = (dx_gai_request_t)context; |
| if (!error || (error == kDNSServiceErr_NoSuchRecord)) { |
| _dx_gai_request_failover_check_gai_answer(me, answer); |
| if (q->CNAMEReferrals == 0) { |
| _dx_gai_request_reset_cnames(me, q->qtype); |
| } |
| if (answer->rrtype == kDNSServiceType_CNAME) { |
| require_quiet(!error, exit); |
| |
| _dx_gai_request_append_cname(me, q->qtype, &answer->rdata->u.name, expired); |
| } |
| require_quiet((answer->rrtype == kDNSServiceType_A) || (answer->rrtype == kDNSServiceType_AAAA), exit); |
| |
| const uint8_t *rdata_ptr; |
| uint16_t rdata_len; |
| if (!error) { |
| if (answer->rrtype == kDNSServiceType_A) { |
| rdata_ptr = answer->rdata->u.ipv4.b; |
| rdata_len = 4; |
| } else { |
| rdata_ptr = answer->rdata->u.ipv6.b; |
| rdata_len = 16; |
| } |
| } else { |
| rdata_ptr = NULL; |
| rdata_len = 0; |
| } |
| failover_restart = _dx_gai_request_check_for_failover_restart(me, answer, expired, rdata_len > 0); |
| if (!failover_restart) { |
| _dx_gai_request_enqueue_result(me, qc_result, answer, expired, rdata_ptr, rdata_len, error, q); |
| } |
| } else { |
| _dx_request_set_error(me, error); |
| } |
| |
| exit: |
| if (failover_restart) { |
| _dx_gai_request_restart_client_requests_in_failover_mode(me); |
| } |
| } |
| |
| static void |
| _dx_gai_request_failover_check_gai_answer(const dx_gai_request_t me, const ResourceRecord * const answer) |
| { |
| if ((me->state & DX_GAI_STATE_WAITING_FOR_RESULTS) && !(me->state & dx_gai_state_service_allowed_failover)) { |
| const mdns_dns_service_t dnsservice = mdns_cache_metadata_get_dns_service(answer->metadata); |
| if (dnsservice && mdns_dns_service_allows_failover(dnsservice)) { |
| me->state |= dx_gai_state_service_allowed_failover; |
| } |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_gai_request_query_result_handler(mDNS * const m, DNSQuestion * const q, const ResourceRecord * const answer, |
| const mDNSBool expired, const QC_result qc_result, const DNSServiceErrorType error, void * const context) |
| { |
| (void)m; |
| bool failover_restart = false; |
| const dx_gai_request_t me = (dx_gai_request_t)context; |
| if (!error || (error == kDNSServiceErr_NoSuchRecord)) { |
| require_quiet((answer->rrtype == kDNSServiceType_SVCB) || (answer->rrtype == kDNSServiceType_HTTPS), exit); |
| |
| const uint8_t *rdata_ptr; |
| uint16_t rdata_len; |
| if (!error) { |
| rdata_ptr = answer->rdata->u.data; |
| rdata_len = answer->rdlength; |
| #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER) |
| char *svcb_doh_uri = dnssd_svcb_copy_doh_uri(rdata_ptr, rdata_len); |
| // Check for a valid DoH URI. |
| if (svcb_doh_uri) { |
| // Pass the domain to map if the record is DNSSEC signed. |
| char *svcb_domain = NULL; |
| #if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2) |
| char svcb_domain_buffer[MAX_ESCAPED_DOMAIN_NAME] = ""; |
| if (resource_record_get_validation_result(answer) == dnssec_secure) { |
| if (ConvertDomainNameToCString(answer->name, svcb_domain_buffer)) { |
| svcb_domain = svcb_domain_buffer; |
| } |
| } |
| #endif |
| Querier_RegisterDoHURI(svcb_doh_uri, svcb_domain); |
| ForgetMem(&svcb_doh_uri); |
| } |
| #endif |
| } else { |
| rdata_ptr = NULL; |
| rdata_len = 0; |
| } |
| failover_restart = _dx_gai_request_check_for_failover_restart(me, answer, expired, rdata_len > 0); |
| if (!failover_restart) { |
| _dx_gai_request_enqueue_result(me, qc_result, answer, expired, rdata_ptr, rdata_len, error, q); |
| } |
| } else { |
| _dx_request_set_error(me, error); |
| } |
| |
| exit: |
| if (failover_restart) { |
| _dx_gai_request_restart_client_requests_in_failover_mode(me); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| _dx_gai_request_needs_sensitive_logging(const dx_gai_request_t me) |
| { |
| #if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL) |
| return (me->log_privacy_level == dnssd_log_privacy_level_private); |
| #else |
| return false; |
| #endif |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_gai_request_append_result(const dx_gai_request_t me, const dx_gai_result_t result) |
| { |
| result->cname_update = _dx_gai_request_copy_cname_update(me, mdns_resource_record_get_type(result->record)); |
| _dx_request_locked(me, |
| ^{ |
| dx_gai_result_t *ptr = &me->results; |
| while (*ptr) { |
| ptr = &(*ptr)->next; |
| } |
| *ptr = result; |
| _dx_retain(*ptr); |
| }); |
| } |
| |
| //====================================================================================================================== |
| |
| static dnssd_negative_reason_t |
| _dx_get_negative_answer_reason(const ResourceRecord *answer, QC_result qc_result); |
| |
| static void |
| _dx_gai_request_enqueue_result(const dx_gai_request_t me, const QC_result qc_result, |
| const ResourceRecord * const answer, const bool answer_is_expired, const uint8_t * const rdata_ptr, |
| const uint16_t rdata_len, const DNSServiceErrorType result_error, const DNSQuestion * const q) |
| { |
| DNSServiceErrorType err; |
| dx_gai_result_t result = NULL; |
| require_action_quiet(!answer_is_expired || (rdata_len > 0), exit, err = kDNSServiceErr_NoError); |
| |
| result = _dx_gai_result_new(); |
| require_action_quiet(result, exit, err = kDNSServiceErr_NoMemory); |
| |
| _dx_replace_domain_name(&me->last_domain_name, answer->name); |
| require_action_quiet(me->last_domain_name, exit, err = kDNSServiceErr_NoMemory); |
| |
| result->record = mdns_resource_record_create(me->last_domain_name, answer->rrtype, answer->rrclass, 0, |
| rdata_ptr, rdata_len, NULL); |
| require_action_quiet(result->record, exit, err = kDNSServiceErr_NoMemory); |
| |
| DNSServiceFlags flags = 0; |
| const bool is_add = _dx_qc_result_is_add(qc_result); |
| if (is_add) { |
| flags |= kDNSServiceFlagsAdd; |
| if (!q->InitialCacheMiss) { |
| flags |= kDNSServiceFlagAnsweredFromCache; |
| } |
| if (rdata_len <= 0) { |
| result->negative_reason = _dx_get_negative_answer_reason(answer, qc_result); |
| } |
| result->extended_dns_error = mdns_cache_metadata_get_extended_dns_error(answer->metadata); |
| mdns_retain_null_safe(result->extended_dns_error); |
| } |
| if (answer_is_expired) { |
| flags |= kDNSServiceFlagsExpiredAnswer; |
| } |
| extern mDNS mDNSStorage; |
| result->flags = flags; |
| result->error = result_error; |
| result->ifindex = mDNSPlatformInterfaceIndexfromInterfaceID(&mDNSStorage, answer->InterfaceID, mDNStrue); |
| result->protocol = mdns_cache_metadata_get_protocol(answer->metadata); |
| result->question_id = mDNSVal16(q->TargetQID); |
| const mdns_dns_service_t dnsservice = mdns_cache_metadata_get_dns_service(answer->metadata); |
| if (dnsservice) { |
| result->provider_name = mdns_dns_service_copy_provider_name(dnsservice); |
| } |
| #if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL) |
| result->sensitive_logging = _dx_gai_request_needs_sensitive_logging(me); |
| #endif |
| const uint16_t record_type = mdns_resource_record_get_type(result->record); |
| if (is_add && !result_error) { |
| #if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS) |
| if (me->signed_resolve && ((record_type == kDNSType_A) || (record_type == kDNSType_AAAA))) { |
| OSStatus create_err; |
| mdns_signed_hostname_result_t signed_hostname; |
| if (record_type == kDNSType_A) { |
| signed_hostname = mdns_signed_hostname_result_create_ipv4(me->signed_resolve, rdata_ptr, &create_err); |
| } else { |
| signed_hostname = mdns_signed_hostname_result_create_ipv6(me->signed_resolve, rdata_ptr, |
| result->ifindex, &create_err); |
| } |
| result->signed_hostname = signed_hostname; |
| if (!result->signed_hostname) { |
| os_log_error(_mdns_server_log(), |
| "[R%u] Failed to create IPv%d signed hostname result: %{mdns:err}ld", |
| me->base.request_id, (record_type == kDNSType_A) ? 4 : 6, (long)create_err); |
| } |
| } |
| #endif |
| } |
| #if MDNSRESPONDER_SUPPORTS(APPLE, TRACKER_STATE) |
| if (resolved_cache_is_enabled() && is_add) { |
| const char *hostname = NULL; |
| const char *owner = NULL; |
| bool approved_domain = false; |
| bool can_block_request = false; |
| const tracker_state_t tracker_state = resolved_cache_get_tracker_state(q, &hostname, &owner, &approved_domain, |
| &can_block_request); |
| if ((tracker_state == tracker_state_known_tracker) && hostname) { |
| mdns_xpc_string_recreate(&me->last_tracker_hostname, hostname); |
| require_action_quiet(me->last_tracker_hostname, exit, err = kDNSServiceErr_NoMemory); |
| |
| result->tracker_hostname = me->last_tracker_hostname; |
| xpc_retain(result->tracker_hostname); |
| if (owner) { |
| mdns_xpc_string_recreate(&me->last_tracker_owner, owner); |
| require_action_quiet(me->last_tracker_owner, exit, err = kDNSServiceErr_NoMemory); |
| |
| result->tracker_owner = me->last_tracker_owner; |
| mdns_xpc_string_retain(result->tracker_owner); |
| } |
| if (approved_domain) { |
| result->tracker_is_approved = true; |
| } |
| if (can_block_request) { |
| result->tracker_can_block = true; |
| } |
| } |
| } |
| #endif |
| if ((me->state & dx_gai_state_avoid_suppressed_a_result) && !answer_is_expired) { |
| switch (record_type) { |
| case kDNSType_A: |
| if (result->negative_reason == dnssd_negative_reason_query_suppressed) { |
| // Put a negative suppressed A result on hold. |
| _dx_replace(&me->pending_suppresed_a, result); |
| err = kDNSServiceErr_NoError; |
| goto exit; |
| } else { |
| // Any other type of A result means that if there's a pending negative suppressed A result, |
| // then it can be dropped. It also means that we no longer care about avoiding negative |
| // suppressed A results. |
| _dx_forget(&me->pending_suppresed_a); |
| SetOrClearBits(&me->state, dx_gai_state_avoid_suppressed_a_result, false); |
| } |
| break; |
| |
| case kDNSType_AAAA: |
| // A AAAA result means that if there's a pending negative suppressed A result, then it should no |
| // longer be kept from the client. It also means that we no longer care about avoiding negative |
| // suppressed A results because if the A DNSQuestion was stuck at a DNS service that suppresses A |
| // queries, then it should have advanced past it by now if there was a CNAME chain to traverse |
| // because the AAAA DNSQuestion just did. |
| if (me->pending_suppresed_a) { |
| _dx_gai_request_append_result(me, me->pending_suppresed_a); |
| _dx_forget(&me->pending_suppresed_a); |
| } |
| SetOrClearBits(&me->state, dx_gai_state_avoid_suppressed_a_result, false); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| _dx_gai_request_append_result(me, result); |
| err = kDNSServiceErr_NoError; |
| |
| exit: |
| static_analyzer_malloc_freed(result); // Analyzer isn't aware that result will be freed if non-NULL. |
| _dx_forget(&result); |
| if (err) { |
| _dx_request_set_error(me, err); |
| } |
| } |
| |
| static dnssd_negative_reason_t |
| _dx_get_negative_answer_reason(const ResourceRecord * const answer, const QC_result qc_result) |
| { |
| const mdns_dns_service_t dnsservice = mdns_cache_metadata_get_dns_service(answer->metadata); |
| if (dnsservice) { |
| if (_dx_qc_result_is_suppressed(qc_result)) { |
| return dnssd_negative_reason_query_suppressed; |
| } else { |
| switch (answer->rcode) { |
| case kDNSFlag1_RC_NoErr: |
| return dnssd_negative_reason_no_data; |
| |
| case kDNSFlag1_RC_NXDomain: |
| return dnssd_negative_reason_nxdomain; |
| |
| default: |
| return dnssd_negative_reason_server_error; |
| } |
| } |
| } else { |
| if (answer->InterfaceID) { |
| // A non-zero InterfaceID means that an mDNS NSEC record asserted that there's no data for the record. |
| return dnssd_negative_reason_no_data; |
| } else { |
| return dnssd_negative_reason_no_dns_service; |
| } |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER) |
| static const uint8_t * |
| _dx_gai_request_get_resolver_uuid(const xpc_object_t params) |
| { |
| const xpc_object_t resolver_uuids = dnssd_xpc_parameters_get_resolver_uuid_array(params); |
| if (resolver_uuids && (xpc_array_get_count(resolver_uuids) > 0)) { |
| return xpc_array_get_uuid(resolver_uuids, 0); |
| } else { |
| return NULL; |
| } |
| } |
| #endif |
| |
| //====================================================================================================================== |
| |
| #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN) |
| static bool |
| _dx_gai_request_is_for_in_app_browser(const xpc_object_t params) |
| { |
| const char * const account_id = dnssd_xpc_parameters_get_account_id(params); |
| if (account_id && (strcmp(account_id, "com.apple.WebKit.InAppBrowser") == 0)) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| #endif |
| |
| //====================================================================================================================== |
| |
| #define _dx_log_private_string_with_formatted_bookends(SENSITIVE, PREFIX_FMT, SUFFIX_FMT, ...) \ |
| do { \ |
| if (SENSITIVE) { \ |
| os_log(_mdns_server_log(), PREFIX_FMT "%{sensitive,mask.hash}s" SUFFIX_FMT, __VA_ARGS__); \ |
| } else { \ |
| os_log(_mdns_server_log(), PREFIX_FMT "%{private,mask.hash}s" SUFFIX_FMT, __VA_ARGS__); \ |
| } \ |
| } while (0) |
| |
| static void |
| _dx_gai_request_log_start(const dx_gai_request_t me, const pid_t delegator_pid, const uuid_t delegator_uuid) |
| { |
| const dx_session_t session = me->base.session; |
| const bool sensitive_logging = _dx_gai_request_needs_sensitive_logging(me); |
| if (delegator_uuid) { |
| _dx_log_private_string_with_formatted_bookends(sensitive_logging, |
| "[R%u] getaddrinfo start -- flags: 0x%X, ifindex: %d, protocols: %u, hostname: ", /* hostname */ |
| ", options: %{mdns:gaiopts}X, client pid: %lld (%{public}s), delegator uuid: %{public,uuid_t}.16P", |
| me->base.request_id, me->flags, (int32_t)me->ifindex, me->protocols, me->hostname, |
| me->options, (long long)session->client_pid, session->client_name, delegator_uuid); |
| } else if (delegator_pid != 0) { |
| char delegator_name[MAXCOMLEN]; |
| delegator_name[0] = '\0'; |
| mdns_system_pid_to_name(delegator_pid, delegator_name); |
| _dx_log_private_string_with_formatted_bookends(sensitive_logging, |
| "[R%u] getaddrinfo start -- flags: 0x%X, ifindex: %d, protocols: %u, hostname: ", /* hostname */ |
| ", options: %{mdns:gaiopts}X, client pid: %lld (%{public}s), delegator pid: %lld (%{public}s)", |
| me->base.request_id, me->flags, (int32_t)me->ifindex, me->protocols, me->hostname, |
| me->options, (long long)session->client_pid, session->client_name, (long long)delegator_pid, |
| delegator_name); |
| } else { |
| _dx_log_private_string_with_formatted_bookends(sensitive_logging, |
| "[R%u] getaddrinfo start -- flags: 0x%X, ifindex: %d, protocols: %u, hostname: ", /* hostname */ |
| ", options: %{mdns:gaiopts}X, client pid: %lld (%{public}s)", |
| me->base.request_id, me->flags, (int32_t)me->ifindex, me->protocols, me->hostname, |
| me->options, (long long)session->client_pid, session->client_name); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_gai_request_log_stop(const dx_gai_request_t me) |
| { |
| const dx_session_t session = me->base.session; |
| const bool sensitive_logging = _dx_gai_request_needs_sensitive_logging(me); |
| if (session->terminated) { |
| _dx_log_private_string_with_formatted_bookends(sensitive_logging, |
| "[R%u] getaddrinfo stop (forced) -- hostname: ", /* hostname */ ", client pid: %lld (%{public}s)", |
| me->base.request_id, me->hostname, (long long)session->client_pid, session->client_name); |
| } else { |
| _dx_log_private_string_with_formatted_bookends(sensitive_logging, |
| "[R%u] getaddrinfo stop -- hostname: ", /* hostname */ ", client pid: %lld (%{public}s)", |
| me->base.request_id, me->hostname, (long long)session->client_pid, session->client_name); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_gai_request_log_error(const dx_gai_request_t me, const DNSServiceErrorType error) |
| { |
| const dx_session_t session = me->base.session; |
| os_log_error(_mdns_server_log(), |
| "[R%u] getaddrinfo error -- hostname: %{private,mask.hash}s, error: %{mdns:err}ld"", " |
| "client pid: %lld (%{public}s)", |
| me->base.request_id, me->hostname, (long)error, (long long)session->client_pid, session->client_name); |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| _dx_gai_request_involves_parallel_a_and_aaaa_questions(const dx_gai_request_t me) |
| { |
| switch (me->protocols & (kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6)) { |
| case 0: |
| return true; |
| |
| case (kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6): |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static DNSServiceErrorType |
| _dx_gai_request_start_client_requests(const dx_gai_request_t me, const bool need_lock) |
| { |
| const dx_session_t session = me->base.session; |
| |
| // Set up GetAddrInfo parameters. |
| GetAddrInfoClientRequestParams gai_params; |
| GetAddrInfoClientRequestParamsInit(&gai_params); |
| gai_params.hostnameStr = me->hostname; |
| gai_params.requestID = me->base.request_id; |
| gai_params.interfaceIndex = me->ifindex; |
| gai_params.flags = me->flags; |
| gai_params.protocols = me->protocols; |
| gai_params.effectivePID = me->effective_pid; |
| gai_params.effectiveUUID = me->effective_uuid; |
| gai_params.peerUID = session->client_euid; |
| #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER) |
| gai_params.needEncryption = (me->options & mdns_gai_option_need_encryption) != 0; |
| gai_params.failoverMode = (me->state & dx_gai_state_failover_mode) != 0; |
| gai_params.prohibitEncryptedDNS = (me->options & mdns_gai_option_prohibit_encrypted_dns) != 0; |
| #endif |
| #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN) |
| gai_params.peerToken = session->peer_token; |
| gai_params.delegatorToken = me->delegator_token; |
| gai_params.isInAppBrowserRequest = (me->options & mdns_gai_option_in_app_browser) != 0; |
| #endif |
| #if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL) |
| gai_params.logPrivacyLevel = me->log_privacy_level; |
| #endif |
| const bool will_have_parallel_a_and_aaaa = _dx_gai_request_involves_parallel_a_and_aaaa_questions(me); |
| SetOrClearBits(&me->state, dx_gai_state_avoid_suppressed_a_result, will_have_parallel_a_and_aaaa); |
| _dx_forget(&me->pending_suppresed_a); |
| gai_params.persistWhenARecordsUnusable = will_have_parallel_a_and_aaaa; |
| |
| // Set up QueryRecord parameters. |
| QueryRecordClientRequestParams query_params; |
| QueryRecordClientRequestParams *query_params_ptr = NULL; |
| if (me->svcb_name) { |
| QueryRecordClientRequestParamsInit(&query_params); |
| query_params.requestID = me->base.request_id; |
| query_params.qnameStr = me->svcb_name; |
| query_params.interfaceIndex = me->ifindex; |
| query_params.flags = me->flags; |
| query_params.qtype = me->svcb_type; |
| query_params.qclass = kDNSServiceClass_IN; |
| query_params.effectivePID = me->effective_pid; |
| query_params.effectiveUUID = me->effective_uuid; |
| query_params.peerUID = session->client_euid; |
| #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER) |
| query_params.needEncryption = (me->options & mdns_gai_option_need_encryption) != 0; |
| query_params.failoverMode = (me->state & dx_gai_state_failover_mode) != 0; |
| query_params.prohibitEncryptedDNS = (me->options & mdns_gai_option_prohibit_encrypted_dns) != 0; |
| #endif |
| #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN) |
| query_params.peerToken = session->peer_token; |
| query_params.delegatorToken = me->delegator_token; |
| query_params.isInAppBrowserRequest = (me->options & mdns_gai_option_in_app_browser) != 0; |
| #endif |
| #if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL) |
| query_params.logPrivacyLevel = me->log_privacy_level; |
| #endif |
| query_params_ptr = &query_params; |
| } |
| return _dx_gai_request_start_client_requests_internal(me, &gai_params, query_params_ptr, need_lock); |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| _dx_gai_request_check_for_failover_restart(const dx_gai_request_t me, const ResourceRecord * const answer, |
| const bool answer_is_expired, const bool answer_is_positive) |
| { |
| __block bool restart = false; |
| __block dx_gai_result_t free_list = NULL; |
| if ((me->state & DX_GAI_STATE_WAITING_FOR_RESULTS) && !answer_is_expired) { |
| _dx_request_locked(me, |
| ^{ |
| if (answer_is_positive) { |
| switch (answer->rrtype) { |
| case kDNSType_A: |
| case kDNSType_AAAA: |
| case kDNSType_HTTPS: |
| me->state &= ~DX_GAI_STATE_WAITING_FOR_RESULTS; |
| break; |
| |
| default: |
| break; |
| } |
| } else { |
| switch (answer->rrtype) { |
| case kDNSServiceType_A: |
| me->state &= ~dx_gai_state_waiting_for_a; |
| break; |
| |
| case kDNSServiceType_AAAA: |
| me->state &= ~dx_gai_state_waiting_for_aaaa; |
| break; |
| |
| default: |
| break; |
| } |
| const dx_gai_state_t state = me->state; |
| if (!(state & DX_GAI_STATE_WAITING_FOR_RESULTS) && (state & dx_gai_state_service_allowed_failover)) { |
| restart = true; |
| free_list = me->results; |
| me->results = NULL; |
| } |
| } |
| }); |
| } |
| _dx_gai_result_list_forget(&free_list); |
| return restart; |
| } |
| |
| //====================================================================================================================== |
| // MARK: - GAI Result Methods |
| |
| static void |
| _dx_gai_result_finalize(const dx_gai_result_t me) |
| { |
| mdns_forget(&me->record); |
| mdns_xpc_string_forget(&me->provider_name); |
| xpc_forget(&me->cname_update); |
| mdns_xpc_string_forget(&me->tracker_hostname); |
| mdns_xpc_string_forget(&me->tracker_owner); |
| #if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS) |
| mdns_forget(&me->signed_hostname); |
| #endif |
| mdns_forget(&me->extended_dns_error); |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_gai_result_list_forget(dx_gai_result_t * const list_ptr) |
| { |
| dx_gai_result_t list; |
| if ((list = *list_ptr) != NULL) { |
| *list_ptr = NULL; |
| dx_gai_result_t result; |
| while ((result = list) != NULL) { |
| list = result->next; |
| _dx_forget(&result); |
| } |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static xpc_object_t |
| _dx_gai_result_to_dictionary(const dx_gai_result_t me) |
| { |
| xpc_object_t result = xpc_dictionary_create(NULL, NULL, 0); |
| require_return_value(result, NULL); |
| |
| dnssd_xpc_result_set_error(result, me->error); |
| dnssd_xpc_result_set_flags(result, me->flags); |
| dnssd_xpc_result_set_interface_index(result, me->ifindex); |
| const mdns_domain_name_t name = mdns_resource_record_get_name(me->record); |
| dnssd_xpc_result_set_record_name(result, mdns_domain_name_get_presentation(name)); |
| dnssd_xpc_result_set_record_type(result, mdns_resource_record_get_type(me->record)); |
| dnssd_xpc_result_set_record_protocol(result, (uint16_t)me->protocol); |
| dnssd_xpc_result_set_record_class(result, mdns_resource_record_get_class(me->record)); |
| dnssd_xpc_result_set_record_data(result, mdns_resource_record_get_rdata_bytes_ptr(me->record), |
| mdns_resource_record_get_rdata_length(me->record)); |
| if (me->negative_reason != dnssd_negative_reason_none) { |
| dnssd_xpc_result_set_negative_reason(result, me->negative_reason); |
| } |
| if (me->provider_name) { |
| dnssd_xpc_result_set_provider_name(result, me->provider_name); |
| } |
| if (me->cname_update) { |
| dnssd_xpc_result_set_cname_update(result, me->cname_update); |
| } |
| if (me->tracker_hostname) { |
| dnssd_xpc_result_set_tracker_hostname(result, me->tracker_hostname); |
| if (me->tracker_owner) { |
| dnssd_xpc_result_set_tracker_owner(result, me->tracker_owner); |
| } |
| dnssd_xpc_result_set_tracker_is_approved(result, me->tracker_is_approved); |
| dnssd_xpc_result_set_tracker_can_block_request(result, me->tracker_can_block); |
| } |
| #if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS) |
| if (me->signed_hostname) { |
| size_t data_len; |
| const uint8_t * const data = mdns_signed_result_get_data(me->signed_hostname, &data_len); |
| if (data) { |
| dnssd_xpc_result_set_validation_data(result, data, data_len); |
| } |
| } |
| #endif |
| const mdns_extended_dns_error_t ede = me->extended_dns_error; |
| if (ede) { |
| const uint16_t code = mdns_extended_dns_error_get_code(ede); |
| const mdns_xpc_string_t text = mdns_extended_dns_error_get_extra_text(ede); |
| dnssd_xpc_result_set_extended_dns_error(result, code, text); |
| } |
| return result; |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| _dx_gai_result_needs_sensitive_logging(const dx_gai_result_t me) |
| { |
| #if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL) |
| return me->sensitive_logging; |
| #else |
| return false; |
| #endif |
| } |
| |
| //====================================================================================================================== |
| |
| #define _dx_log_name_and_record_with_formatted_bookends(NAME, RECORD, SENSITIVE, PREFIX_FMT, MID_FMT, SUFFIX_FMT, \ |
| REQUEST_ID, QUESTION_ID, IS_ADD, IFINDEX, RRTYPE, EXPIRED) \ |
| do { \ |
| const bool _sensitive = SENSITIVE; \ |
| const mdns_domain_name_t _name = NAME; \ |
| char *_sensitive_name_desc = _sensitive ? mdns_copy_private_description(_name) : NULL; \ |
| const mdns_resource_record_t _record = RECORD; \ |
| char *_sensitive_record_desc = _sensitive ? mdns_copy_private_description(_record) : NULL; \ |
| if (_sensitive_name_desc && _sensitive_record_desc) { \ |
| os_log(_mdns_server_log(), PREFIX_FMT "%{public}s" MID_FMT "%{public}s" SUFFIX_FMT, \ |
| REQUEST_ID, QUESTION_ID, IS_ADD, IFINDEX, _sensitive_name_desc, RRTYPE, _sensitive_record_desc, \ |
| EXPIRED); \ |
| } else { \ |
| os_log(_mdns_server_log(), PREFIX_FMT "%@" MID_FMT "%@" SUFFIX_FMT, \ |
| REQUEST_ID, QUESTION_ID, IS_ADD, IFINDEX, _name, RRTYPE, _record, EXPIRED); \ |
| } \ |
| ForgetMem(&_sensitive_name_desc); \ |
| ForgetMem(&_sensitive_record_desc); \ |
| } while (0) |
| |
| #define _dx_log_name_with_formatted_bookends(NAME, SENSITIVE, PREFIX_FMT, SUFFIX_FMT, REQUEST_ID, QUESTION_ID, \ |
| IS_ADD, IFINDEX, RRTYPE, NEGATIVE_REASON) \ |
| do { \ |
| const mdns_domain_name_t _name = NAME; \ |
| char *_sensitive_name_desc = (SENSITIVE) ? mdns_copy_private_description(_name) : NULL; \ |
| if (_sensitive_name_desc) { \ |
| os_log(_mdns_server_log(), PREFIX_FMT "%{public}s" SUFFIX_FMT, \ |
| REQUEST_ID, QUESTION_ID, IS_ADD, IFINDEX, _sensitive_name_desc, RRTYPE, NEGATIVE_REASON); \ |
| ForgetMem(&_sensitive_name_desc); \ |
| } else { \ |
| os_log(_mdns_server_log(), PREFIX_FMT "%@" SUFFIX_FMT, \ |
| REQUEST_ID, QUESTION_ID, IS_ADD, IFINDEX, _name, RRTYPE, NEGATIVE_REASON); \ |
| } \ |
| } while (0) |
| |
| static void |
| _dx_gai_result_log(const dx_gai_result_t me, const uint32_t request_id) |
| { |
| const bool is_add = (me->flags & kDNSServiceFlagsAdd) ? true : false; |
| const mdns_domain_name_t name = me->record ? mdns_resource_record_get_name(me->record) : NULL; |
| const int type = me->record ? mdns_resource_record_get_type(me->record) : 0; |
| const bool sensitive_logging = _dx_gai_result_needs_sensitive_logging(me); |
| if (me->record && (mdns_resource_record_get_rdata_length(me->record) > 0)) { |
| const bool expired = (me->flags & kDNSServiceFlagsExpiredAnswer) != 0; |
| _dx_log_name_and_record_with_formatted_bookends(name, me->record, sensitive_logging, |
| "[R%u->Q%u] getaddrinfo result -- event: %{mdns:addrmv}d, ifindex: %d, name: ", /* name */ |
| ", type: %{mdns:rrtype}d, rdata: ", /* rdata */ ", expired: %{mdns:yesno}d", |
| request_id, me->question_id, is_add, me->ifindex, type, expired); |
| } else { |
| _dx_log_name_with_formatted_bookends(name, sensitive_logging, |
| "[R%u->Q%u] getaddrinfo result -- event: %{mdns:addrmv}d, ifindex: %d, name: ", /* name */ |
| ", type: %{mdns:rrtype}d, rdata: <none>, reason: %{mdns:nreason}d", |
| request_id, me->question_id, is_add, me->ifindex, type, me->negative_reason); |
| } |
| } |
| |
| //====================================================================================================================== |
| // MARK: - Helper Functions |
| |
| static void |
| _dx_kqueue_locked(const char * const description, const bool need_lock, const dx_block_t block) |
| { |
| if (need_lock) { |
| KQueueLock(); |
| block(); |
| KQueueUnlock(description); |
| } else { |
| block(); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_replace_domain_name(mdns_domain_name_t * const ptr, const domainname * const name) |
| { |
| const mdns_domain_name_t original = *ptr; |
| if (!original || !SameDomainNameBytes(mdns_domain_name_get_labels(original), name->c)) { |
| mdns_forget(ptr); |
| *ptr = mdns_domain_name_create_with_labels(name->c, NULL); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| _dx_qc_result_is_add(const QC_result qc_result) |
| { |
| switch (qc_result) { |
| case QC_rmv: |
| return false; |
| |
| case QC_add: |
| case QC_addnocache: |
| case QC_forceresponse: |
| case QC_suppressed: |
| MDNS_COVERED_SWITCH_DEFAULT: |
| return true; |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static bool |
| _dx_qc_result_is_suppressed(const QC_result qc_result) |
| { |
| switch (qc_result) { |
| case QC_suppressed: |
| return true; |
| |
| case QC_rmv: |
| case QC_add: |
| case QC_addnocache: |
| case QC_forceresponse: |
| MDNS_COVERED_SWITCH_DEFAULT: |
| return false; |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static QueryRecordClientRequest * |
| _dx_query_record_client_request_start(const QueryRecordClientRequestParams * const params, |
| const QueryRecordResultHandler handler, void * const context, DNSServiceErrorType * const out_error) |
| { |
| DNSServiceErrorType err; |
| QueryRecordClientRequest *query = (QueryRecordClientRequest *)mdns_calloc(1, sizeof(*query)); |
| mdns_require_action_quiet(query, exit, err = kDNSServiceErr_NoMemory); |
| |
| err = QueryRecordClientRequestStart(query, params, handler, context); |
| if (err) { |
| ForgetMem(&query); |
| } |
| |
| exit: |
| mdns_assign(out_error, err); |
| return query; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_query_record_client_request_forget(QueryRecordClientRequest ** const request_ptr) |
| { |
| QueryRecordClientRequest * const query = *request_ptr; |
| if (query) { |
| QueryRecordClientRequestStop(query); |
| ForgetMem(request_ptr); |
| } |
| } |
| |
| //====================================================================================================================== |
| |
| static GetAddrInfoClientRequest * |
| _dx_get_addr_info_client_request_start(const GetAddrInfoClientRequestParams * const params, |
| const QueryRecordResultHandler handler, void * const context, DNSServiceErrorType * const out_error) |
| { |
| DNSServiceErrorType err; |
| GetAddrInfoClientRequest *gai = (GetAddrInfoClientRequest *)mdns_calloc(1, sizeof(*gai)); |
| mdns_require_action_quiet(gai, exit, err = kDNSServiceErr_NoMemory); |
| |
| err = GetAddrInfoClientRequestStart(gai, params, handler, context); |
| if (err) { |
| ForgetMem(&gai); |
| } |
| |
| exit: |
| mdns_assign(out_error, err); |
| return gai; |
| } |
| |
| //====================================================================================================================== |
| |
| static void |
| _dx_get_addr_info_client_request_forget(GetAddrInfoClientRequest ** const request_ptr) |
| { |
| GetAddrInfoClientRequest * const gai = *request_ptr; |
| if (gai) { |
| GetAddrInfoClientRequestStop(gai); |
| ForgetMem(request_ptr); |
| } |
| } |