/* * Copyright (c) 1999-2002 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 1.1 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at * http://www.apple.com/publicsource and read it before using this file. * * This Original Code and all software distributed under the License are * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * ip6config.c * - plugin that configures IPv6 on the various interfaces. */ #include #include #include #include #include #include #include #include #include #include "configthreads_common.h" #include "globals.h" #include "config_method.h" #include "ip6config_utils.h" /* Local Variables */ static interface_list_t * S_interfaces = NULL; static CFStringRef S_state_interface_prefix = NULL; static CFStringRef S_setup_service_prefix = NULL; /* Global Variables */ 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); } /* * Function: check_for_detached_interfaces * Purpose: * Remove interface state for any interface that has been removed. * Create a temporary list to store the name of each interface that * has been removed. Iterate through that list to remove individual * interface state records. This is done to avoid problems with * iterating over a list while it is modified. */ 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; } /* allocate worst case scenario in which each ifstate needs to be removed */ 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; } /* * Function: parse_component * Purpose: * Given a string 'key' and a string prefix 'prefix', * return the next component in the slash '/' separated * key. If no slash follows the prefix, return NULL. * * Examples: * 1. key = "a/b/c" prefix = "a/" * returns "b" * 2. key = "a/b/c" prefix = "a/b/" * returns NULL */ 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; } /* stop services that are no longer active */ service_free_inactive_services(if_name(if_p), if_services, count); ifstate = IFStateList_ifstate_create(&G_ifstate_list, if_p); if (ifstate) { int k; /* update each of the services that are configured */ 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; } /* populate patterns array for Setup:/Network/Service/any/{IPv6,Interface, 6to4} */ 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); /* get keys and values atomically */ values = SCDynamicStoreCopyMultiple(session, NULL, get_patterns); if (values == NULL) { goto done; } /* if there are no values, we're done */ count = CFDictionaryGetCount(values); if (count == 0) { goto done; } /* build a list of configured service ID's */ 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; /* populate all_services array with annotated IPv6 dict's */ 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) { /* we only configure ethernet, firewire and 6to4 currently */ 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); } } } /* annotate with serviceID and interface name */ 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) { /* make sure address information is up to date */ 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) { /* update address information in ifstate too */ 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) { /* do linklocal first */ 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) { /* do linklocal after all others are taken care of */ 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); } /* do linklocal after all others are taken care of */ 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)) { /* IPv6 configuration changed */ config_changed = TRUE; } else if (CFStringHasSuffix(cache_key, kSCCompInterface)) { /* list of interfaces changed */ iflist_changed = TRUE; } } /* an interface was added/removed */ if (iflist_changed) { if (update_interface_list()) { config_changed = TRUE; check_for_detached_interfaces(); } } /* configuration changed */ 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); /* notify when IPv6 config of any service changes */ pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetIPv6); CFArrayAppendValue(patterns, pattern); my_CFRelease(&pattern); /* notify when Interface service <-> interface binding changes */ pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetInterface); CFArrayAppendValue(patterns, pattern); my_CFRelease(&pattern); /* notify when the 6to4 relay changes */ pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNet6to4); CFArrayAppendValue(patterns, pattern); my_CFRelease(&pattern); /* notify when the status of any address changes */ pattern = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv6); CFArrayAppendValue(patterns, pattern); my_CFRelease(&pattern); /* notify when the link status of any interface changes */ pattern = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetLink); CFArrayAppendValue(patterns, pattern); my_CFRelease(&pattern); /* notify when list of interfaces changes */ key = SCDynamicStoreKeyCreateNetworkInterface(NULL, kSCDynamicStoreDomainState); CFArrayAppendValue(keys, key); my_CFRelease(&key); /* notify when the IPv4 primary service changes (for 6to4) */ 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; } /* begin interface initialization */ 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); /* install run-time notifiers */ notifier_init(G_scd_session); (void)update_interface_list(); /* cache should already be populated by prefs monitor */ configure_from_cache(G_scd_session); return; }