#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCValidation.h>
#include <SystemConfiguration/SCPrivate.h>
#include "configthreads_common.h"
#include "globals.h"
#include "config_method.h"
#include "ip6config_utils.h"
static interface_list_t * S_interfaces = NULL;
static CFStringRef S_state_interface_prefix = NULL;
static CFStringRef S_setup_service_prefix = NULL;
int G_verbose = FALSE;
CFBundleRef G_bundle = NULL;
SCDynamicStoreRef G_scd_session = NULL;
int G_debug = TRUE;
const struct in6_addr G_ip6_zeroes = IN6ADDR_ANY_INIT;
IFStateList_t G_ifstate_list;
static boolean_t
update_interface_list()
{
interface_list_t * new_interfaces = NULL;
new_interfaces = ifl_init();
if (new_interfaces == NULL) {
my_log(LOG_ERR, "ip6config: ifl_init failed");
return (FALSE);
}
if (S_interfaces) {
ifl_free(&S_interfaces);
}
S_interfaces = new_interfaces;
return (TRUE);
}
static void
check_for_detached_interfaces()
{
int count = dynarray_count(&G_ifstate_list);
char * * names = NULL;
int names_count = 0;
int i;
if (count == 0) {
return;
}
names = (char * *)malloc(sizeof(char *) * count);
if (names == NULL) {
return;
}
for (i = 0; i < count; i++) {
IFState_t * ifstate = dynarray_element(&G_ifstate_list, i);
if (ifl_find_name(S_interfaces, if_name(ifstate->if_p)) == NULL) {
names[names_count++] = if_name(ifstate->if_p);
}
}
for (i = 0; i < names_count; i++) {
IFStateList_ifstate_free(&G_ifstate_list, names[i]);
}
free(names);
return;
}
static CFStringRef
parse_component(CFStringRef key, CFStringRef prefix)
{
CFMutableStringRef comp;
CFRange range;
if (CFStringHasPrefix(key, prefix) == FALSE) {
return (NULL);
}
comp = CFStringCreateMutableCopy(NULL, 0, key);
if (comp == NULL) {
return (NULL);
}
CFStringDelete(comp, CFRangeMake(0, CFStringGetLength(prefix)));
range = CFStringFind(comp, CFSTR("/"), 0);
if (range.location == kCFNotFound) {
CFRelease(comp);
return (NULL);
}
range.length = CFStringGetLength(comp) - range.location;
CFStringDelete(comp, range);
return (comp);
}
static void
handle_configuration_changed(SCDynamicStoreRef session, CFArrayRef all_ipv6)
{
int i, n;
n = ifl_count(S_interfaces);
for (i = 0; i < n; i++) {
ServiceConfig_t * config;
int count = 0;
IFState_t * ifstate;
ServiceConfig_t * if_services = NULL;
interface_t * if_p = ifl_at_index(S_interfaces, i);
if (strcmp(if_name(if_p), "lo0") == 0) {
my_log(LOG_DEBUG, "HANDLE_configuration_changed: skipping loopback");
continue;
}
if_services = service_config_list_init(session, all_ipv6, if_name(if_p), &count);
if (if_services == NULL) {
ifstate = IFStateList_ifstate_with_name(&G_ifstate_list,
if_name(if_p), NULL);
if (ifstate != NULL) {
IFState_services_free(ifstate);
}
continue;
}
service_free_inactive_services(if_name(if_p), if_services, count);
ifstate = IFStateList_ifstate_create(&G_ifstate_list, if_p);
if (ifstate) {
int k;
for (k = 0, config = if_services; k < count; k++, config++) {
(void)service_set_service(ifstate, config);
}
}
service_config_list_free(&if_services, count);
}
return;
}
static CFArrayRef
entity_all(SCDynamicStoreRef session)
{
CFMutableArrayRef all_services = NULL;
int count;
int id_count;
CFMutableArrayRef get_patterns = NULL;
int i;
CFStringRef key = NULL;
void * * keys = NULL;
CFMutableArrayRef service_IDs = NULL;
CFDictionaryRef values = NULL;
get_patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
service_IDs = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
all_services = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
if (get_patterns == NULL || service_IDs == NULL || all_services == NULL) {
goto done;
}
key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCCompAnyRegex,
kSCEntNetIPv6);
if (key == NULL) {
goto done;
}
CFArrayAppendValue(get_patterns, key);
my_CFRelease(&key);
key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCCompAnyRegex,
kSCEntNetInterface);
if (key == NULL) {
goto done;
}
CFArrayAppendValue(get_patterns, key);
my_CFRelease(&key);
key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCCompAnyRegex,
kSCEntNet6to4);
if (key == NULL) {
goto done;
}
CFArrayAppendValue(get_patterns, key);
my_CFRelease(&key);
values = SCDynamicStoreCopyMultiple(session, NULL, get_patterns);
if (values == NULL) {
goto done;
}
count = CFDictionaryGetCount(values);
if (count == 0) {
goto done;
}
keys = (void * *)malloc(sizeof(void *) * count);
if (keys == NULL) {
goto done;
}
CFDictionaryGetKeysAndValues(values, (const void * *)keys, NULL);
for (i = 0; i < count; i++) {
CFStringRef serviceID;
serviceID = parse_component(keys[i], S_setup_service_prefix);
if (serviceID == NULL) {
continue;
}
my_CFArrayAppendUniqueValue(service_IDs, serviceID);
my_CFRelease(&serviceID);
}
free(keys);
keys = NULL;
id_count = CFArrayGetCount(service_IDs);
for (i = 0; i < id_count; i++) {
CFStringRef key = NULL;
CFDictionaryRef if_dict;
CFStringRef ifn_cf;
CFDictionaryRef ipv6_dict;
CFMutableDictionaryRef service_dict = NULL;
CFStringRef serviceID;
CFStringRef type = NULL;
CFDictionaryRef stf_dict;
CFStringRef relay = NULL;
serviceID = CFArrayGetValueAtIndex(service_IDs, i);
key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
serviceID,
kSCEntNetInterface);
if (key == NULL) {
goto loop_done;
}
if_dict = CFDictionaryGetValue(values, key);
my_CFRelease(&key);
if_dict = isA_CFDictionary(if_dict);
if (if_dict == NULL) {
goto loop_done;
}
type = CFDictionaryGetValue(if_dict, kSCPropNetInterfaceType);
if (type == NULL) {
goto loop_done;
}
if (CFEqual(type, kSCValNetInterfaceTypeEthernet) == FALSE
&& CFEqual(type, kSCValNetInterfaceTypeFireWire) == FALSE
&& CFEqual(type, kSCValNetInterfaceType6to4) == FALSE) {
goto loop_done;
}
ifn_cf = CFDictionaryGetValue(if_dict, kSCPropNetInterfaceDeviceName);
if (ifn_cf == NULL) {
goto loop_done;
}
key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
serviceID,
kSCEntNetIPv6);
if (key == NULL) {
goto loop_done;
}
ipv6_dict = CFDictionaryGetValue(values, key);
my_CFRelease(&key);
ipv6_dict = isA_CFDictionary(ipv6_dict);
if (ipv6_dict == NULL) {
goto loop_done;
}
service_dict = CFDictionaryCreateMutableCopy(NULL, 0, ipv6_dict);
if (service_dict == NULL) {
goto loop_done;
}
key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
serviceID,
kSCEntNet6to4);
if (key != NULL) {
stf_dict = CFDictionaryGetValue(values, key);
my_CFRelease(&key);
stf_dict = isA_CFDictionary(stf_dict);
if (stf_dict != NULL) {
relay = CFDictionaryGetValue(stf_dict, kSCPropNet6to4Relay);
if (relay == NULL) {
my_log(LOG_DEBUG, "entity_all: error getting 6to4 relay");
}
else {
CFDictionarySetValue(service_dict, kSCPropNet6to4Relay, relay);
}
}
}
CFDictionarySetValue(service_dict, kSCPropNetInterfaceDeviceName, ifn_cf);
CFDictionarySetValue(service_dict, PROP_SERVICEID, serviceID);
CFArrayAppendValue(all_services, service_dict);
loop_done:
my_CFRelease(&service_dict);
}
SCLog(G_verbose, LOG_INFO, CFSTR("ALL_SERVICES: %@ (%d)"), all_services,
CFArrayGetCount(all_services));
done:
my_CFRelease(&values);
my_CFRelease(&get_patterns);
my_CFRelease(&service_IDs);
if (all_services == NULL || CFArrayGetCount(all_services) == 0) {
my_CFRelease(&all_services);
}
return (all_services);
}
static void
configure_from_cache(SCDynamicStoreRef session)
{
CFArrayRef all_ipv6 = NULL;
all_ipv6 = entity_all(session);
handle_configuration_changed(session, all_ipv6);
my_CFRelease(&all_ipv6);
return;
}
static void
link_key_changed(SCDynamicStoreRef session, CFStringRef cache_key)
{
CFDictionaryRef dict = NULL;
interface_t * if_p = NULL;
CFStringRef ifn_cf = NULL;
char ifn[IFNAMSIZ + 1];
IFState_t * ifstate;
int j;
int service_count;
link_status_t link;
CFBooleanRef link_val = NULL;
ifn_cf = parse_component(cache_key, S_state_interface_prefix);
if (ifn_cf == NULL) {
return;
}
cfstring_to_cstring(ifn_cf, ifn, sizeof(ifn));
dict = my_SCDynamicStoreCopyValue(session, cache_key);
if (dict != NULL) {
if (CFDictionaryContainsKey(dict, kSCPropNetLinkDetaching)) {
ifstate = IFStateList_ifstate_with_name(&G_ifstate_list,
ifn, NULL);
if (ifstate != NULL) {
IFState_services_free(ifstate);
}
goto done;
}
link_val = CFDictionaryGetValue(dict, kSCPropNetLinkActive);
link_val = isA_CFBoolean(link_val);
}
if (link_val == NULL) {
link.valid = link.active = FALSE;
}
else {
link.valid = TRUE;
link.active = CFEqual(link_val, kCFBooleanTrue);
}
if (link.valid) {
if_p = ifl_find_name(S_interfaces, ifn);
if (if_p != NULL) {
if_link_update(if_p);
}
else {
if_p = NULL;
}
}
ifstate = IFStateList_ifstate_with_name(&G_ifstate_list, ifn, NULL);
if (ifstate == NULL) {
goto done;
}
ifstate->link = link;
if (if_p != NULL) {
if_link_copy(ifstate->if_p, if_p);
}
if (link.valid == FALSE) {
my_log(LOG_DEBUG, "link_key_changed: %s link is unknown", ifn);
}
else {
my_log(LOG_DEBUG, "%s link is %s",
ifn, link.active ? "up" : "down");
}
if (link.active == TRUE) {
if (ifstate->llocal_service != NULL) {
config_method_media(ifstate->llocal_service);
}
}
service_count = dynarray_count(&ifstate->services);
for (j = 0; j < service_count; j++) {
Service_t * service_p = dynarray_element(&ifstate->services, j);
config_method_media(service_p);
}
if (link.active == FALSE) {
if (ifstate->llocal_service != NULL) {
config_method_media(ifstate->llocal_service);
}
}
done:
my_CFRelease(&dict);
my_CFRelease(&ifn_cf);
return;
}
static void
interface_ipv6_changed(SCDynamicStoreRef session, CFStringRef cache_key)
{
CFDictionaryRef dict = NULL;
CFStringRef ifn_cf = NULL;
char ifn[IFNAMSIZ + 1];
IFState_t * ifstate;
ip6_addrinfo_list_t ip6_addrs;
int j, service_count;
ifn_cf = parse_component(cache_key, S_state_interface_prefix);
if (ifn_cf == NULL) {
return;
}
ip6_addrs.addr_list = NULL;
cfstring_to_cstring(ifn_cf, ifn, sizeof(ifn));
ifstate = IFStateList_ifstate_with_name(&G_ifstate_list, ifn, NULL);
if (ifstate == NULL) {
goto done;
}
dict = my_SCDynamicStoreCopyValue(session, cache_key);
if (dict == NULL) {
goto done;
}
if (ip6config_address_data_from_state(dict, &ip6_addrs) != 0) {
goto done;
}
service_count = dynarray_count(&ifstate->services);
for (j = 0; j < service_count; j++) {
Service_t * service_p = dynarray_element(&ifstate->services, j);
(void)config_method_state_change(service_p, &ip6_addrs);
}
if (ifstate->llocal_service != NULL) {
(void)config_method_state_change(ifstate->llocal_service, &ip6_addrs);
}
done:
my_CFRelease(&dict);
my_CFRelease(&ifn_cf);
if (ip6_addrs.addr_list) {
free(ip6_addrs.addr_list);
}
return;
}
static void
ipv4_primary_service_changed(SCDynamicStoreRef session, CFStringRef cache_key)
{
IFState_t * ifstate;
ip6config_method_t method;
ip6config_method_data_t method_data = { 0 };
int i, service_count;
ifstate = IFStateList_ifstate_with_name(&G_ifstate_list, "stf0", NULL);
if (ifstate == NULL) {
goto done;
}
method = ip6config_method_6to4_e;
ip6config_get_6to4_address_data(session, &method_data);
service_count = dynarray_count(&ifstate->services);
for (i = 0; i < service_count; i++) {
Service_t * service_p = dynarray_element(&ifstate->services, i);
(void)config_method_ipv4_primary_change(service_p, method, &method_data);
}
done:
if (method_data.stf_data.ip4_addrs_list) {
free(method_data.stf_data.ip4_addrs_list);
}
return;
}
static void
handle_change(SCDynamicStoreRef session, CFArrayRef changes, void * arg)
{
boolean_t config_changed = FALSE;
CFIndex count;
CFIndex i;
boolean_t iflist_changed = FALSE;
count = CFArrayGetCount(changes);
if (count == 0) {
goto done;
}
SCLog(G_verbose, LOG_INFO, CFSTR("Changes: %@ (%d)"), changes,
count);
for (i = 0; i < count; i++) {
CFStringRef cache_key = CFArrayGetValueAtIndex(changes, i);
if (CFStringHasPrefix(cache_key, kSCDynamicStoreDomainSetup)) {
config_changed = TRUE;
}
else if (CFStringHasSuffix(cache_key, kSCCompInterface)) {
iflist_changed = TRUE;
}
}
if (iflist_changed) {
if (update_interface_list()) {
config_changed = TRUE;
check_for_detached_interfaces();
}
}
if (config_changed) {
configure_from_cache(session);
}
for (i = 0; i < count; i++) {
CFStringRef cache_key = CFArrayGetValueAtIndex(changes, i);
if (CFStringHasSuffix(cache_key, kSCEntNetLink)) {
link_key_changed(session, cache_key);
}
else if (CFStringHasSuffix(cache_key, kSCEntNetIPv6)) {
interface_ipv6_changed(session, cache_key);
}
else if (CFStringHasSuffix(cache_key, kSCEntNetIPv4)) {
ipv4_primary_service_changed(session, cache_key);
}
}
done:
return;
}
static void
notifier_init(SCDynamicStoreRef session)
{
CFMutableArrayRef keys = NULL;
CFStringRef key;
CFMutableArrayRef patterns = NULL;
CFStringRef pattern;
CFRunLoopSourceRef rls;
if (session == NULL) {
return;
}
keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCCompAnyRegex,
kSCEntNetIPv6);
CFArrayAppendValue(patterns, pattern);
my_CFRelease(&pattern);
pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCCompAnyRegex,
kSCEntNetInterface);
CFArrayAppendValue(patterns, pattern);
my_CFRelease(&pattern);
pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCCompAnyRegex,
kSCEntNet6to4);
CFArrayAppendValue(patterns, pattern);
my_CFRelease(&pattern);
pattern = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
kSCDynamicStoreDomainState,
kSCCompAnyRegex,
kSCEntNetIPv6);
CFArrayAppendValue(patterns, pattern);
my_CFRelease(&pattern);
pattern = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
kSCDynamicStoreDomainState,
kSCCompAnyRegex,
kSCEntNetLink);
CFArrayAppendValue(patterns, pattern);
my_CFRelease(&pattern);
key = SCDynamicStoreKeyCreateNetworkInterface(NULL,
kSCDynamicStoreDomainState);
CFArrayAppendValue(keys, key);
my_CFRelease(&key);
key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
kSCDynamicStoreDomainState,
kSCEntNetIPv4);
CFArrayAppendValue(keys, key);
my_CFRelease(&key);
SCDynamicStoreSetNotificationKeys(session, keys, patterns);
my_CFRelease(&keys);
my_CFRelease(&patterns);
rls = SCDynamicStoreCreateRunLoopSource(NULL, session, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
CFRelease(rls);
return;
}
void
load(CFBundleRef bundle, Boolean bundleVerbose)
{
G_bundle = (CFBundleRef)CFRetain(bundle);
G_verbose = bundleVerbose;
{
struct timeval start_time;
gettimeofday(&start_time, 0);
srandom(start_time.tv_usec & ~start_time.tv_sec);
}
G_scd_session = SCDynamicStoreCreate(NULL,
CFSTR("IP6Configuration"),
handle_change, NULL);
if (G_scd_session == NULL) {
G_scd_session = NULL;
my_log(LOG_ERR, "SCDynamicStoreCreate failed: %s",
SCErrorString(SCError()));
}
dynarray_init(&G_ifstate_list, IFState_free, NULL);
return;
}
void
prime()
{
CFPropertyListRef value = NULL;
if (G_scd_session == NULL) {
return;
}
S_setup_service_prefix = SCDynamicStoreKeyCreate(NULL,
CFSTR("%@/%@/%@/"),
kSCDynamicStoreDomainSetup,
kSCCompNetwork,
kSCCompService);
S_state_interface_prefix = SCDynamicStoreKeyCreate(NULL,
CFSTR("%@/%@/%@/"),
kSCDynamicStoreDomainState,
kSCCompNetwork,
kSCCompInterface);
value = SCDynamicStoreCopyValue(G_scd_session, kSCDynamicStoreDomainSetup);
if (value == NULL) {
my_log(LOG_INFO, "IP6Configuration needs PreferencesMonitor to run first");
}
my_CFRelease(&value);
notifier_init(G_scd_session);
(void)update_interface_list();
configure_from_cache(G_scd_session);
return;
}