blob: 55e51dd02278c1d47fcc403185115ec9a8ad1116 [file] [log] [blame]
/*
* Copyright (c) 2021-2022 Apple Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <mrc/dns_proxy.h>
#include "helpers.h"
#include "mdns_memcpy_bits.h"
#include "mrc_objects.h"
#include "mrc_xpc.h"
#include <arpa/inet.h>
#include <CoreUtils/CoreUtils.h>
#include <mdns/xpc.h>
#include <os/log.h>
#include "mdns_strict.h"
//======================================================================================================================
// MARK: - DNS Proxy Kind Definition
OS_CLOSED_ENUM(mrc_dns_proxy_state, int8_t,
mrc_dns_proxy_state_failed = -2,
mrc_dns_proxy_state_invalidated = -1,
mrc_dns_proxy_state_nascent = 0,
mrc_dns_proxy_state_starting = 1,
mrc_dns_proxy_state_started = 2
);
struct mrc_dns_proxy_s {
struct mdns_obj_s base; // Object base.
mrc_dns_proxy_t next; // Next DNS proxy object in list.
dispatch_queue_t queue; // User's dispatch queue.
xpc_object_t params; // DNS proxy parameters.
mrc_dns_proxy_event_handler_t event_handler; // Event handler.
uint64_t cmd_id; // Command ID.
mrc_dns_proxy_state_t state; // Current state.
bool immutable; // True if the DNS proxy is no longer externally mutable.
};
MRC_OBJECT_SUBKIND_DEFINE(dns_proxy);
//======================================================================================================================
struct mrc_dns_proxy_parameters_s {
struct mdns_obj_s base; // Object base.
xpc_object_t dict; // DNS proxy parameters.
};
MRC_OBJECT_SUBKIND_DEFINE(dns_proxy_parameters);
//======================================================================================================================
// MARK: - DNS Proxy State Inquiry Kind Definition
OS_CLOSED_ENUM(mrc_dns_proxy_state_inquiry_state, int8_t,
mrc_dns_proxy_state_inquiry_state_nascent = 0,
mrc_dns_proxy_state_inquiry_state_registered = 1,
mrc_dns_proxy_state_inquiry_state_in_progress = 2,
mrc_dns_proxy_state_inquiry_state_done = 3
);
struct mrc_dns_proxy_state_inquiry_s {
struct mdns_obj_s base; // Object base.
mrc_dns_proxy_state_inquiry_t next; // Next inquiry in list.
dispatch_queue_t queue; // User's dispatch queue.
mrc_dns_proxy_state_inquiry_response_handler_t handler; // Response handler.
uint64_t cmd_id; // Command ID.
mrc_dns_proxy_state_inquiry_state_t state; // Current inquiry state.
bool immutable; // True if this object is no longer externally mutable.
};
MRC_OBJECT_SUBKIND_DEFINE(dns_proxy_state_inquiry);
//======================================================================================================================
// MARK: - Local Prototypes
static void
_mrc_dns_proxy_activate(mrc_dns_proxy_t proxy);
static void
_mrc_dns_proxy_terminate_async(mrc_dns_proxy_t proxy, OSStatus error);
static void
_mrc_dns_proxy_register(mrc_dns_proxy_t proxy);
static void
_mrc_dns_proxy_deregister(mrc_dns_proxy_t proxy);
static void
_mrc_dns_proxy_start(mrc_dns_proxy_t proxy);
static void
_mrc_dns_proxy_stop(mrc_dns_proxy_t proxy);
static void
_mrc_dns_proxy_state_inquiry_register(mrc_dns_proxy_state_inquiry_t inquiry);
static void
_mrc_dns_proxy_state_inquiry_deregister(mrc_dns_proxy_state_inquiry_t inquiry);
static void
_mrc_dns_proxy_state_inquiry_send_command(mrc_dns_proxy_state_inquiry_t inquiry);
static void
_mrc_dns_proxy_state_inquiry_terminate_with_error(mrc_dns_proxy_state_inquiry_t inquiry, OSStatus error);
static void
_mrc_dns_proxy_state_inquiry_terminate_with_state_description(mrc_dns_proxy_state_inquiry_t inquiry,
mdns_xpc_string_t description);
static os_log_t
_mrc_client_log(void);
static dispatch_queue_t
_mrc_client_queue(void);
static xpc_connection_t
_mrc_client_connection(void);
static OSStatus
_mrc_xpc_dns_proxy_params_print_description(xpc_object_t params, bool debug, bool privacy, char *buf, size_t buf_len,
size_t *out_len, size_t *out_full_len);
//======================================================================================================================
// MARK: - Globals
static mrc_dns_proxy_t g_dns_proxy_list = NULL;
static mrc_dns_proxy_state_inquiry_t g_dns_proxy_state_inquiry_list = NULL;
//======================================================================================================================
// MARK: - DNS Proxy Public Methods
mrc_dns_proxy_t
mrc_dns_proxy_create(const mrc_dns_proxy_parameters_t params, OSStatus * const out_error)
{
OSStatus err;
mrc_dns_proxy_t proxy = NULL;
mrc_dns_proxy_t obj = _mrc_dns_proxy_new();
require_action_quiet(obj, exit, err = kNoMemoryErr);
obj->params = xpc_copy(params->dict);
require_action_quiet(obj->params, exit, err = kNoResourcesErr);
proxy = obj;
obj = NULL;
err = kNoErr;
exit:
if (out_error) {
*out_error = err;
}
mrc_forget(&obj);
return proxy;
}
//======================================================================================================================
void
mrc_dns_proxy_set_queue(const mrc_dns_proxy_t me, const dispatch_queue_t queue)
{
require_return(!me->immutable);
if (queue) {
dispatch_retain(queue);
}
dispatch_forget(&me->queue);
me->queue = queue;
}
//======================================================================================================================
void
mrc_dns_proxy_set_event_handler(const mrc_dns_proxy_t me, const mrc_dns_proxy_event_handler_t handler)
{
require_return(!me->immutable);
const mrc_dns_proxy_event_handler_t new_handler = handler ? Block_copy(handler) : NULL;
BlockForget(&me->event_handler);
me->event_handler = new_handler;
}
//======================================================================================================================
void
mrc_dns_proxy_activate(const mrc_dns_proxy_t me)
{
me->immutable = true;
mrc_retain(me);
dispatch_async(_mrc_client_queue(),
^{
_mrc_dns_proxy_activate(me);
mrc_release(me);
});
}
//======================================================================================================================
void
mrc_dns_proxy_invalidate(const mrc_dns_proxy_t me)
{
me->immutable = true;
_mrc_dns_proxy_terminate_async(me, kNoErr);
}
//======================================================================================================================
// MARK: - DNS Proxy Private Methods
static OSStatus
_mrc_dns_proxy_print_description(const mrc_dns_proxy_t me, const bool debug, const bool privacy, char * const buf,
const size_t buf_len, size_t * const out_len, size_t * const out_full_len)
{
OSStatus err;
char *dst = buf;
const char * const lim = &buf[buf_len];
size_t full_len = 0;
#define _do_appendf(...) \
do { \
const int _n = mdns_snprintf_add(&dst, lim, __VA_ARGS__); \
require_action_quiet(_n >= 0, exit, err = kUnknownErr); \
full_len += (size_t)_n; \
} while(0)
if (debug) {
_do_appendf("<%s: %p>: ", me->base.kind->name, me);
}
#undef _do_appendf
size_t wrote_len, desc_full_len;
err = _mrc_xpc_dns_proxy_params_print_description(me->params, debug, privacy, dst, (size_t)(lim - dst), &wrote_len,
&desc_full_len);
require_noerr_quiet(err, exit);
dst += wrote_len;
full_len += desc_full_len;
if (out_len) {
*out_len = (size_t)(dst - buf);
}
if (out_full_len) {
*out_full_len = full_len;
}
exit:
return err;
}
//======================================================================================================================
static char *
_mrc_dns_proxy_copy_description(const mrc_dns_proxy_t me, const bool debug, const bool privacy)
{
char *description = NULL;
char buf[128];
size_t full_len;
OSStatus err = _mrc_dns_proxy_print_description(me, debug, privacy, buf, sizeof(buf), NULL, &full_len);
require_noerr_quiet(err, exit);
if (full_len < sizeof(buf)) {
description = mdns_strdup(buf);
} else {
const size_t buf_len = full_len + 1;
char *buf_ptr = (char *)mdns_malloc(buf_len);
require_quiet(buf_ptr, exit);
err = _mrc_dns_proxy_print_description(me, debug, privacy, buf_ptr, buf_len, NULL, NULL);
require_noerr_action_quiet(err, exit, ForgetMem(&buf_ptr));
description = buf_ptr;
}
exit:
return description;
}
//======================================================================================================================
static void
_mrc_dns_proxy_finalize(const mrc_dns_proxy_t me)
{
dispatch_forget(&me->queue);
xpc_forget(&me->params);
}
//======================================================================================================================
static void
_mrc_dns_proxy_activate(const mrc_dns_proxy_t me)
{
require_return(me->state == mrc_dns_proxy_state_nascent);
_mrc_dns_proxy_register(me);
_mrc_dns_proxy_start(me);
}
//======================================================================================================================
static void
_mrc_dns_proxy_generate_event_with_error(const mrc_dns_proxy_t me, const mrc_dns_proxy_event_t event,
const OSStatus error)
{
require_return(me->queue && me->event_handler);
const mrc_dns_proxy_event_handler_t event_handler = Block_copy(me->event_handler);
dispatch_async(me->queue,
^{
event_handler(event, error);
mrc_dns_proxy_event_handler_t tmp = event_handler;
BlockForget(&tmp);
});
}
//======================================================================================================================
static void
_mrc_dns_proxy_generate_event(const mrc_dns_proxy_t me, const mrc_dns_proxy_event_t event)
{
_mrc_dns_proxy_generate_event_with_error(me, event, kNoErr);
}
//======================================================================================================================
static uint64_t
_mrc_client_get_new_command_id(void)
{
static uint64_t last_command_id = 0;
return ++last_command_id;
}
//======================================================================================================================
static void
_mrc_dns_proxy_register(const mrc_dns_proxy_t me)
{
require_return(me->cmd_id == 0);
me->cmd_id = _mrc_client_get_new_command_id();
mrc_dns_proxy_t *ptr = &g_dns_proxy_list;
while (*ptr) {
ptr = &(*ptr)->next;
}
*ptr = me;
mrc_retain(*ptr);
}
//======================================================================================================================
static void
_mrc_dns_proxy_deregister(const mrc_dns_proxy_t me)
{
mrc_dns_proxy_t *ptr = &g_dns_proxy_list;
while (*ptr && (*ptr != me)) {
ptr = &(*ptr)->next;
}
if (*ptr) {
mrc_release(*ptr);
*ptr = me->next;
me->next = NULL;
}
}
static void
_mrcs_dns_proxy_handle_dns_proxy_start_reply(const mrc_dns_proxy_t me, const uint64_t cmd_id, const xpc_object_t reply)
{
require_return(me->cmd_id == cmd_id);
require_return(me->state == mrc_dns_proxy_state_starting);
if (xpc_get_type(reply) == XPC_TYPE_DICTIONARY) {
bool valid;
OSStatus err = mrc_xpc_message_get_error(reply, &valid);
if (!valid) {
err = kResponseErr;
}
os_log_with_type(_mrc_client_log(), err ? OS_LOG_TYPE_ERROR : OS_LOG_TYPE_INFO,
"[DP%llu] DNS proxy start reply -- error: %{mdns:err}ld", (unsigned long long)me->cmd_id, (long)err);
if (!err) {
me->state = mrc_dns_proxy_state_started;
_mrc_dns_proxy_generate_event(me, mrc_dns_proxy_event_started);
} else {
me->state = mrc_dns_proxy_state_failed;
_mrc_dns_proxy_terminate_async(me, err);
}
} else {
char *description = xpc_copy_description(reply);
os_log_error(_mrc_client_log(),
"[DP%llu] Abnormal DNS proxy start reply: %{public}s", (unsigned long long)me->cmd_id, description);
ForgetMem(&description);
if (reply != XPC_ERROR_CONNECTION_INTERRUPTED) {
const OSStatus err = (reply == XPC_ERROR_CONNECTION_INVALID) ? kConnectionErr : kResponseErr;
me->state = mrc_dns_proxy_state_failed;
_mrc_dns_proxy_terminate_async(me, err);
}
}
}
//======================================================================================================================
static void
_mrc_dns_proxy_start(const mrc_dns_proxy_t me)
{
me->state = mrc_dns_proxy_state_starting;
xpc_object_t msg = mrc_xpc_create_dns_proxy_start_command_message(me->cmd_id, me->params);
mrc_retain(me);
const uint64_t cmd_id = me->cmd_id;
xpc_connection_send_message_with_reply(_mrc_client_connection(), msg, _mrc_client_queue(),
^(const xpc_object_t reply)
{
_mrcs_dns_proxy_handle_dns_proxy_start_reply(me, cmd_id, reply);
mrc_release(me);
});
xpc_forget(&msg);
}
//======================================================================================================================
static void
_mrcs_dns_proxy_handle_dns_proxy_stop_reply(const mrc_dns_proxy_t me, const xpc_object_t reply)
{
if (xpc_get_type(reply) == XPC_TYPE_DICTIONARY) {
bool valid;
OSStatus err = mrc_xpc_message_get_error(reply, &valid);
if (!valid) {
err = kResponseErr;
}
os_log_with_type(_mrc_client_log(), err ? OS_LOG_TYPE_ERROR : OS_LOG_TYPE_INFO,
"[DP%llu] DNS proxy stop reply -- error: %{mdns:err}ld", (unsigned long long)me->cmd_id, (long)err);
} else {
char *description = xpc_copy_description(reply);
os_log_error(_mrc_client_log(),
"[DP%llu] Abnormal DNS proxy stop reply: %{public}s", (unsigned long long)me->cmd_id, description);
ForgetMem(&description);
}
}
//======================================================================================================================
static void
_mrc_dns_proxy_stop(const mrc_dns_proxy_t me)
{
xpc_object_t msg = mrc_xpc_create_dns_proxy_stop_command_message(me->cmd_id);
mrc_retain(me);
xpc_connection_send_message_with_reply(_mrc_client_connection(), msg, _mrc_client_queue(),
^(const xpc_object_t reply)
{
_mrcs_dns_proxy_handle_dns_proxy_stop_reply(me, reply);
mrc_release(me);
});
xpc_forget(&msg);
}
//======================================================================================================================
static void
_mrc_dns_proxy_terminate_direct(const mrc_dns_proxy_t me, const OSStatus error)
{
require_return(me->state != mrc_dns_proxy_state_invalidated);
_mrc_dns_proxy_deregister(me);
switch (me->state) {
case mrc_dns_proxy_state_starting:
case mrc_dns_proxy_state_started:
_mrc_dns_proxy_stop(me);
break;
case mrc_dns_proxy_state_failed:
case mrc_dns_proxy_state_invalidated:
case mrc_dns_proxy_state_nascent:
break;
}
me->state = mrc_dns_proxy_state_invalidated;
_mrc_dns_proxy_generate_event_with_error(me, mrc_dns_proxy_event_invalidation, error);
BlockForget(&me->event_handler);
}
//======================================================================================================================
static void
_mrc_dns_proxy_terminate_async(const mrc_dns_proxy_t me, const OSStatus error)
{
mrc_retain(me);
dispatch_async(_mrc_client_queue(),
^{
_mrc_dns_proxy_terminate_direct(me, error);
mrc_release(me);
});
}
//======================================================================================================================
// MARK: - DNS Proxy Parameters Public Methods
mrc_dns_proxy_parameters_t
mrc_dns_proxy_parameters_create(OSStatus * const out_error)
{
OSStatus err;
mrc_dns_proxy_parameters_t params = NULL;
mrc_dns_proxy_parameters_t obj = _mrc_dns_proxy_parameters_new();
require_action_quiet(obj, exit, err = kNoMemoryErr);
obj->dict = xpc_dictionary_create(NULL, NULL, 0);
require_action_quiet(obj->dict, exit, err = kNoResourcesErr);
params = obj;
obj = NULL;
err = kNoErr;
exit:
if (out_error) {
*out_error = err;
}
mrc_forget(&obj);
return params;
}
//======================================================================================================================
void
mrc_dns_proxy_parameters_add_input_interface(const mrc_dns_proxy_parameters_t me, const uint32_t ifindex)
{
mrc_xpc_dns_proxy_params_add_input_interface(me->dict, ifindex);
}
//======================================================================================================================
void
mrc_dns_proxy_parameters_set_output_interface(const mrc_dns_proxy_parameters_t me, const uint32_t ifindex)
{
mrc_xpc_dns_proxy_params_set_output_interface(me->dict, ifindex);
}
//======================================================================================================================
void
mrc_dns_proxy_parameters_set_nat64_prefix(const mrc_dns_proxy_parameters_t me, const uint8_t * const prefix,
const size_t prefix_bitlen)
{
mrc_xpc_dns_proxy_params_set_nat64_prefix(me->dict, prefix, prefix_bitlen);
}
//======================================================================================================================
void
mrc_dns_proxy_parameters_set_force_aaaa_synthesis(const mrc_dns_proxy_parameters_t me, bool value)
{
mrc_xpc_dns_proxy_params_set_force_aaaa_synthesis(me->dict, value);
}
//======================================================================================================================
bool
mrc_dns_proxy_parameters_enumerate_input_interfaces(const mrc_dns_proxy_parameters_t me,
const mrc_dns_proxy_parameters_interface_applier_t applier)
{
bool completed = false;
const xpc_object_t interfaces = mrc_xpc_dns_proxy_params_get_input_interfaces(me->dict);
const size_t n = interfaces ? xpc_array_get_count(interfaces) : 0;
for (size_t i = 0; i < n; ++i) {
const uint32_t ifindex = mdns_xpc_array_get_uint32(interfaces, i, NULL);
const bool proceed = applier(ifindex);
if (!proceed) {
goto exit;
}
}
completed = true;
exit:
return completed;
}
//======================================================================================================================
uint32_t
mrc_dns_proxy_parameters_get_output_interface(const mrc_dns_proxy_parameters_t me)
{
return mrc_xpc_dns_proxy_params_get_output_interface(me->dict, NULL);
}
//======================================================================================================================
bool
mrc_dns_proxy_parameters_get_force_aaaa_synthesis(const mrc_dns_proxy_parameters_t me)
{
return mrc_xpc_dns_proxy_params_get_force_aaaa_synthesis(me->dict, NULL);
}
//======================================================================================================================
// MARK: - DNS Proxy Parameters Private Methods
static OSStatus
_mrc_dns_proxy_parameters_print_description(const mrc_dns_proxy_parameters_t me, const bool debug,
const bool privacy, char * const buf, const size_t buf_len, size_t * const out_len, size_t * const out_full_len)
{
OSStatus err;
char *dst = buf;
const char * const lim = &buf[buf_len];
size_t full_len = 0;
#define _do_appendf(...) \
do { \
const int _n = mdns_snprintf_add(&dst, lim, __VA_ARGS__); \
require_action_quiet(_n >= 0, exit, err = kUnknownErr); \
full_len += (size_t)_n; \
} while(0)
if (debug) {
_do_appendf("<%s: %p>: ", me->base.kind->name, me);
}
#undef _do_appendf
size_t wrote_len, desc_full_len;
err = _mrc_xpc_dns_proxy_params_print_description(me->dict, debug, privacy, dst, (size_t)(lim - dst), &wrote_len,
&desc_full_len);
require_noerr_quiet(err, exit);
dst += wrote_len;
full_len += desc_full_len;
if (out_len) {
*out_len = (size_t)(dst - buf);
}
if (out_full_len) {
*out_full_len = full_len;
}
exit:
return err;
}
//======================================================================================================================
static char *
_mrc_dns_proxy_parameters_copy_description(const mrc_dns_proxy_parameters_t me, const bool debug, const bool privacy)
{
char *description = NULL;
char buf[128];
size_t full_len;
OSStatus err = _mrc_dns_proxy_parameters_print_description(me, debug, privacy, buf, sizeof(buf), NULL, &full_len);
require_noerr_quiet(err, exit);
if (full_len < sizeof(buf)) {
description = mdns_strdup(buf);
} else {
const size_t buf_len = full_len + 1;
char *buf_ptr = (char *)mdns_malloc(buf_len);
require_quiet(buf_ptr, exit);
err = _mrc_dns_proxy_parameters_print_description(me, debug, privacy, buf_ptr, buf_len, NULL, NULL);
require_noerr_action_quiet(err, exit, ForgetMem(&buf_ptr));
description = buf_ptr;
}
exit:
return description;
}
//======================================================================================================================
static void
_mrc_dns_proxy_parameters_finalize(const mrc_dns_proxy_parameters_t me)
{
xpc_forget(&me->dict);
}
//======================================================================================================================
// MARK: - DNS Proxy State Inquiry Public Methods
mrc_dns_proxy_state_inquiry_t
mrc_dns_proxy_state_inquiry_create()
{
return _mrc_dns_proxy_state_inquiry_new();
}
//======================================================================================================================
void
mrc_dns_proxy_state_inquiry_set_queue(const mrc_dns_proxy_state_inquiry_t me, const dispatch_queue_t queue)
{
require_return(!me->immutable);
if (queue) {
dispatch_retain(queue);
}
dispatch_forget(&me->queue);
me->queue = queue;
}
//======================================================================================================================
void
mrc_dns_proxy_state_inquiry_set_handler(const mrc_dns_proxy_state_inquiry_t me,
const mrc_dns_proxy_state_inquiry_response_handler_t handler)
{
require_return(!me->immutable);
const mrc_dns_proxy_state_inquiry_response_handler_t new_handler = handler ? Block_copy(handler) : NULL;
BlockForget(&me->handler);
me->handler = new_handler;
}
//======================================================================================================================
void
mrc_dns_proxy_state_inquiry_activate(const mrc_dns_proxy_state_inquiry_t me)
{
me->immutable = true;
mrc_retain(me);
dispatch_async(_mrc_client_queue(),
^{
_mrc_dns_proxy_state_inquiry_register(me);
_mrc_dns_proxy_state_inquiry_send_command(me);
mrc_release(me);
});
}
//======================================================================================================================
void
mrc_dns_proxy_state_inquiry_invalidate(const mrc_dns_proxy_state_inquiry_t me)
{
me->immutable = true;
mrc_retain(me);
dispatch_async(_mrc_client_queue(),
^{
_mrc_dns_proxy_state_inquiry_terminate_with_state_description(me, NULL);
mrc_release(me);
});
}
//======================================================================================================================
// MARK: - DNS Proxy State Inquiry Private Methods
static char *
_mrc_dns_proxy_state_inquiry_copy_description(const mrc_dns_proxy_state_inquiry_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
_mrc_dns_proxy_state_inquiry_finalize(const mrc_dns_proxy_state_inquiry_t me)
{
dispatch_forget(&me->queue);
}
//======================================================================================================================
static void
_mrc_dns_proxy_state_inquiry_register(const mrc_dns_proxy_state_inquiry_t me)
{
require_return(me->state == mrc_dns_proxy_state_inquiry_state_nascent);
me->state = mrc_dns_proxy_state_inquiry_state_registered;
mrc_dns_proxy_state_inquiry_t *ptr = &g_dns_proxy_state_inquiry_list;
while (*ptr) {
ptr = &(*ptr)->next;
}
*ptr = me;
mrc_retain(*ptr);
}
//======================================================================================================================
static void
_mrc_dns_proxy_state_inquiry_deregister(const mrc_dns_proxy_state_inquiry_t me)
{
mrc_dns_proxy_state_inquiry_t *ptr = &g_dns_proxy_state_inquiry_list;
while (*ptr && (*ptr != me)) {
ptr = &(*ptr)->next;
}
if (*ptr) {
mrc_release(*ptr);
*ptr = me->next;
me->next = NULL;
}
}
//======================================================================================================================
static void
_mrc_dns_proxy_state_inquiry_handle_reply(const mrc_dns_proxy_state_inquiry_t me, const uint64_t cmd_id,
const xpc_object_t reply)
{
require_return(me->cmd_id == cmd_id);
require_return(me->state == mrc_dns_proxy_state_inquiry_state_in_progress);
if (xpc_get_type(reply) == XPC_TYPE_DICTIONARY) {
bool valid;
OSStatus err = mrc_xpc_message_get_error(reply, &valid);
if (!valid) {
err = kResponseErr;
}
mdns_xpc_string_t state = NULL;
if (!err) {
mdns_xpc_dictionary_t result = mrc_xpc_message_get_result(reply);
if (result) {
state = mrc_xpc_dns_proxy_state_result_get_description(result);
}
if (!state) {
err = kResponseErr;
}
}
os_log_with_type(_mrc_client_log(), err ? OS_LOG_TYPE_ERROR : OS_LOG_TYPE_INFO,
"[DP%llu] DNS proxy state reply -- error: %{mdns:err}ld", (unsigned long long)me->cmd_id, (long)err);
if (err) {
_mrc_dns_proxy_state_inquiry_terminate_with_error(me, err);
} else {
_mrc_dns_proxy_state_inquiry_terminate_with_state_description(me, state);
}
} else {
char *description = xpc_copy_description(reply);
os_log_error(_mrc_client_log(),
"[DP%llu] Abnormal DNS proxy state reply: %{public}s", (unsigned long long)me->cmd_id, description);
ForgetMem(&description);
if (reply != XPC_ERROR_CONNECTION_INTERRUPTED) {
const OSStatus err = (reply == XPC_ERROR_CONNECTION_INVALID) ? kConnectionErr : kResponseErr;
_mrc_dns_proxy_state_inquiry_terminate_with_error(me, err);
}
}
}
//======================================================================================================================
static void
_mrc_dns_proxy_state_inquiry_send_command(const mrc_dns_proxy_state_inquiry_t me)
{
require_return(
(me->state == mrc_dns_proxy_state_inquiry_state_registered) ||
(me->state == mrc_dns_proxy_state_inquiry_state_in_progress)
);
me->state = mrc_dns_proxy_state_inquiry_state_in_progress;
me->cmd_id = _mrc_client_get_new_command_id();
const uint64_t cmd_id = me->cmd_id;
xpc_object_t msg = mrc_xpc_create_dns_proxy_get_state_command_message(cmd_id);
mrc_retain(me);
xpc_connection_send_message_with_reply(_mrc_client_connection(), msg, _mrc_client_queue(),
^(const xpc_object_t reply)
{
_mrc_dns_proxy_state_inquiry_handle_reply(me, cmd_id, reply);
mrc_release(me);
});
xpc_forget(&msg);
}
//======================================================================================================================
static void
_mrc_dns_proxy_state_inquiry_terminate_imp(const mrc_dns_proxy_state_inquiry_t me,
const mdns_xpc_string_t state_description, const OSStatus error)
{
require_return(me->state != mrc_dns_proxy_state_inquiry_state_done);
_mrc_dns_proxy_state_inquiry_deregister(me);
me->state = mrc_dns_proxy_state_inquiry_state_done;
if (me->queue && me->handler) {
const mrc_dns_proxy_state_inquiry_response_handler_t handler = me->handler;
me->handler = NULL;
if (state_description) {
mdns_xpc_string_retain(state_description);
}
dispatch_async(me->queue,
^{
const char * const cstr = state_description ? mdns_xpc_string_get_string_ptr(state_description) : NULL;
handler(cstr, error);
mrc_dns_proxy_state_inquiry_response_handler_t tmp_handler = handler;
BlockForget(&tmp_handler);
mdns_xpc_string_t tmp = state_description;
mdns_xpc_string_forget(&tmp);
});
}
BlockForget(&me->handler);
}
//======================================================================================================================
static void
_mrc_dns_proxy_state_inquiry_terminate_with_error(const mrc_dns_proxy_state_inquiry_t me, const OSStatus error)
{
_mrc_dns_proxy_state_inquiry_terminate_imp(me, NULL, error);
}
//======================================================================================================================
static void
_mrc_dns_proxy_state_inquiry_terminate_with_state_description(const mrc_dns_proxy_state_inquiry_t me,
const mdns_xpc_string_t description)
{
_mrc_dns_proxy_state_inquiry_terminate_imp(me, description, kNoErr);
}
//======================================================================================================================
// MARK: - Internal Functions
static os_log_t
_mrc_client_log(void)
{
static dispatch_once_t s_once = 0;
static os_log_t s_log = NULL;
dispatch_once(&s_once,
^{
s_log = os_log_create("com.apple.mdns", "mrc");
});
return s_log;
}
//======================================================================================================================
static dispatch_queue_t
_mrc_client_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.mdns.mrc", DISPATCH_QUEUE_SERIAL);
});
return s_queue;
}
//======================================================================================================================
static void
_mrc_client_handle_connection_interruption(void)
{
for (mrc_dns_proxy_t proxy = g_dns_proxy_list; proxy; proxy = proxy->next) {
switch (proxy->state) {
case mrc_dns_proxy_state_starting:
case mrc_dns_proxy_state_started:
if (proxy->state == mrc_dns_proxy_state_started) {
_mrc_dns_proxy_generate_event(proxy, mrc_dns_proxy_event_interruption);
}
proxy->cmd_id = _mrc_client_get_new_command_id();
_mrc_dns_proxy_start(proxy);
break;
case mrc_dns_proxy_state_failed:
case mrc_dns_proxy_state_invalidated:
case mrc_dns_proxy_state_nascent:
break;
}
}
for (mrc_dns_proxy_state_inquiry_t inquiry = g_dns_proxy_state_inquiry_list; inquiry; inquiry = inquiry->next) {
switch (inquiry->state) {
case mrc_dns_proxy_state_inquiry_state_in_progress:
_mrc_dns_proxy_state_inquiry_send_command(inquiry);
break;
case mrc_dns_proxy_state_inquiry_state_nascent:
case mrc_dns_proxy_state_inquiry_state_registered:
case mrc_dns_proxy_state_inquiry_state_done:
break;
}
}
}
//======================================================================================================================
static xpc_connection_t
_mrc_client_connection(void)
{
static xpc_connection_t s_connection = NULL;
require_quiet(!s_connection, exit);
const uint64_t flags = XPC_CONNECTION_MACH_SERVICE_PRIVILEGED;
s_connection = xpc_connection_create_mach_service(g_mrc_mach_service_name, _mrc_client_queue(), flags);
xpc_connection_set_event_handler(s_connection,
^(const xpc_object_t event)
{
const xpc_type_t type = xpc_get_type(event);
if (type == XPC_TYPE_ERROR) {
os_log_error(_mrc_client_log(),
"Connection error: %{public}s", xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
} else {
char *description = xpc_copy_description(event);
os_log(_mrc_client_log(),
"Unexpected connection event: %s", description);
ForgetMem(&description);
}
if (event == XPC_ERROR_CONNECTION_INTERRUPTED) {
_mrc_client_handle_connection_interruption();
}
});
xpc_connection_activate(s_connection);
exit:
return s_connection;
}
//======================================================================================================================
static OSStatus
_mrc_xpc_dns_proxy_params_print_description(const xpc_object_t params, __unused const bool debug,
__unused const bool privacy, char * const buf, const size_t buf_len, size_t * const out_len,
size_t * const out_full_len)
{
OSStatus err;
char *dst = buf;
const char * const lim = &buf[buf_len];
size_t full_len = 0;
#define _do_appendf(...) \
do { \
const int _n = mdns_snprintf_add(&dst, lim, __VA_ARGS__); \
require_action_quiet(_n >= 0, exit, err = kUnknownErr); \
full_len += (size_t)_n; \
} while(0)
_do_appendf("input interface indexes: {");
const xpc_object_t input_interfaces = mrc_xpc_dns_proxy_params_get_input_interfaces(params);
const size_t n = input_interfaces ? xpc_array_get_count(input_interfaces) : 0;
for (size_t i = 0; i < n; ++i) {
_do_appendf("%s%u", (i == 0) ? "" : ", ", mdns_xpc_array_get_uint32(input_interfaces, i, NULL));
}
_do_appendf("}, output interface index: %u", mrc_xpc_dns_proxy_params_get_output_interface(params, NULL));
size_t bitlen;
const uint8_t * const prefix = mrc_xpc_dns_proxy_params_get_nat64_prefix(params, &bitlen);
if (prefix) {
uint8_t ipv6_addr[16] = {0};
mdns_memcpy_bits(ipv6_addr, prefix, Min(bitlen, sizeof(ipv6_addr) * 8));
char addr_buf[INET6_ADDRSTRLEN];
const char * const addr_str = inet_ntop(AF_INET6, ipv6_addr, addr_buf, (socklen_t)sizeof(addr_buf));
err = map_global_value_errno(addr_str, addr_str);
require_noerr_quiet(err, exit);
_do_appendf(", nat64 prefix: %s/%zu", addr_str, bitlen);
}
const bool force_aaaa_synthesis = mrc_xpc_dns_proxy_params_get_force_aaaa_synthesis(params, NULL);
_do_appendf(", forces AAAA synthesis: %s", YesNoStr(force_aaaa_synthesis));
#undef _do_appendf
if (out_len) {
*out_len = (size_t)(dst - buf);
}
if (out_full_len) {
*out_full_len = full_len;
}
err = kNoErr;
exit:
return err;
}