nat64-configuration.c [plain text]
#include "nat64-configuration.h"
#include <TargetConditionals.h>
#include <CoreFoundation/CoreFoundation.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCPrivate.h>
#if TEST_NAT64_CONFIGURATION
static Boolean G_set_prefixes_force_failure;
#define my_if_nametoindex if_nametoindex
#else
#include "ip_plugin.h"
#endif
#define INET6 1
#include <string.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <netinet/in.h>
#include <nw/private.h>
#include <sys/queue.h>
static dispatch_queue_t
nat64_dispatch_queue(void)
{
static dispatch_once_t once;
static dispatch_queue_t q;
dispatch_once(&once, ^{
q = dispatch_queue_create("nat64 prefix request queue", NULL);
});
return q;
}
static Boolean
_nat64_prefix_set(const char *if_name,
int32_t num_prefixes,
nw_nat64_prefix_t *prefixes)
{
struct if_nat64req req;
int ret;
int s;
memset(&req, 0, sizeof(req));
strlcpy(req.ifnat64_name, if_name, sizeof(req.ifnat64_name));
if (num_prefixes == 0) {
SC_log(LOG_NOTICE, "%s: nat64 prefix unavailable", if_name);
}
for (int32_t i = 0; i < num_prefixes; i++) {
char prefix_str[NW_NAT64_PREFIX_STR_LENGTH] = {0};
nw_nat64_write_prefix_to_string(&prefixes[i], prefix_str, sizeof(prefix_str));
SC_log(LOG_NOTICE, "%s: nat64 prefix[%d] = %s", if_name, i, prefix_str);
if (i < NAT64_MAX_NUM_PREFIXES) {
req.ifnat64_prefixes[i].prefix_len = prefixes[i].length;
memcpy(&req.ifnat64_prefixes[i].ipv6_prefix,
&prefixes[i].data,
MIN(sizeof(req.ifnat64_prefixes[i].ipv6_prefix), sizeof(prefixes[i].data))); }
}
s = socket(AF_INET, SOCK_DGRAM, 0);
if (s == -1) {
SC_log(LOG_ERR, "socket() failed: %s", strerror(errno));
return (FALSE);
}
ret = ioctl(s, SIOCSIFNAT64PREFIX, &req);
close(s);
if (ret == -1) {
if ((errno != ENOENT) || (num_prefixes != 0)) {
SC_log(LOG_ERR, "%s: ioctl(SIOCSIFNAT64PREFIX) failed: %s", if_name, strerror(errno));
}
return (FALSE);
}
SC_log(LOG_NOTICE, "%s: nat64 prefix%s updated", if_name, (num_prefixes != 1) ? "es" : "");
return (TRUE);
}
static void
_nat64_prefix_post(CFStringRef interface,
int32_t num_prefixes,
nw_nat64_prefix_t *prefixes,
CFAbsoluteTime start_time)
{
#if TEST_NAT64_CONFIGURATION
#pragma unused(interface)
#pragma unused(num_prefixes)
#pragma unused(prefixes)
#pragma unused(start_time)
return;
#else
CFStringRef key;
key = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
kSCDynamicStoreDomainState,
interface,
kSCEntNetNAT64);
if (num_prefixes >= 0) {
CFDateRef date;
CFMutableDictionaryRef plat_dict;
plat_dict = CFDictionaryCreateMutable(NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (num_prefixes > 0) {
CFMutableArrayRef prefix_array;
prefix_array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
for (int32_t i = 0; i < num_prefixes; i++) {
char prefix_str[NW_NAT64_PREFIX_STR_LENGTH] = {0};
CFStringRef str;
nw_nat64_write_prefix_to_string(&prefixes[i], prefix_str, sizeof(prefix_str));
str = CFStringCreateWithCString(NULL, prefix_str, kCFStringEncodingASCII);
CFArrayAppendValue(prefix_array, str);
CFRelease(str);
}
CFDictionarySetValue(plat_dict, kSCPropNetNAT64PrefixList, prefix_array);
CFRelease(prefix_array);
}
date = CFDateCreate(NULL, start_time);
CFDictionarySetValue(plat_dict,
kSCPropNetNAT64PLATDiscoveryStartTime,
date);
CFRelease(date);
date = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
CFDictionarySetValue(plat_dict,
kSCPropNetNAT64PLATDiscoveryCompletionTime,
date);
CFRelease(date);
(void)SCDynamicStoreSetValue(NULL, key, plat_dict);
SC_log(LOG_NOTICE, "%@: PLAT discovery complete %@",
interface, plat_dict);
CFRelease(plat_dict);
} else {
(void)SCDynamicStoreRemoveValue(NULL, key);
}
CFRelease(key);
#endif
return;
}
static nw_nat64_prefixes_resolver_t
_nat64_resolver_create(unsigned int if_index)
{
nw_interface_t interface;
nw_parameters_t params;
nw_nat64_prefixes_resolver_t resolver;
params = nw_parameters_create();
interface = nw_interface_create_with_index(if_index);
if (interface == NULL) {
SC_log(LOG_NOTICE,
"nw_interface_create_with_index(%u) failed",
if_index);
return (NULL);
}
nw_parameters_require_interface(params, interface);
nw_parameters_set_required_address_family(params, AF_INET6);
nw_release(interface);
resolver = nw_nat64_prefixes_resolver_create(params);
nw_release(params);
return (resolver);
}
struct NAT64PrefixRequest;
typedef struct NAT64PrefixRequest NAT64PrefixRequest, * NAT64PrefixRequestRef;
#define NAT64PrefixRequest_LIST_ENTRY LIST_ENTRY(NAT64PrefixRequest)
#define NAT64PrefixRequest_LIST_HEAD LIST_HEAD(NAT64PrefixRequestHead, \
NAT64PrefixRequest)
static NAT64PrefixRequest_LIST_HEAD S_request_head;
static struct NAT64PrefixRequestHead * S_request_head_p = &S_request_head;
typedef CF_ENUM(uint16_t, RequestFlags) {
kRequestFlagsNone = 0x0000,
kRequestFlagsValid = 0x0001,
};
struct NAT64PrefixRequest {
NAT64PrefixRequest_LIST_ENTRY link;
nw_nat64_prefixes_resolver_t resolver;
const char * if_name;
CFStringRef if_name_cf;
unsigned int if_index;
unsigned int retain_count;
RequestFlags flags;
};
static Boolean
NAT64PrefixRequestFlagsIsSet(NAT64PrefixRequestRef request, RequestFlags flags)
{
return ((request->flags & flags) != 0);
}
static void
NAT64PrefixRequestFlagsSet(NAT64PrefixRequestRef request, RequestFlags flags)
{
request->flags |= flags;
}
static void
NAT64PrefixRequestFlagsClear(NAT64PrefixRequestRef request, RequestFlags flags)
{
request->flags &= ~flags;
}
static NAT64PrefixRequestRef
NAT64PrefixRequestFindInterface(CFStringRef if_name_cf)
{
NAT64PrefixRequestRef scan;
LIST_FOREACH(scan, S_request_head_p, link) {
if (CFEqual(if_name_cf, scan->if_name_cf)) {
return (scan);
}
}
return (NULL);
}
static void
NAT64PrefixRequestRetain(NAT64PrefixRequestRef request)
{
request->retain_count++;
SC_log(LOG_DEBUG, "%s: %s %p %u",
request->if_name, __FUNCTION__,
request, request->retain_count);
return;
}
static NAT64PrefixRequestRef
NAT64PrefixRequestCreate(CFStringRef if_name_cf)
{
unsigned int if_index;
char * if_name;
NAT64PrefixRequestRef request;
if_name = _SC_cfstring_to_cstring(if_name_cf, NULL, 0,
kCFStringEncodingASCII);
if (if_name == NULL) {
SC_log(LOG_ERR,
"%@: could not convert interface name",
if_name_cf);
return (NULL);
}
if_index = my_if_nametoindex(if_name);
if (if_index == 0) {
SC_log(LOG_NOTICE,
"%s: interface does not exist", if_name);
CFAllocatorDeallocate(NULL, if_name);
return (NULL);
}
request = malloc(sizeof(*request));
SC_log(LOG_DEBUG, "%@: %s %p", if_name_cf, __FUNCTION__, request);
bzero(request, sizeof(*request));
request->if_name_cf = CFRetain(if_name_cf);
request->if_name = if_name;
request->if_index = if_index;
LIST_INSERT_HEAD(S_request_head_p, request, link);
NAT64PrefixRequestFlagsSet(request, kRequestFlagsValid);
NAT64PrefixRequestRetain(request);
return (request);
}
static void
NAT64PrefixRequestStopResolver(NAT64PrefixRequestRef request)
{
if (request->resolver != NULL) {
SC_log(LOG_DEBUG, "%s: %s",
request->if_name, __FUNCTION__);
nw_nat64_prefixes_resolver_cancel(request->resolver);
nw_release(request->resolver);
request->resolver = NULL;
}
return;
}
static void
NAT64PrefixRequestInvalidate(NAT64PrefixRequestRef request)
{
SC_log(LOG_DEBUG, "%s: %s", request->if_name, __FUNCTION__);
NAT64PrefixRequestStopResolver(request);
if (NAT64PrefixRequestFlagsIsSet(request, kRequestFlagsValid)) {
NAT64PrefixRequestFlagsClear(request, kRequestFlagsValid);
LIST_REMOVE(request, link);
}
return;
}
static void
NAT64PrefixRequestRelease(NAT64PrefixRequestRef request)
{
if (request->retain_count == 0) {
SC_log(LOG_ERR, "%s: retain count is zero %p",
__FUNCTION__, request);
return;
}
request->retain_count--;
SC_log(LOG_DEBUG,
"%s: %s %p %u",
request->if_name, __FUNCTION__, request, request->retain_count);
if (request->retain_count != 0) {
return;
}
NAT64PrefixRequestInvalidate(request);
SC_log(LOG_DEBUG, "%s %s: deallocate %p",
request->if_name, __FUNCTION__, request);
if (request->if_name_cf != NULL) {
CFRelease(request->if_name_cf);
request->if_name_cf = NULL;
}
if (request->if_name != NULL) {
CFAllocatorDeallocate(NULL, (void *)request->if_name);
request->if_name = NULL;
}
free(request);
return;
}
static void
NAT64PrefixRequestStart(NAT64PrefixRequestRef request)
{
dispatch_block_t cancel_handler;
nw_nat64_copy_prefixes_block_t handler;
nw_nat64_prefixes_resolver_t resolver;
CFAbsoluteTime start_time;
SC_log(LOG_INFO, "%s: %s", request->if_name, __FUNCTION__);
if (request->resolver != NULL) {
SC_log(LOG_DEBUG, "%s %s: resolver is already active",
request->if_name, __FUNCTION__);
return;
}
resolver = _nat64_resolver_create(request->if_index);
if (resolver == NULL) {
return;
}
NAT64PrefixRequestRetain(request);
cancel_handler = ^{
SC_log(LOG_DEBUG, "%s: NAT64 resolver cancelled",
request->if_name);
NAT64PrefixRequestRelease(request);
return;
};
start_time = CFAbsoluteTimeGetCurrent();
handler = ^(int32_t num_prefixes, nw_nat64_prefix_t *prefixes) {
Boolean remove_resolver = FALSE;
if (!NAT64PrefixRequestFlagsIsSet(request,
kRequestFlagsValid)) {
SC_log(LOG_INFO, "%s: NAT64 request is stale %p",
request->if_name, request);
return;
}
if (prefixes != NULL) {
_nat64_prefix_set(request->if_name,
num_prefixes, prefixes);
remove_resolver = TRUE;
} else {
SC_log(LOG_ERR, "%s: NAT64 no prefixes",
request->if_name);
}
_nat64_prefix_post(request->if_name_cf,
num_prefixes, prefixes, start_time);
#if TEST_NAT64_CONFIGURATION
if (G_set_prefixes_force_failure) {
remove_resolver = TRUE;
}
#endif
if (remove_resolver) {
NAT64PrefixRequestInvalidate(request);
NAT64PrefixRequestRelease(request);
return;
}
};
nw_nat64_prefixes_resolver_set_cancel_handler(resolver, cancel_handler);
nw_nat64_prefixes_resolver_set_update_handler(resolver,
nat64_dispatch_queue(),
handler);
nw_nat64_prefixes_resolver_start(resolver);
request->resolver = resolver;
return;
}
static void
_nat64_process_prefix_request(const void *value, void *context)
{
#pragma unused(context)
CFStringRef interface = (CFStringRef)value;
NAT64PrefixRequestRef request;
request = NAT64PrefixRequestFindInterface(interface);
if (request != NULL) {
return;
}
request = NAT64PrefixRequestCreate(interface);
if (request != NULL) {
NAT64PrefixRequestStart(request);
}
return;
}
static void
_nat64_process_prefix_update(const void *value, void *context)
{
#pragma unused(context)
CFStringRef interface = (CFStringRef)value;
NAT64PrefixRequestRef request;
request = NAT64PrefixRequestFindInterface(interface);
if (request == NULL) {
SC_log(LOG_DEBUG, "%@ %s: no existing request",
interface, __FUNCTION__);
return;
}
SC_log(LOG_INFO, "%@: %s", interface, __FUNCTION__);
NAT64PrefixRequestInvalidate(request);
NAT64PrefixRequestRelease(request);
request = NAT64PrefixRequestCreate(interface);
if (request != NULL) {
NAT64PrefixRequestStart(request);
}
return;
}
static void
_nat64_process_cancel_request(const void * value, void * context)
{
#pragma unused(context)
CFStringRef interface = (CFStringRef)value;
NAT64PrefixRequestRef request;
request = NAT64PrefixRequestFindInterface(interface);
if (request == NULL) {
SC_log(LOG_DEBUG, "%@ %s: no active NAT64 request",
interface, __FUNCTION__);
return;
}
SC_log(LOG_DEBUG, "%s %s: removing NAT64 request",
request->if_name, __FUNCTION__);
_nat64_prefix_set(request->if_name, 0, NULL);
NAT64PrefixRequestInvalidate(request);
NAT64PrefixRequestRelease(request);
return;
}
#pragma mark -
#pragma mark NAT64 prefix functions (for IPMonitor)
__private_extern__
Boolean
is_nat64_prefix_request(CFStringRef change, CFStringRef *interface)
{
CFArrayRef components;
static CFStringRef prefix = NULL;
Boolean yn = FALSE;
static dispatch_once_t once;
dispatch_once(&once, ^{
prefix = SCDynamicStoreKeyCreateNetworkInterface(NULL, kSCDynamicStoreDomainState);
});
*interface = NULL;
if (!CFStringHasPrefix(change, prefix) ||
!CFStringHasSuffix(change, kSCEntNetNAT64PrefixRequest)) {
return FALSE;
}
components = CFStringCreateArrayBySeparatingStrings(NULL, change, CFSTR("/"));
if (CFArrayGetCount(components) == 5) {
*interface = CFArrayGetValueAtIndex(components, 3);
CFRetain(*interface);
yn = TRUE;
}
CFRelease(components);
return yn;
}
__private_extern__ void
nat64_prefix_request_add_pattern(CFMutableArrayRef patterns)
{
CFStringRef pattern;
pattern = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
kSCDynamicStoreDomainState,
kSCCompAnyRegex,
kSCEntNetNAT64PrefixRequest);
CFArrayAppendValue(patterns, pattern);
CFRelease(pattern);
return;
}
static void
nat64_configuration_update_locked(CFSetRef requests, CFSetRef updates,
CFSetRef cancellations)
{
if (cancellations != NULL) {
CFSetApplyFunction(cancellations,
_nat64_process_cancel_request,
NULL);
}
if (updates != NULL) {
CFSetApplyFunction(updates, _nat64_process_prefix_update, NULL);
}
if (requests != NULL) {
CFSetApplyFunction(requests, _nat64_process_prefix_request,
NULL);
}
return;
}
__private_extern__
void
nat64_configuration_update(CFSetRef requests, CFSetRef updates,
CFSetRef cancellations)
{
dispatch_block_t update_block;
if (requests != NULL) {
CFRetain(requests);
}
if (updates != NULL) {
CFRetain(updates);
}
if (cancellations != NULL) {
CFRetain(cancellations);
}
update_block = ^{
SC_log(LOG_DEBUG,
"NAT64 requests %@ updates %@ cancellations %@",
requests, updates, cancellations);
nat64_configuration_update_locked(requests, updates,
cancellations);
if (requests != NULL) {
CFRelease(requests);
}
if (updates != NULL) {
CFRelease(updates);
}
if (cancellations != NULL) {
CFRelease(cancellations);
}
};
dispatch_async(nat64_dispatch_queue(), update_block);
return;
}
#if TEST_NAT64_CONFIGURATION
int
main(int argc, char * argv[])
{
CFStringRef if_name_cf;
CFMutableSetRef set;
set = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
for (int i = 1; i < argc; i++) {
if_name_cf = CFStringCreateWithCString(NULL,
argv[i],
kCFStringEncodingASCII);
CFSetAddValue(set, if_name_cf);
CFRelease(if_name_cf);
}
if (CFSetGetCount(set) == 0) {
fprintf(stderr, "nothing to do\n");
exit(0);
}
SC_log(LOG_NOTICE, "Starting %@", set);
nat64_configuration_update(set, NULL, NULL);
sleep(2);
SC_log(LOG_NOTICE, "Starting 2 %@", set);
nat64_configuration_update(set, NULL, NULL);
sleep(2);
SC_log(LOG_NOTICE, "Updating");
nat64_configuration_update(NULL, set, NULL);
sleep(2);
SC_log(LOG_NOTICE, "Cancelling");
nat64_configuration_update(NULL, NULL, set);
sleep(2);
G_set_prefixes_force_failure = TRUE;
SC_log(LOG_NOTICE, "Starting (with forced failure) %@", set);
nat64_configuration_update(set, NULL, NULL);
sleep(2);
SC_log(LOG_NOTICE, "Starting (with forced failure 2) %@", set);
nat64_configuration_update(set, NULL, NULL);
dispatch_main();
exit(0);
return (0);
}
#endif