blob: 6235544998760e33d2071f32a046bdf4d2f527e5 [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_dns_proxy.h"
#include "helpers.h"
#include "memory.h"
#include "mrcs_cf_support.h"
#include "mrcs_objects.h"
#include <arpa/inet.h>
#include <CoreUtils/CoreUtils.h>
#include <mdns/system.h>
#include "mdns_strict.h"
//======================================================================================================================
// MARK: - DNS Proxy Kind Definition
typedef struct {
char * name; // Network interface's name.
uint32_t index; // Network interface's index.
} mrcs_interface_t;
struct mrcs_dns_proxy_s {
struct mdns_obj_s base; // Object base.
nw_nat64_prefix_t nat64_prefix; // NAT64 prefix.
mrcs_interface_t * input_interfaces; // Array of input interfaces.
size_t input_interface_count; // Current number of input interface indexes.
mrcs_interface_t output_interface; // Output interface.
uid_t euid; // Effective user ID.
bool nat64_prefix_valid; // True if the NAT64 prefix is currently valid.
bool force_aaaa_synthesis; // True if the DNS proxy policy is to force AAAA synthesis.
};
MRCS_OBJECT_SUBKIND_DEFINE(dns_proxy);
//======================================================================================================================
// MARK: - DNS Proxy Kind Definition
struct mrcs_dns_proxy_manager_s {
struct mdns_obj_s base; // Object base.
CFMutableArrayRef proxies; // DNS proxy collection.
};
MRCS_OBJECT_SUBKIND_DEFINE(dns_proxy_manager);
//======================================================================================================================
// MARK: - Local Prototypes
static bool
_mrcs_dns_proxy_manager_conflicts_with_proxy(mrcs_dns_proxy_manager_t me, mrcs_dns_proxy_t proxy);
static bool
_mrcs_nat64_prefix_equal_null_safe(const nw_nat64_prefix_t *p1, const nw_nat64_prefix_t *p2);
static int
_mrcs_nat64_prefix_to_byte_length(const nw_nat64_prefix_t *prefix);
static const char *
_mrcs_string_or_empty(const char *string);
//======================================================================================================================
// MARK: - DNS Proxy Public Methods
mrcs_dns_proxy_t
mrcs_dns_proxy_create(OSStatus * const out_error)
{
OSStatus err;
mrcs_dns_proxy_t proxy = NULL;
mrcs_dns_proxy_t obj = _mrcs_dns_proxy_new();
require_action_quiet(obj, exit, err = kNoMemoryErr);
proxy = obj;
obj = NULL;
err = kNoErr;
exit:
if (out_error) {
*out_error = err;
}
mrcs_forget(&obj);
return proxy;
}
//======================================================================================================================
OSStatus
mrcs_dns_proxy_add_input_interface(const mrcs_dns_proxy_t me, const uint32_t ifindex)
{
OSStatus err;
if (!mrcs_dns_proxy_contains_input_interface(me, ifindex)) {
require_action_quiet(me->input_interface_count <= (SIZE_MAX - 1), exit, err = kCountErr);
const size_t new_count = me->input_interface_count + 1;
mrcs_interface_t *new_interfaces = (mrcs_interface_t *)mdns_calloc(new_count, sizeof(*new_interfaces));
require_action_quiet(new_interfaces, exit, err = kNoMemoryErr);
// Shallow copy the new interface array, but remember to NULL out the old allocated interface name pointer.
for (size_t i = 0; i < me->input_interface_count; ++i) {
new_interfaces[i] = me->input_interfaces[i];
me->input_interfaces[i].name = NULL;
}
// Set the newest input interface.
mrcs_interface_t * const interface = &new_interfaces[me->input_interface_count];
interface->index = ifindex;
interface->name = mdns_system_interface_index_to_name(interface->index, NULL);
// Free the old input interface array and take ownership of the new one.
ForgetMem(&me->input_interfaces);
me->input_interfaces = new_interfaces;
new_interfaces = NULL;
me->input_interface_count = new_count;
}
err = kNoErr;
exit:
return err;
}
//======================================================================================================================
void
mrcs_dns_proxy_set_output_interface(const mrcs_dns_proxy_t me, const uint32_t ifindex)
{
mrcs_interface_t * const interface = &me->output_interface;
interface->index = ifindex;
ForgetMem(&interface->name);
interface->name = mdns_system_interface_index_to_name(interface->index, NULL);
}
//======================================================================================================================
OSStatus
mrcs_dns_proxy_set_nat64_prefix(const mrcs_dns_proxy_t me, const uint8_t * const prefix, const size_t prefix_bitlen)
{
OSStatus err;
nw_nat64_prefix_length_t length;
switch (prefix_bitlen) {
#define _nat64_prefix_bitlen_case(BITLEN) \
case BITLEN: \
length = nw_nat64_prefix_length_ ## BITLEN; \
break;
_nat64_prefix_bitlen_case(32)
_nat64_prefix_bitlen_case(40)
_nat64_prefix_bitlen_case(48)
_nat64_prefix_bitlen_case(56)
_nat64_prefix_bitlen_case(64)
_nat64_prefix_bitlen_case(96)
#undef _nat64_prefix_bitlen_case
default:
err = kSizeErr;
goto exit;
}
memset(me->nat64_prefix.data, 0, sizeof(me->nat64_prefix.data));
mdns_memcpy_bits(me->nat64_prefix.data, prefix, prefix_bitlen);
me->nat64_prefix.length = length;
me->nat64_prefix_valid = true;
err = kNoErr;
exit:
return err;
}
//======================================================================================================================
void
mrcs_dns_proxy_enable_force_aaaa_synthesis(const mrcs_dns_proxy_t me, const bool enable)
{
me->force_aaaa_synthesis = enable;
}
//======================================================================================================================
void
mrcs_dns_proxy_set_euid(const mrcs_dns_proxy_t me, const uid_t euid)
{
me->euid = euid;
}
//======================================================================================================================
bool
mrcs_dns_proxy_contains_input_interface(const mrcs_dns_proxy_t me, const uint32_t ifindex)
{
for (size_t i = 0; i < me->input_interface_count; ++i) {
if (me->input_interfaces[i].index == ifindex) {
return true;
}
}
return false;
}
//======================================================================================================================
uint32_t
mrcs_dns_proxy_get_output_interface(const mrcs_dns_proxy_t me)
{
return me->output_interface.index;
}
//======================================================================================================================
const nw_nat64_prefix_t *
mrcs_dns_proxy_get_nat64_prefix(const mrcs_dns_proxy_t me)
{
return (me->nat64_prefix_valid ? &me->nat64_prefix : NULL);
}
//======================================================================================================================
bool
mrcs_dns_proxy_forces_aaaa_synthesis(const mrcs_dns_proxy_t me)
{
return me->force_aaaa_synthesis;
}
//======================================================================================================================
uid_t
mrcs_dns_proxy_get_euid(const mrcs_dns_proxy_t me)
{
return me->euid;
}
//======================================================================================================================
// MARK: - DNS Proxy Private Methods
static OSStatus
_mrcs_dns_proxy_print_description(const mrcs_dns_proxy_t me, 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)
if (debug) {
_do_appendf("<%s: %p>: ", me->base.kind->name, me);
}
_do_appendf("input interfaces: {");
const mrcs_interface_t *interface;
const char *separator = "";
for (size_t i = 0; i < me->input_interface_count; ++i) {
interface = &me->input_interfaces[i];
_do_appendf("%s%s/%u", separator, _mrcs_string_or_empty(interface->name), interface->index);
separator = ", ";
}
interface = &me->output_interface;
_do_appendf("}, output interface: %s/%u", _mrcs_string_or_empty(interface->name), interface->index);
const nw_nat64_prefix_t * const prefix = mrcs_dns_proxy_get_nat64_prefix(me);
if (prefix) {
uint8_t ipv6_addr[16] = {0};
mdns_compile_time_check_local(sizeof(prefix->data) <= sizeof(ipv6_addr));
memcpy(ipv6_addr, prefix->data, sizeof(prefix->data));
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);
const int byte_len = _mrcs_nat64_prefix_to_byte_length(prefix);
const int bitlen = (byte_len >= 0) ? (byte_len * 8) : -1;
_do_appendf(", nat64 prefix: %s/%d", addr_str, bitlen);
}
_do_appendf(", forces AAAA synthesis: %s", mrcs_dns_proxy_forces_aaaa_synthesis(me) ? "yes" : "no");
#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;
}
//======================================================================================================================
static char *
_mrcs_dns_proxy_copy_description(const mrcs_dns_proxy_t me, const bool debug, const bool privacy)
{
char *description = NULL;
char buf[128];
size_t full_len;
OSStatus err = _mrcs_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 = _mrcs_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
_mrcs_dns_proxy_finalize(const mrcs_dns_proxy_t me)
{
for (size_t i = 0; i < me->input_interface_count; ++i) {
ForgetMem(&me->input_interfaces[i].name);
}
ForgetMem(&me->input_interfaces);
ForgetMem(&me->output_interface.name);
}
//======================================================================================================================
static bool
_mrcs_dns_proxy_has_common_input_interface(const mrcs_dns_proxy_t me, const mrcs_dns_proxy_t other)
{
for (size_t i = 0; i < me->input_interface_count; ++i) {
if (mrcs_dns_proxy_contains_input_interface(other, me->input_interfaces[i].index)) {
return true;
}
}
return false;
}
//======================================================================================================================
static bool
_mrcs_dns_proxy_conflicts_with_other_proxy(const mrcs_dns_proxy_t me, const mrcs_dns_proxy_t other)
{
if (_mrcs_dns_proxy_has_common_input_interface(me, other)) {
if (me->output_interface.index != other->output_interface.index) {
return true;
}
const nw_nat64_prefix_t * const prefix = mrcs_dns_proxy_get_nat64_prefix(me);
const nw_nat64_prefix_t * const other_prefix = mrcs_dns_proxy_get_nat64_prefix(other);
if (!_mrcs_nat64_prefix_equal_null_safe(prefix, other_prefix)) {
return true;
}
if (prefix && other_prefix) {
if (!!me->force_aaaa_synthesis != !!other->force_aaaa_synthesis) {
return true;
}
}
}
return false;
}
//======================================================================================================================
// MARK: - DNS Proxy Manager Public Methods
mrcs_dns_proxy_manager_t
mrcs_dns_proxy_manager_create(OSStatus * const out_error)
{
OSStatus err;
mrcs_dns_proxy_manager_t manager = NULL;
mrcs_dns_proxy_manager_t obj = _mrcs_dns_proxy_manager_new();
require_action_quiet(obj, exit, err = kNoMemoryErr);
obj->proxies = CFArrayCreateMutable(kCFAllocatorDefault, 0, &mrcs_cfarray_callbacks);
require_action_quiet(obj->proxies, exit, err = kNoResourcesErr);
manager = obj;
obj = NULL;
err = kNoErr;
exit:
if (out_error) {
*out_error = err;
}
mrcs_forget(&obj);
return manager;
}
//======================================================================================================================
OSStatus
mrcs_dns_proxy_manager_add_proxy(const mrcs_dns_proxy_manager_t me, const mrcs_dns_proxy_t proxy)
{
OSStatus err;
const bool conflicts = _mrcs_dns_proxy_manager_conflicts_with_proxy(me, proxy);
require_action_quiet(!conflicts, exit, err = kCollisionErr);
CFArrayAppendValue(me->proxies, proxy);
err = kNoErr;
exit:
return err;
}
//======================================================================================================================
OSStatus
mrcs_dns_proxy_manager_remove_proxy(const mrcs_dns_proxy_manager_t me, const mrcs_dns_proxy_t proxy)
{
CFIndex i;
const CFIndex n = CFArrayGetCount(me->proxies);
for (i = 0; i < n; ++i) {
if (CFArrayGetValueAtIndex(me->proxies, i) == proxy) {
break;
}
}
OSStatus err;
require_action_quiet(i < n, exit, err = kNotFoundErr);
CFArrayRemoveValueAtIndex(me->proxies, i);
err = kNoErr;
exit:
return err;
}
//======================================================================================================================
size_t
mrcs_dns_proxy_manager_get_count(const mrcs_dns_proxy_manager_t me)
{
return (size_t)CFArrayGetCount(me->proxies);
}
//======================================================================================================================
mrcs_dns_proxy_t
mrcs_dns_proxy_manager_get_proxy_by_input_interface(const mrcs_dns_proxy_manager_t me, const uint32_t ifindex)
{
__block mrcs_dns_proxy_t result = NULL;
mrcs_cfarray_enumerate(me->proxies,
^ bool (const mrcs_dns_proxy_t proxy)
{
if (mrcs_dns_proxy_contains_input_interface(proxy, ifindex)) {
result = proxy;
}
const bool proceed = (result == NULL);
return proceed;
});
return result;
}
//======================================================================================================================
// MARK: - DNS Proxy Manager Private Methods
static OSStatus
_mrcs_dns_proxy_manager_print_description(const mrcs_dns_proxy_manager_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);
}
_do_appendf("{");
const CFIndex n = CFArrayGetCount(me->proxies);
for (CFIndex i = 0; i < n; ++i) {
_do_appendf("%s\n\t", (i == 0) ? "" : ",");
size_t wrote_len, desc_len;
const mrcs_dns_proxy_t proxy = (mrcs_dns_proxy_t)CFArrayGetValueAtIndex(me->proxies, i);
err = _mrcs_dns_proxy_print_description(proxy, false, privacy, dst, (size_t)(lim - dst), &wrote_len, &desc_len);
require_noerr_quiet(err, exit);
dst += wrote_len;
full_len += desc_len;
}
_do_appendf("\n}");
#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;
}
//======================================================================================================================
static char *
_mrcs_dns_proxy_manager_copy_description(const mrcs_dns_proxy_manager_t me, const bool debug, const bool privacy)
{
char *description = NULL;
char buf[512];
size_t full_len;
OSStatus err = _mrcs_dns_proxy_manager_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 = _mrcs_dns_proxy_manager_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
_mrcs_dns_proxy_manager_finalize(const mrcs_dns_proxy_manager_t me)
{
CFForget(&me->proxies);
}
//======================================================================================================================
static bool
_mrcs_dns_proxy_manager_conflicts_with_proxy(const mrcs_dns_proxy_manager_t me, const mrcs_dns_proxy_t other)
{
const bool conflict_free = mrcs_cfarray_enumerate(me->proxies,
^ bool (const mrcs_dns_proxy_t proxy)
{
const bool proceed = !_mrcs_dns_proxy_conflicts_with_other_proxy(proxy, other);
return proceed;
});
return !conflict_free;
}
//======================================================================================================================
// MARK: - Helpers
static bool
_mrcs_nat64_prefix_equal_null_safe(const nw_nat64_prefix_t * const p1, const nw_nat64_prefix_t * const p2)
{
if (p1 == p2) {
return true;
}
if (!p1 || !p2) {
return false;
}
if (p1->length != p2->length) {
return false;
}
const int byte_len = _mrcs_nat64_prefix_to_byte_length(p1);
if (byte_len < 0) {
return false;
}
return (memcmp(p1->data, p2->data, (size_t)byte_len) == 0);
}
//======================================================================================================================
static int
_mrcs_nat64_prefix_to_byte_length(const nw_nat64_prefix_t * const prefix)
{
int len;
switch (prefix->length) {
#define _nat64_prefix_length_case(BITLEN) \
case nw_nat64_prefix_length_ ## BITLEN: { \
mdns_compile_time_check_local(((BITLEN) % 8) == 0); \
len = (BITLEN) / 8; \
break; \
}
_nat64_prefix_length_case(32)
_nat64_prefix_length_case(40)
_nat64_prefix_length_case(48)
_nat64_prefix_length_case(56)
_nat64_prefix_length_case(64)
_nat64_prefix_length_case(96)
#undef _nat64_prefix_length_case
MDNS_COVERED_SWITCH_DEFAULT:
len = -1;
break;
}
return len;
}
//======================================================================================================================
static const char *
_mrcs_string_or_empty(const char * const string)
{
return (string ? string : "");
}