blob: e31867183ac151d07285f3f7cc640e412c57814f [file] [log] [blame] [edit]
/*
* Copyright (c) 2021-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 "mrcs_server_internal.h"
#include "helpers.h"
#include "mrc_xpc.h"
#include "mrcs_object_internal.h"
#include "mrcs_objects.h"
#include <CoreUtils/CoreUtils.h>
#include <mdns/DNSMessage.h>
#include <mdns/string_builder.h>
#include <mdns/xpc.h>
#include <xpc/xpc.h>
#include "mdns_strict.h"
//======================================================================================================================
// MARK: - Constants
MDNS_CLOSED_ENUM(mrcs_command_t, uint8_t,
mrcs_command_invalid = 0,
mrcs_command_dns_proxy_start = 1,
mrcs_command_dns_proxy_stop = 2,
mrcs_command_dns_proxy_get_state = 3,
mrcs_command_dns_service_registration_start = 4,
mrcs_command_dns_service_registration_stop = 5,
mrcs_command_discovery_proxy_start = 6,
mrcs_command_discovery_proxy_stop = 7,
mrcs_command_cached_local_record_inquiry = 8,
mrcs_command_record_cache_flush = 9,
);
//======================================================================================================================
// MARK: - Session Kind
struct mrcs_session_s {
struct mdns_obj_s base; // Object base.
mrcs_session_t next; // Next session in list.
xpc_connection_t connection; // Underlying XPC connection.
mrcs_dns_proxy_request_t dns_proxy_request_list; // List of client requests.
mrcs_dns_service_registration_request_t dns_service_reg_request_list; // List of DNS service requests.
};
MRCS_OBJECT_SUBKIND_DEFINE(session);
//======================================================================================================================
// MARK: - DNS Proxy Request Kind
struct mrcs_dns_proxy_request_s {
struct mdns_obj_s base; // Object base.
mrcs_dns_proxy_request_t next; // Next request in list.
mrcs_dns_proxy_t proxy; // DNS proxy.
uint64_t command_id; // Command ID.
};
MRCS_OBJECT_SUBKIND_DEFINE(dns_proxy_request);
//======================================================================================================================
// MARK: - DNS Service Registration Request Kind
struct mrcs_dns_service_registration_request_s {
struct mdns_obj_s base; // Object base.
mrcs_dns_service_registration_request_t next; // Next request in list.
mdns_dns_service_definition_t do53_definition; // DNS over 53 service definition.
mdns_dns_push_service_definition_t push_definition; // DNS push service definition.
uint64_t command_id; // Command ID.
mdns_dns_service_id_t ident; // DNS service ID.
};
MRCS_OBJECT_SUBKIND_DEFINE(dns_service_registration_request);
//======================================================================================================================
// MARK: - Local Prototypes
static dispatch_queue_t
_mrcs_server_queue(void);
static void
_mrcs_server_handle_new_connection(xpc_connection_t connection);
static mrcs_session_t
_mrcs_session_create(xpc_connection_t connection, OSStatus *out_error);
static void
_mrcs_session_activate(mrcs_session_t session);
static void
_mrcs_session_invalidate(mrcs_session_t session);
static void
_mrcs_session_register(mrcs_session_t session);
static void
_mrcs_session_deregister(mrcs_session_t session);
static void
_mrcs_session_handle_message(mrcs_session_t session, xpc_object_t msg);
static mrcs_dns_proxy_request_t
_mrcs_dns_proxy_request_create(mrcs_dns_proxy_t proxy, uint64_t command_id, OSStatus *out_error);
static mrcs_dns_proxy_t
_mrcs_create_dns_proxy_from_params_dictionary(xpc_object_t params, OSStatus *out_error);
//======================================================================================================================
// MARK: - Logging
MDNS_LOG_CATEGORY_DEFINE(server, "mrcs_server");
//======================================================================================================================
// MARK: - Globals
static mrcs_server_dns_proxy_handlers_t g_dns_proxy_handlers = NULL;
static mrcs_server_dns_service_registration_handlers_t g_dns_service_registration_handlers = NULL;
static mrcs_server_discovery_proxy_handlers_t g_discovery_proxy_handlers = NULL;
static mrcs_server_record_cache_handlers_t g_record_cache_handlers = NULL;
static mrcs_session_t g_session_list = NULL;
static mrcs_session_t g_current_discovery_proxy_owner = NULL;
static bool g_activated = false;
//======================================================================================================================
// MARK: - Public Server Functions
void
mrcs_server_set_dns_proxy_handlers(const mrcs_server_dns_proxy_handlers_t handlers)
{
dispatch_async(_mrcs_server_queue(),
^{
mdns_require_return(!g_activated);
g_dns_proxy_handlers = handlers;
});
}
//======================================================================================================================
void
mrcs_server_set_dns_service_registration_handlers(const mrcs_server_dns_service_registration_handlers_t handlers)
{
dispatch_async(_mrcs_server_queue(),
^{
mdns_require_return(!g_activated);
g_dns_service_registration_handlers = handlers;
});
}
//======================================================================================================================
void
mrcs_server_set_discovery_proxy_handlers(const mrcs_server_discovery_proxy_handlers_t handlers)
{
dispatch_async(_mrcs_server_queue(),
^{
mdns_require_return(!g_activated);
g_discovery_proxy_handlers = handlers;
});
}
//======================================================================================================================
void
mrcs_server_set_record_cache_handlers(const mrcs_server_record_cache_handlers_t handlers)
{
dispatch_async(_mrcs_server_queue(),
^{
mdns_require_return(!g_activated);
g_record_cache_handlers = handlers;
});
}
//======================================================================================================================
void
mrcs_server_activate(void)
{
dispatch_async(_mrcs_server_queue(),
^{
static xpc_connection_t s_listener = NULL;
mdns_require_return(!s_listener);
const uint64_t flags = XPC_CONNECTION_MACH_SERVICE_LISTENER;
s_listener = xpc_connection_create_mach_service(g_mrc_mach_service_name, _mrcs_server_queue(), flags);
mdns_require_return_action(s_listener, os_log_fault(_mdns_server_log(),
"Failed to create XPC listener for %{public}s", g_mrc_mach_service_name));
xpc_connection_set_event_handler(s_listener,
^(const xpc_object_t event)
{
if (xpc_get_type(event) == XPC_TYPE_CONNECTION) {
_mrcs_server_handle_new_connection((xpc_connection_t)event);
}
});
xpc_connection_activate(s_listener);
g_activated = true;
});
}
//======================================================================================================================
// MARK: - Private Server Functions
static dispatch_queue_t
_mrcs_server_queue(void)
{
static dispatch_once_t s_once = 0;
static dispatch_queue_t s_queue = NULL;
dispatch_once(&s_once,
^{
s_queue = dispatch_queue_create("com.apple.mDNSResponder.control.server", DISPATCH_QUEUE_SERIAL);
});
return s_queue;
}
//======================================================================================================================
static void
_mrcs_server_handle_new_connection(const xpc_connection_t connection)
{
mrcs_session_t session = _mrcs_session_create(connection, NULL);
if (session) {
_mrcs_session_register(session);
_mrcs_session_activate(session);
mrcs_forget(&session);
} else {
xpc_connection_cancel(connection);
}
}
//======================================================================================================================
static OSStatus
_mrcs_server_dns_proxy_start(const mrcs_dns_proxy_t proxy)
{
OSStatus err;
if (g_dns_proxy_handlers->start) {
err = g_dns_proxy_handlers->start(proxy);
} else {
err = kNotHandledErr;
}
return err;
}
//======================================================================================================================
static OSStatus
_mrcs_server_dns_proxy_stop(const mrcs_dns_proxy_t proxy)
{
OSStatus err;
if (g_dns_proxy_handlers->stop) {
err = g_dns_proxy_handlers->stop(proxy);
} else {
err = kNotHandledErr;
}
return err;
}
//======================================================================================================================
static char *
_mrcs_server_dns_proxy_state(OSStatus * const out_error)
{
char *state;
OSStatus err;
if (g_dns_proxy_handlers->get_state) {
state = g_dns_proxy_handlers->get_state();
err = state ? kNoErr : kNoMemoryErr;
} else {
state = NULL;
err = kNotHandledErr;
}
if (out_error) {
*out_error = err;
}
return state;
}
//======================================================================================================================
static OSStatus
_mrcs_server_discovery_proxy_start(const uint32_t ifindex, const CFArrayRef addresses, const CFArrayRef match_domains,
const CFArrayRef server_certificates)
{
OSStatus err;
if (g_discovery_proxy_handlers->start) {
err = g_discovery_proxy_handlers->start(ifindex, addresses, match_domains, server_certificates);
} else {
err = kNotHandledErr;
}
return err;
}
//======================================================================================================================
static OSStatus
_mrcs_server_discovery_proxy_stop(void)
{
OSStatus err;
if (g_discovery_proxy_handlers->stop) {
err = g_discovery_proxy_handlers->stop();
} else {
err = kNotHandledErr;
}
return err;
}
//======================================================================================================================
static mdns_dns_service_id_t
_mrcs_server_dns_service_registration_start_with_connection_error_handler(
const mdns_any_dns_service_definition_t definition, const dispatch_queue_t connection_error_queue,
const mdns_event_handler_t connection_error_handler, OSStatus * const out_error)
{
OSStatus err;
mdns_dns_service_id_t ident;
if (g_dns_service_registration_handlers->start) {
ident = g_dns_service_registration_handlers->start(definition, connection_error_queue,
connection_error_handler);
err = (ident != MDNS_DNS_SERVICE_INVALID_ID) ? kNoErr : kUnknownErr;
} else {
ident = MDNS_DNS_SERVICE_INVALID_ID;
err = kNotHandledErr;
}
mdns_assign(out_error, err);
return ident;
}
//======================================================================================================================
static mdns_dns_service_id_t
_mrcs_server_dns_service_registration_start(const mdns_any_dns_service_definition_t definition,
OSStatus * const out_error)
{
return _mrcs_server_dns_service_registration_start_with_connection_error_handler(definition, NULL, NULL, out_error);
}
//======================================================================================================================
static OSStatus
_mrcs_server_dns_service_registration_stop(const mdns_dns_service_id_t ident)
{
OSStatus err;
if (g_dns_service_registration_handlers->stop) {
g_dns_service_registration_handlers->stop(ident);
err = kNoErr;
} else {
err = kNotHandledErr;
}
return err;
}
//======================================================================================================================
static OSStatus
_mrcs_server_record_cache_enumerate_local_records(const mrcs_record_applier_t applier)
{
OSStatus err;
if (g_record_cache_handlers->enumerate_local_records) {
g_record_cache_handlers->enumerate_local_records(applier);
err = kNoErr;
} else {
err = kNotHandledErr;
}
return err;
}
//======================================================================================================================
static OSStatus
_mrcs_server_record_cache_flush_by_name(const char * const record_name)
{
OSStatus err;
if (g_record_cache_handlers->flush_by_name) {
g_record_cache_handlers->flush_by_name(record_name);
err = kNoErr;
} else {
err = kNotHandledErr;
}
return err;
}
//======================================================================================================================
static OSStatus
_mrcs_server_record_cache_flush_by_name_and_key_tag(const char * const record_name, const uint16_t key_tag)
{
OSStatus err;
if (g_record_cache_handlers->flush_by_name_and_key_tag) {
g_record_cache_handlers->flush_by_name_and_key_tag(record_name, key_tag);
err = kNoErr;
} else {
err = kNotHandledErr;
}
return err;
}
//======================================================================================================================
typedef struct {
const char * string;
mrcs_command_t code;
} mrcs_command_string_code_pair_t;
static mrcs_command_t
_mrcs_server_command_string_to_code(const char * const command_str)
{
#define MRCS_SERVER_COMMAND_STRING_CODE_PAIR(NAME) {g_mrc_command_ ## NAME, mrcs_command_ ## NAME}
const mrcs_command_string_code_pair_t pairs[] = {
MRCS_SERVER_COMMAND_STRING_CODE_PAIR(cached_local_record_inquiry),
MRCS_SERVER_COMMAND_STRING_CODE_PAIR(discovery_proxy_start),
MRCS_SERVER_COMMAND_STRING_CODE_PAIR(discovery_proxy_stop),
MRCS_SERVER_COMMAND_STRING_CODE_PAIR(dns_proxy_start),
MRCS_SERVER_COMMAND_STRING_CODE_PAIR(dns_proxy_stop),
MRCS_SERVER_COMMAND_STRING_CODE_PAIR(dns_proxy_get_state),
MRCS_SERVER_COMMAND_STRING_CODE_PAIR(dns_service_registration_start),
MRCS_SERVER_COMMAND_STRING_CODE_PAIR(dns_service_registration_stop),
MRCS_SERVER_COMMAND_STRING_CODE_PAIR(record_cache_flush),
};
for (size_t i = 0; i < mdns_countof(pairs); ++i) {
const mrcs_command_string_code_pair_t * const pair = &pairs[i];
if (strcmp(command_str, pair->string) == 0) {
return pair->code;
}
}
return mrcs_command_invalid;
}
//======================================================================================================================
static const char *
_mrcs_server_get_required_entitlement(const mrcs_command_t command)
{
switch (command) {
case mrcs_command_discovery_proxy_start:
case mrcs_command_discovery_proxy_stop:
return "com.apple.mDNSResponder.discovery-proxy";
case mrcs_command_dns_proxy_start:
case mrcs_command_dns_proxy_stop:
case mrcs_command_dns_proxy_get_state:
return "com.apple.mDNSResponder.dnsproxy";
case mrcs_command_dns_service_registration_start:
case mrcs_command_dns_service_registration_stop:
return "com.apple.mDNSResponder.dns-service-registration";
case mrcs_command_cached_local_record_inquiry:
return "com.apple.mDNSResponder.record-cache.local-record-info";
case mrcs_command_record_cache_flush:
return "com.apple.private.mDNSResponder.record-cache.flush";
case mrcs_command_invalid:
MDNS_COVERED_SWITCH_DEFAULT:
return NULL;
}
}
//======================================================================================================================
// MARK: - Session Methods
static mrcs_session_t
_mrcs_session_create(const xpc_connection_t connection, OSStatus * const out_error)
{
OSStatus err;
mrcs_session_t session = NULL;
mrcs_session_t obj = _mrcs_session_new();
require_action_quiet(obj, exit, err = kNoMemoryErr);
obj->connection = connection;
xpc_retain(obj->connection);
session = obj;
obj = NULL;
err = kNoErr;
exit:
if (out_error) {
*out_error = err;
}
mrcs_forget(&obj);
return session;
}
//======================================================================================================================
static char *
_mrcs_session_copy_description(const mrcs_session_t me, __unused const bool debug, __unused const bool privacy)
{
char *description = NULL;
asprintf(&description, "<%s: %p>", me->base.kind->name, (void *)me);
return description;
}
//======================================================================================================================
static void
_mrcs_session_finalize(const mrcs_session_t me)
{
xpc_forget(&me->connection);
}
//======================================================================================================================
static void
_mrcs_session_activate(const mrcs_session_t me)
{
mrcs_retain(me);
xpc_connection_set_target_queue(me->connection, _mrcs_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) {
_mrcs_session_handle_message(me, event);
}
} else if (event == XPC_ERROR_CONNECTION_INVALID) {
_mrcs_session_deregister(me);
_mrcs_session_invalidate(me);
mrcs_release(me);
} else {
xpc_connection_forget(&me->connection);
}
});
xpc_connection_activate(me->connection);
}
//======================================================================================================================
static void
_mrcs_session_invalidate(const mrcs_session_t me)
{
xpc_connection_forget(&me->connection);
while (me->dns_proxy_request_list) {
mrcs_dns_proxy_request_t request = me->dns_proxy_request_list;
me->dns_proxy_request_list = request->next;
_mrcs_server_dns_proxy_stop(request->proxy);
mrcs_forget(&request);
}
while (me->dns_service_reg_request_list) {
mrcs_dns_service_registration_request_t request = me->dns_service_reg_request_list;
me->dns_service_reg_request_list = request->next;
_mrcs_server_dns_service_registration_stop(request->ident);
mrcs_forget(&request);
}
// Handle discovery proxy invalidation.
if (g_current_discovery_proxy_owner == me) {
_mrcs_server_discovery_proxy_stop();
mrcs_forget(&g_current_discovery_proxy_owner);
}
}
//======================================================================================================================
static void
_mrcs_session_register(const mrcs_session_t me)
{
mrcs_session_t *ptr = &g_session_list;
while (*ptr) {
ptr = &(*ptr)->next;
}
me->next = NULL;
*ptr = me;
mrcs_retain(me);
}
//======================================================================================================================
static void
_mrcs_session_deregister(const mrcs_session_t me)
{
mrcs_session_t *ptr = &g_session_list;
while (*ptr && (*ptr != me)) {
ptr = &(*ptr)->next;
}
if (*ptr) {
*ptr = me->next;
me->next = NULL;
mrcs_release(me);
}
}
//======================================================================================================================
static OSStatus
_mrcs_session_handle_dns_proxy_start(const mrcs_session_t me, const xpc_object_t msg)
{
OSStatus err;
mrcs_dns_proxy_t proxy = NULL;
const uint64_t command_id = mrc_xpc_message_get_id(msg);
mrcs_dns_proxy_request_t *ptr = &me->dns_proxy_request_list;
while (*ptr && ((*ptr)->command_id != command_id)) {
ptr = &(*ptr)->next;
}
require_action_quiet(!*ptr, exit, err = kStateErr);
const xpc_object_t params = mrc_xpc_message_get_params(msg);
require_action_quiet(params, exit, err = kParamErr);
proxy = _mrcs_create_dns_proxy_from_params_dictionary(params, &err);
require_noerr_quiet(err, exit);
mrcs_dns_proxy_set_euid(proxy, xpc_connection_get_euid(me->connection));
err = _mrcs_server_dns_proxy_start(proxy);
require_noerr_quiet(err, exit);
mrcs_dns_proxy_request_t request = _mrcs_dns_proxy_request_create(proxy, command_id, &err);
require_noerr_quiet(err, exit);
*ptr = request;
request = NULL;
exit:
mrcs_forget(&proxy);
return err;
}
//======================================================================================================================
static OSStatus
_mrcs_session_handle_dns_proxy_stop(const mrcs_session_t me, const xpc_object_t msg)
{
OSStatus err;
const uint64_t command_id = mrc_xpc_message_get_id(msg);
mrcs_dns_proxy_request_t *ptr = &me->dns_proxy_request_list;
while (*ptr && ((*ptr)->command_id != command_id)) {
ptr = &(*ptr)->next;
}
mrcs_dns_proxy_request_t request = *ptr;
require_action_quiet(request, exit, err = kIDErr);
*ptr = request->next;
request->next = NULL;
_mrcs_server_dns_proxy_stop(request->proxy);
mrcs_forget(&request);
err = kNoErr;
exit:
return err;
}
//======================================================================================================================
static mdns_xpc_dictionary_t
_mrcs_session_handle_dns_proxy_get_state(OSStatus * const out_error)
{
OSStatus err;
mdns_xpc_dictionary_t result = mdns_xpc_dictionary_create_empty();
require_action_quiet(result, exit, err = kNoResourcesErr);
char *state = _mrcs_server_dns_proxy_state(&err);
if (state) {
mrc_xpc_dns_proxy_state_result_set_description(result, state);
}
ForgetMem(&state);
exit:
if (out_error) {
*out_error = err;
}
return result;
}
//======================================================================================================================
static mrcs_dns_service_registration_request_t *
_mrcs_session_get_dns_service_registration(const mrcs_session_t me, const uint64_t command_id)
{
mrcs_dns_service_registration_request_t *ptr = &me->dns_service_reg_request_list;
while (*ptr && ((*ptr)->command_id != command_id)) {
ptr = &(*ptr)->next;
}
return ptr;
}
//======================================================================================================================
static xpc_object_t
mrc_xpc_dns_service_registration_create_connection_error_notification(const uint64_t command_id,
const OSStatus connection_error, OSStatus * const out_error)
{
OSStatus err;
xpc_object_t body = NULL;
xpc_object_t notification = NULL;
body = mrc_xpc_dns_service_registration_notification_create_connection_error_body(connection_error, &err);
mdns_require_noerr_quiet(err, exit);
notification = mrc_xpc_create_notification(command_id, body);
mdns_require_action_quiet(notification, exit, err = kNoResourcesErr);
err = kNoErr;
exit:
mdns_assign(out_error, err);
xpc_forget(&body);
return notification;
}
//======================================================================================================================
static void
_mrcs_session_handle_dns_service_registration_connection_error(const mrcs_session_t me, const uint64_t command_id,
const OSStatus connection_error)
{
if (me->connection && _mrcs_session_get_dns_service_registration(me, command_id)) {
OSStatus err;
xpc_object_t notification =
mrc_xpc_dns_service_registration_create_connection_error_notification(command_id, connection_error, &err);
if (err == kNoErr) {
xpc_connection_send_message(me->connection, notification);
}
mdns_forget(&notification);
}
}
//======================================================================================================================
static OSStatus
_mrcs_session_handle_dns_service_registration_start(const mrcs_session_t me, const xpc_object_t msg)
{
OSStatus err;
mrcs_dns_service_registration_request_t new_request = NULL;
const uint64_t command_id = mrc_xpc_message_get_id(msg);
mrcs_dns_service_registration_request_t * const ptr = _mrcs_session_get_dns_service_registration(me, command_id);
mrcs_dns_service_registration_request_t request = *ptr;
mdns_require_action_quiet(!request, exit, err = kAlreadyInUseErr);
const xpc_object_t params = mrc_xpc_message_get_params(msg);
mdns_require_action_quiet(params, exit, err = kParamErr);
const mdns_xpc_dictionary_t def_dict = mrc_xpc_dns_service_registration_params_get_defintion_dictionary(params);
mdns_require_action_quiet(def_dict, exit, err = kParamErr);
bool type_valid = false;
const mrc_dns_service_definition_type_t def_type =
mrc_xpc_dns_service_registration_params_get_definition_type(params, &type_valid);
mdns_require_action_quiet(type_valid, exit, err = kParamErr);
new_request = _mrcs_dns_service_registration_request_new();
require_action_quiet(new_request, exit, err = kNoMemoryErr);
switch (def_type) {
case mrc_dns_service_definition_type_do53:
new_request->do53_definition = mdns_dns_service_definition_create_from_xpc_dictionary(def_dict, &err);
mdns_require_noerr_quiet(err, exit);
new_request->ident = _mrcs_server_dns_service_registration_start(new_request->do53_definition, &err);
mdns_require_noerr_quiet(err, exit);
break;
case mrc_dns_service_definition_type_push:
{
new_request->push_definition = mdns_dns_push_service_definition_create_from_xpc_dictionary(def_dict, &err);
mdns_require_noerr_quiet(err, exit);
const bool reports_connection_errors =
mrc_xpc_dns_service_registration_params_get_reports_connection_errors(params);
dispatch_queue_t connection_error_queue = NULL;
mdns_event_handler_t connection_error_handler = NULL;
if (reports_connection_errors) {
connection_error_queue = _mrcs_server_queue();
connection_error_handler =
^(const mdns_event_t event, const OSStatus error)
{
switch (event) {
case mdns_event_invalidated:
mrcs_release(me);
break;
case mdns_event_error:
_mrcs_session_handle_dns_service_registration_connection_error(me, command_id, error);
break;
case mdns_event_update:
// Not handled for now.
break;
}
};
}
new_request->ident = _mrcs_server_dns_service_registration_start_with_connection_error_handler(
new_request->push_definition, connection_error_queue, connection_error_handler, &err);
mdns_require_noerr_quiet(err, exit);
if (reports_connection_errors) {
// Only retain the object if the registration has succeeded.
mrcs_retain(me);
}
break;
}
MDNS_COVERED_SWITCH_DEFAULT:
err = kParamErr;
goto exit;
}
new_request->command_id = command_id;
*ptr = new_request;
new_request = NULL;
exit:
mrcs_forget(&new_request);
return err;
}
//======================================================================================================================
static OSStatus
_mrcs_session_handle_dns_service_registration_stop(const mrcs_session_t me, const xpc_object_t msg)
{
OSStatus err;
const uint64_t command_id = mrc_xpc_message_get_id(msg);
mrcs_dns_service_registration_request_t * const ptr = _mrcs_session_get_dns_service_registration(me, command_id);
mrcs_dns_service_registration_request_t request = *ptr;
mdns_require_action_quiet(request, exit, err = kIDErr);
*ptr = request->next;
request->next = NULL;
err = _mrcs_server_dns_service_registration_stop(request->ident);
mrcs_forget(&request);
mdns_require_noerr_quiet(err, exit);
exit:
return err;
}
//======================================================================================================================
static OSStatus
_mrcs_session_handle_discovery_proxy_start(const mrcs_session_t me, const xpc_object_t msg)
{
OSStatus err;
CFMutableArrayRef addresses = NULL;
CFMutableArrayRef domains = NULL;
CFMutableArrayRef certificates = NULL;
mdns_require_action_quiet(!g_current_discovery_proxy_owner, exit, err = kAlreadyInitializedErr);
const xpc_object_t params = mrc_xpc_message_get_params(msg);
mdns_require_action_quiet(params, exit, err = kParamErr);
// Get interface index.
bool valid;
const uint32_t ifindex = mrc_xpc_discovery_proxy_params_get_interface(params, &valid);
mdns_require_action_quiet(valid, exit, err = kParamErr);
// Get address list.
const xpc_object_t addr_str_array = mrc_xpc_discovery_proxy_params_get_ip_address_strings(params);
mdns_require_action_quiet(addr_str_array, exit, err = kParamErr);
const size_t addr_count = xpc_array_get_count(addr_str_array);
mdns_require_action_quiet(addr_count > 0, exit, err = kParamErr);
addresses = CFArrayCreateMutable(kCFAllocatorDefault, (CFIndex)addr_count, &mdns_cfarray_callbacks);
mdns_require_action_quiet(addresses, exit, err = kNoResourcesErr);
for (size_t i = 0; i < addr_count; ++i) {
const char * const addr_str = xpc_array_get_string(addr_str_array, i);
mdns_require_action_quiet(addr_str, exit, err = kParamErr);
mdns_address_t addr = mdns_address_create_from_ip_address_string(addr_str);
mdns_require_action_quiet(addr, exit, err = kParamErr);
CFArrayAppendValue(addresses, addr);
mdns_forget(&addr);
}
// Get domain list.
const xpc_object_t domain_array = mrc_xpc_discovery_proxy_params_get_match_domains(params);
mdns_require_action_quiet(domain_array, exit, err = kParamErr);
const size_t domain_count = xpc_array_get_count(domain_array);
mdns_require_action_quiet(domain_count > 0, exit, err = kParamErr);
domains = CFArrayCreateMutable(kCFAllocatorDefault, 0, &mdns_cfarray_callbacks);
mdns_require_action_quiet(domains, exit, err = kNoResourcesErr);
for (size_t i = 0; i < domain_count; ++i) {
const char * const domain_str = xpc_array_get_string(domain_array, i);
mdns_require_action_quiet(domain_str, exit, err = kParamErr);
mdns_domain_name_t domain = mdns_domain_name_create(domain_str, mdns_domain_name_create_opts_none, &err);
mdns_require_noerr_quiet(err, exit);
CFArrayAppendValue(domains, domain);
mdns_forget(&domain);
}
// Get certificate list.
const xpc_object_t cert_array = mrc_xpc_discovery_proxy_params_get_server_certificates(params);
mdns_require_action_quiet(domain_array, exit, err = kParamErr);
const size_t cert_count = xpc_array_get_count(cert_array);
mdns_require_action_quiet(cert_count > 0, exit, err = kParamErr);
certificates = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
mdns_require_action_quiet(certificates, exit, err = kParamErr);
for (size_t i = 0; i < cert_count; ++i) {
size_t data_len = 0;
const uint8_t * const data_ptr = xpc_array_get_data(cert_array, i, &data_len);
mdns_require_action_quiet(data_ptr && (data_len > 0), exit, err = kParamErr);
CFDataRef cert_cf_data = CFDataCreate(kCFAllocatorDefault, data_ptr, (CFIndex)data_len);
mdns_require_action_quiet(cert_cf_data, exit, err = kNoResourcesErr);
CFArrayAppendValue(certificates, cert_cf_data);
CFForget(&cert_cf_data);
}
err = _mrcs_server_discovery_proxy_start(ifindex, addresses, domains, certificates);
mdns_require_noerr_quiet(err, exit);
g_current_discovery_proxy_owner = me;
mrcs_retain(g_current_discovery_proxy_owner);
exit:
CFForget(&addresses);
CFForget(&domains);
CFForget(&certificates);
return err;
}
//======================================================================================================================
static OSStatus
_mrcs_session_handle_discovery_proxy_stop(const mrcs_session_t me, __unused const xpc_object_t msg)
{
OSStatus err = kNotInitializedErr;
if (g_current_discovery_proxy_owner == me) {
err = _mrcs_server_discovery_proxy_stop();
mrcs_forget(&g_current_discovery_proxy_owner);
}
return err;
}
//======================================================================================================================
static mdns_xpc_dictionary_t
_mrcs_session_handle_record_cache_local_record_inquiry(OSStatus * const out_error)
{
OSStatus err;
mdns_xpc_dictionary_t result = mdns_xpc_dictionary_create_empty();
require_action_quiet(result, exit, err = kNoResourcesErr);
err = _mrcs_server_record_cache_enumerate_local_records(
^(const char * const name, const uint8_t * const rdata, const uint16_t rdlen, const sockaddr_ip * const source_addr)
{
char *txt_str = NULL;
if (rdata) {
const OSStatus txt_str_err = DNSRecordDataToString(rdata, rdlen, kDNSRecordType_TXT, &txt_str);
if (!txt_str) {
os_log_fault(_mdns_server_log(),
"Failed to create device-info TXT RDATA string -- record name: '%{private}s', error: %{mdns:err}ld",
name, (long)txt_str_err);
}
}
const char *source_addr_str = NULL;
char str_buf[Max(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
switch (source_addr->sa.sa_family) {
case AF_INET:
source_addr_str = inet_ntop(AF_INET, &source_addr->v4.sin_addr.s_addr, str_buf, sizeof(str_buf));
break;
case AF_INET6:
source_addr_str = inet_ntop(AF_INET6, source_addr->v6.sin6_addr.s6_addr, str_buf, sizeof(str_buf));
break;
default:
break;
}
mrc_xpc_cached_local_record_inquiry_result_add(result, name, txt_str, source_addr_str);
ForgetMem(&txt_str);
});
mdns_require_noerr_quiet(err, exit);
exit:
mdns_assign(out_error, err);
return result;
}
//======================================================================================================================
static OSStatus
_mrcs_session_handle_record_cache_flush(__unused const mrcs_session_t me, const xpc_object_t msg)
{
OSStatus err;
const xpc_object_t params = mrc_xpc_message_get_params(msg);
mdns_require_action_quiet(params, exit, err = kParamErr);
const char * const record_name = mrc_xpc_record_cache_flush_params_get_record_name(params);
mdns_require_action_quiet(record_name, exit, err = kParamErr);
bool have_key_tag = false;
const uint16_t key_tag = mrc_xpc_record_cache_flush_params_get_key_tag(params, &have_key_tag);
if (have_key_tag) {
err = _mrcs_server_record_cache_flush_by_name_and_key_tag(record_name, key_tag);
mdns_require_noerr_quiet(err, exit);
} else {
err = _mrcs_server_record_cache_flush_by_name(record_name);
mdns_require_noerr_quiet(err, exit);
}
exit:
return err;
}
//======================================================================================================================
static mdns_xpc_dictionary_t
_mrcs_session_handle_command(const mrcs_session_t me, const char * const command_str, const xpc_object_t msg,
OSStatus * const out_error)
{
OSStatus err;
mdns_xpc_dictionary_t result = NULL;
const mrcs_command_t command = _mrcs_server_command_string_to_code(command_str);
const char * const entitlement = _mrcs_server_get_required_entitlement(command);
mdns_require_action_quiet(entitlement, exit, err = kCommandErr);
const bool entitled = mdns_xpc_connection_is_entitled(me->connection, entitlement);
require_action_quiet(entitled, exit, err = kMissingEntitlementErr);
switch (command) {
case mrcs_command_dns_proxy_start:
err = _mrcs_session_handle_dns_proxy_start(me, msg);
break;
case mrcs_command_dns_proxy_stop:
err = _mrcs_session_handle_dns_proxy_stop(me, msg);
break;
case mrcs_command_dns_proxy_get_state:
result = _mrcs_session_handle_dns_proxy_get_state(&err);
break;
case mrcs_command_dns_service_registration_start:
err = _mrcs_session_handle_dns_service_registration_start(me, msg);
break;
case mrcs_command_dns_service_registration_stop:
err = _mrcs_session_handle_dns_service_registration_stop(me, msg);
break;
case mrcs_command_discovery_proxy_start:
err = _mrcs_session_handle_discovery_proxy_start(me, msg);
break;
case mrcs_command_discovery_proxy_stop:
err = _mrcs_session_handle_discovery_proxy_stop(me, msg);
break;
case mrcs_command_cached_local_record_inquiry:
result = _mrcs_session_handle_record_cache_local_record_inquiry(&err);
break;
case mrcs_command_record_cache_flush:
err = _mrcs_session_handle_record_cache_flush(me, msg);
break;
case mrcs_command_invalid:
MDNS_COVERED_SWITCH_DEFAULT:
err = kCommandErr;
break;
}
exit:
if (out_error) {
*out_error = err;
}
return result;
}
//======================================================================================================================
static void
_mrcs_session_handle_message(const mrcs_session_t me, const xpc_object_t msg)
{
OSStatus err;
mdns_xpc_dictionary_t result = NULL;
const char * const command = mrc_xpc_message_get_command(msg);
require_action_quiet(command, exit, err = kCommandErr);
result = _mrcs_session_handle_command(me, command, msg, &err);
require_noerr_quiet(err, exit);
exit:;
xpc_object_t reply = mrc_xpc_create_reply(msg, err, result);
xpc_forget(&result);
if (reply) {
xpc_connection_send_message(me->connection, reply);
xpc_forget(&reply);
}
}
//======================================================================================================================
// MARK: - Session Methods
static mrcs_dns_proxy_request_t
_mrcs_dns_proxy_request_create(const mrcs_dns_proxy_t proxy, const uint64_t command_id, OSStatus * const out_error)
{
OSStatus err;
mrcs_dns_proxy_request_t request = NULL;
mrcs_dns_proxy_request_t obj = _mrcs_dns_proxy_request_new();
require_action_quiet(obj, exit, err = kNoMemoryErr);
obj->proxy = proxy;
mrcs_retain(obj->proxy);
obj->command_id = command_id;
request = obj;
obj = NULL;
err = kNoErr;
exit:
if (out_error) {
*out_error = err;
}
mrcs_forget(&obj);
return request;
}
//======================================================================================================================
static char *
_mrcs_dns_proxy_request_copy_description(const mrcs_dns_proxy_request_t me, __unused const bool debug,
__unused const bool privacy)
{
char *description = NULL;
asprintf(&description, "<%s: %p>", me->base.kind->name, (void *)me);
return description;
}
//======================================================================================================================
static void
_mrcs_dns_proxy_request_finalize(const mrcs_dns_proxy_request_t me)
{
mrcs_forget(&me->proxy);
}
//======================================================================================================================
// MARK: - DNS Service Registration Request Methods
static char *
_mrcs_dns_service_registration_request_copy_description(const mrcs_dns_service_registration_request_t me,
const bool debug, const bool privacy)
{
char *description = NULL;
const mdns_description_options_t desp_opt = (privacy ? mdns_description_opt_privacy : mdns_description_opt_none);
mdns_string_builder_t sb = mdns_string_builder_create(0, NULL);
mdns_require_quiet(sb, exit);
OSStatus err;
if (debug) {
const mdns_kind_t kind = mrcs_get_kind(me);
err = mdns_string_builder_append_formatted(sb, "<%s: %p>: ", kind->name, (void *)me);
mdns_require_noerr_quiet(err, exit);
}
if (me->do53_definition) {
mdns_string_builder_append_description(sb, me->do53_definition, desp_opt);
} else if (me->push_definition) {
mdns_string_builder_append_description(sb, me->push_definition, desp_opt);
} else {
mdns_string_builder_append_formatted(sb, "<empty dns service definition>");
}
description = mdns_string_builder_copy_string(sb);
mdns_require_quiet(description, exit);
exit:
mdns_forget(&sb);
return description;
}
//======================================================================================================================
static void
_mrcs_dns_service_registration_request_finalize(const mrcs_dns_service_registration_request_t me)
{
mdns_forget(&me->do53_definition);
mdns_forget(&me->push_definition);
}
//======================================================================================================================
// MARK: - Helpers
static mrcs_dns_proxy_t
_mrcs_create_dns_proxy_from_params_dictionary(const xpc_object_t params, OSStatus * const out_error)
{
OSStatus err;
mrcs_dns_proxy_t result = NULL;
mrcs_dns_proxy_t proxy = mrcs_dns_proxy_create(&err);
require_noerr_quiet(err, exit);
// Set input interfaces.
const xpc_object_t input_interfaces = mrc_xpc_dns_proxy_params_get_input_interfaces(params);
require_action_quiet(input_interfaces, exit, err = kParamErr);
const size_t input_interface_count = xpc_array_get_count(input_interfaces);
require_action_quiet(input_interface_count > 0, exit, err = kParamErr);
bool valid;
for (size_t i = 0; i < input_interface_count; ++i) {
const uint32_t ifindex = mdns_xpc_array_get_uint32(input_interfaces, i, &valid);
require_action_quiet(valid, exit, err = kParamErr);
mrcs_dns_proxy_add_input_interface(proxy, ifindex);
}
// Set output interface.
const uint32_t output_ifindex = mrc_xpc_dns_proxy_params_get_output_interface(params, &valid);
require_action_quiet(valid, exit, err = kParamErr);
mrcs_dns_proxy_set_output_interface(proxy, output_ifindex);
// Set optional NAT64 prefix.
size_t nat64_prefix_bitlen;
const uint8_t * const nat64_prefix = mrc_xpc_dns_proxy_params_get_nat64_prefix(params, &nat64_prefix_bitlen);
if (nat64_prefix) {
err = mrcs_dns_proxy_set_nat64_prefix(proxy, nat64_prefix, nat64_prefix_bitlen);
require_noerr_quiet(err, exit);
}
const bool force_aaaa_synthesis = mrc_xpc_dns_proxy_params_get_force_aaaa_synthesis(params, &valid);
require_action_quiet(valid, exit, err = kParamErr);
mrcs_dns_proxy_enable_force_aaaa_synthesis(proxy, force_aaaa_synthesis);
result = proxy;
proxy = NULL;
exit:
if (out_error) {
*out_error = err;
}
mrcs_forget(&proxy);
return result;
}