/* * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The 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, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * ip_plugin.c * - decides which interface will be made the "primary" interface, * that is, the one with the default route assigned */ /* * Modification History * * July 19, 2000 Dieter Siegmund (dieter@apple.com) * - initial revision * * November 15, 2000 Dieter Siegmund (dieter@apple.com) * - changed to use new configuration model * * March 19, 2001 Dieter Siegmund (dieter@apple.com) * - use service state instead of interface state * * July 16, 2001 Allan Nathanson (ajn@apple.com) * - update to public SystemConfiguration.framework APIs * * August 28, 2001 Dieter Siegmund (dieter@apple.com) * - specify the interface name when installing the default route * - this ensures that default traffic goes to the highest priority * service when multiple interfaces are configured to be on the same subnet * * September 16, 2002 Dieter Siegmund (dieter@apple.com) * - don't elect a link-local service to be primary unless it's the only * one that's available * * July 16, 2003 Dieter Siegmund (dieter@apple.com) * - modifications to support IPv6 * - don't elect a service to be primary if it doesn't have a default route * * July 29, 2003 Dieter Siegmund (dieter@apple.com) * - support installing a default route to a router that's not on our subnet * * March 22, 2004 Allan Nathanson (ajn@apple.com) * - create expanded DNS configuration */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for SCLog() */ #include void load_hostname(Boolean verbose); void dns_configuration_init(CFBundleRef bundle); void dns_configuration_set(CFDictionaryRef defaultResolver, CFDictionaryRef services, CFArrayRef serviceOrder); #define IP_FORMAT "%d.%d.%d.%d" #define IP_CH(ip) ((u_char *)(ip)) #define IP_LIST(ip) IP_CH(ip)[0],IP_CH(ip)[1],IP_CH(ip)[2],IP_CH(ip)[3] /* debug output on/off */ static boolean_t S_IPMonitor_debug = FALSE; /* are we netbooted? If so, don't touch the default route */ static boolean_t S_netboot = FALSE; /* notification key indicating dns configuration has been changed */ static CFStringRef S_notify_dnsinfo = NULL; /* dictionary to hold per-service state: key is the serviceID */ static CFMutableDictionaryRef S_service_state_dict = NULL; /* if set, a PPP interface overrides the primary */ static boolean_t S_ppp_override_primary = TRUE; /* the current primary serviceID's */ static CFStringRef S_primary_ipv4 = NULL; static CFStringRef S_primary_ipv6 = NULL; static CFStringRef S_primary_dns = NULL; static CFStringRef S_primary_proxies = NULL; static CFStringRef S_state_global_ipv4 = NULL; static CFStringRef S_state_global_ipv6 = NULL; static CFStringRef S_state_global_dns = NULL; static CFStringRef S_state_global_netinfo = NULL; static CFStringRef S_state_global_proxies = NULL; static CFStringRef S_state_service_prefix = NULL; static CFStringRef S_setup_global_ipv4 = NULL; static CFStringRef S_setup_global_netinfo = NULL; static CFStringRef S_setup_global_proxies = NULL; static CFStringRef S_setup_service_prefix = NULL; static struct in_addr S_router_subnet = { 0 }; static struct in_addr S_router_subnet_mask = { 0 }; static const struct in_addr S_ip_zeros = { 0 }; static const struct in6_addr S_ip6_zeros = IN6ADDR_ANY_INIT; #define kRouterNeedsLocalIP CFSTR("com.apple.IPMonitor.RouterNeedsLocalIP") #define kRouterIsDirect CFSTR("com.apple.IPMonitor.IsDirect") #define VAR_RUN_RESOLV_CONF "/var/run/resolv.conf" #define VAR_RUN_NICONFIG_LOCAL_XML "/var/run/niconfig_local.xml" #ifndef KERN_NETBOOT #define KERN_NETBOOT 40 /* int: are we netbooted? 1=yes,0=no */ #endif KERN_NETBOOT /** ** entityType*, GetEntityChanges* ** - definitions for the entity types we handle **/ #define ENTITY_TYPES_COUNT 5 enum { kEntityTypeIPv4 = 0, kEntityTypeIPv6 = 1, kEntityTypeDNS = 2, kEntityTypeNetInfo = 3, kEntityTypeProxies = 4, }; typedef uint32_t EntityType; static CFStringRef entityTypeNames[ENTITY_TYPES_COUNT]; typedef boolean_t (GetEntityChangesFunc)(CFStringRef serviceID, CFDictionaryRef state_dict, CFDictionaryRef setup_dict, CFDictionaryRef info); typedef GetEntityChangesFunc * GetEntityChangesFuncRef; static GetEntityChangesFunc get_ipv4_changes; static GetEntityChangesFunc get_ipv6_changes; static GetEntityChangesFunc get_dns_changes; static GetEntityChangesFunc get_netinfo_changes; static GetEntityChangesFunc get_proxies_changes; static void my_CFRelease(void * t); static void my_CFArrayAppendUniqueValue(CFMutableArrayRef arr, CFTypeRef new); static void my_CFArrayRemoveValue(CFMutableArrayRef arr, CFStringRef key); static GetEntityChangesFuncRef entityChangeFunc[ENTITY_TYPES_COUNT] = { get_ipv4_changes, /* 0 */ get_ipv6_changes, /* 1 */ get_dns_changes, /* 2 */ get_netinfo_changes,/* 3 */ get_proxies_changes,/* 4 */ }; /** ** keyChangeList ** - mechanism to do an atomic update of the SCDynamicStore ** when the content needs to be changed across multiple functions **/ typedef struct { CFMutableArrayRef notify; CFMutableArrayRef remove; CFMutableDictionaryRef set; } keyChangeList, * keyChangeListRef; static void keyChangeListInit(keyChangeListRef keys) { keys->notify = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); keys->remove = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); keys->set = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); return; } static void keyChangeListFree(keyChangeListRef keys) { my_CFRelease(&keys->notify); my_CFRelease(&keys->remove); my_CFRelease(&keys->set); return; } static void keyChangeListNotifyKey(keyChangeListRef keys, CFStringRef key) { my_CFArrayAppendUniqueValue(keys->notify, key); return; } static void keyChangeListRemoveValue(keyChangeListRef keys, CFStringRef key) { my_CFArrayAppendUniqueValue(keys->remove, key); CFDictionaryRemoveValue(keys->set, key); return; } static void keyChangeListSetValue(keyChangeListRef keys, CFStringRef key, CFTypeRef value) { my_CFArrayRemoveValue(keys->remove, key); CFDictionarySetValue(keys->set, key, value); return; } static void keyChangeListApplyToStore(keyChangeListRef keys, SCDynamicStoreRef session) { CFArrayRef notify = keys->notify; CFArrayRef remove = keys->remove; CFDictionaryRef set = keys->set; if (CFArrayGetCount(notify) == 0) { notify = NULL; } if (CFArrayGetCount(remove) == 0) { remove = NULL; } if (CFDictionaryGetCount(set) == 0) { set = NULL; } if (set == NULL && remove == NULL && notify == NULL) { return; } if (S_IPMonitor_debug) { if (set != NULL) { SCLog(TRUE, LOG_INFO, CFSTR("IPMonitor: Setting:\n%@\n"), set); } if (remove != NULL) { SCLog(TRUE, LOG_INFO, CFSTR("IPMonitor: Removing:\n%@\n"), remove); } if (notify != NULL) { SCLog(TRUE, LOG_INFO, CFSTR("IPMonitor: Notifying:\n%@\n"), notify); } } (void)SCDynamicStoreSetMultiple(session, set, remove, notify); return; } static boolean_t S_netboot_root() { int mib[2]; size_t len; int netboot = 0; mib[0] = CTL_KERN; mib[1] = KERN_NETBOOT; len = sizeof(netboot); sysctl(mib, 2, &netboot, &len, NULL, 0); return (netboot); } static void my_CFArrayAppendUniqueValue(CFMutableArrayRef arr, CFTypeRef new) { CFIndex n = CFArrayGetCount(arr); if (CFArrayContainsValue(arr, CFRangeMake(0, n), new)) { return; } CFArrayAppendValue(arr, new); return; } static void my_CFArrayRemoveValue(CFMutableArrayRef arr, CFStringRef key) { CFIndex i; i = CFArrayGetFirstIndexOfValue(arr, CFRangeMake(0, CFArrayGetCount(arr)), key); if (i != kCFNotFound) { CFArrayRemoveValueAtIndex(arr, i); } return; } static void my_CFRelease(void * t) { void * * obj = (void * *)t; if (obj && *obj) { CFRelease(*obj); *obj = NULL; } return; } static CFDictionaryRef my_CFDictionaryGetDictionary(CFDictionaryRef dict, CFStringRef key) { if (isA_CFDictionary(dict) == NULL) { return (NULL); } return (isA_CFDictionary(CFDictionaryGetValue(dict, key))); } static CFDictionaryRef my_SCDCopy(SCDynamicStoreRef session, CFStringRef key) { CFDictionaryRef dict; dict = SCDynamicStoreCopyValue(session, key); if (isA_CFDictionary(dict) == NULL) { my_CFRelease(&dict); } return dict; } static boolean_t cfstring_to_ipvx(int family, CFStringRef str, void * addr, int addr_size) { char buf[128]; if (isA_CFString(str) == NULL) { goto done; } switch (family) { case AF_INET: if (addr_size < sizeof(struct in_addr)) { goto done; } break; case AF_INET6: if (addr_size < sizeof(struct in6_addr)) { goto done; } break; default: goto done; } (void)_SC_cfstring_to_cstring(str, buf, sizeof(buf), kCFStringEncodingASCII); if (inet_pton(family, buf, addr) == 1) { return (TRUE); } done: bzero(addr, addr_size); return (FALSE); } static boolean_t cfstring_to_ip(CFStringRef str, struct in_addr * ip_p) { return (cfstring_to_ipvx(AF_INET, str, ip_p, sizeof(*ip_p))); } static boolean_t cfstring_to_ip6(CFStringRef str, struct in6_addr * ip6_p) { return (cfstring_to_ipvx(AF_INET6, str, ip6_p, sizeof(*ip6_p))); } /* * Function: parse_component * Purpose: * Given a string 'key' and a string prefix 'prefix', * return the next component in the slash '/' separated * key. * * Examples: * 1. key = "a/b/c" prefix = "a/" * returns "b" * 2. key = "a/b/c" prefix = "a/b/" * returns "c" */ 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) { return (comp); } range.length = CFStringGetLength(comp) - range.location; CFStringDelete(comp, range); return (comp); } static void append_netinfo_arrays(CFDictionaryRef dict, CFMutableArrayRef ni_addrs, CFMutableArrayRef ni_tags) { CFArrayRef addrs; CFArrayRef tags; if (isA_CFDictionary(dict) == NULL) return; addrs = isA_CFArray(CFDictionaryGetValue(dict, kSCPropNetNetInfoServerAddresses)); tags = isA_CFArray(CFDictionaryGetValue(dict, kSCPropNetNetInfoServerTags)); if (addrs && tags) { CFIndex addrs_count = CFArrayGetCount(addrs); CFIndex tags_count = CFArrayGetCount(tags); if (addrs_count > 0) { if (addrs_count == tags_count) { CFArrayAppendArray(ni_addrs, addrs, CFRangeMake(0, addrs_count)); CFArrayAppendArray(ni_tags, tags, CFRangeMake(0, tags_count)); } } } return; } static void append_netinfo_broadcast_addresses(CFDictionaryRef netinfo_dict, CFDictionaryRef ipv4_dict, CFMutableArrayRef ni_addrs, CFMutableArrayRef ni_tags) { CFArrayRef addrs; CFIndex addrs_count; CFIndex i; CFArrayRef masks; CFIndex masks_count; CFStringRef tag; tag = CFDictionaryGetValue(netinfo_dict, kSCPropNetNetInfoBroadcastServerTag); tag = isA_CFString(tag); if (tag == NULL) { tag = kSCValNetNetInfoDefaultServerTag; } addrs = isA_CFArray(CFDictionaryGetValue(ipv4_dict, kSCPropNetIPv4Addresses)); masks = isA_CFArray(CFDictionaryGetValue(ipv4_dict, kSCPropNetIPv4SubnetMasks)); if (addrs == NULL || masks == NULL) { return; } masks_count = CFArrayGetCount(masks); addrs_count = CFArrayGetCount(addrs); if (addrs_count != masks_count) { return; } for (i = 0; i < addrs_count; i++) { struct in_addr addr; CFStringRef broadcast = NULL; struct in_addr mask; if (cfstring_to_ip(CFArrayGetValueAtIndex(addrs, i), &addr) && cfstring_to_ip(CFArrayGetValueAtIndex(masks, i), &mask)) { struct in_addr b; b.s_addr = htonl(ntohl(addr.s_addr) | ~ntohl(mask.s_addr)); broadcast = CFStringCreateWithFormat(NULL, NULL, CFSTR(IP_FORMAT), IP_LIST(&b)); CFArrayAppendValue(ni_addrs, broadcast); CFArrayAppendValue(ni_tags, tag); my_CFRelease(&broadcast); } } return; } static CFDictionaryRef make_netinfo_dict(CFDictionaryRef state_dict, CFDictionaryRef setup_dict, CFDictionaryRef ipv4_dict) { boolean_t has_manual = FALSE; boolean_t has_broadcast = FALSE; boolean_t has_dhcp = FALSE; CFIndex i; CFArrayRef m = NULL; CFIndex n; CFMutableArrayRef ni_addrs = NULL; CFMutableDictionaryRef ni_dict = NULL; CFMutableArrayRef ni_tags = NULL; if (setup_dict == NULL || ipv4_dict == NULL) { goto netinfo_done; } m = isA_CFArray(CFDictionaryGetValue(setup_dict, kSCPropNetNetInfoBindingMethods)); if (m == NULL) { goto netinfo_done; } ni_addrs = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); ni_tags = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); /* find out which are configured */ n = CFArrayGetCount(m); for (i = 0; i < n; i++) { CFStringRef method = CFArrayGetValueAtIndex(m, i); if (CFEqual(method, kSCValNetNetInfoBindingMethodsManual)) { has_manual = TRUE; } else if (CFEqual(method, kSCValNetNetInfoBindingMethodsDHCP)) { has_dhcp = TRUE; } else if (CFEqual(method, kSCValNetNetInfoBindingMethodsBroadcast)) { has_broadcast = TRUE; } } if (has_dhcp && state_dict != NULL) { append_netinfo_arrays(state_dict, ni_addrs, ni_tags); } if (has_manual) { append_netinfo_arrays(setup_dict, ni_addrs, ni_tags); } if (has_broadcast) { append_netinfo_broadcast_addresses(setup_dict, ipv4_dict, ni_addrs, ni_tags); } if (CFArrayGetCount(ni_addrs) == 0) { goto netinfo_done; } ni_dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(ni_dict, kSCPropNetNetInfoServerAddresses, ni_addrs); CFDictionarySetValue(ni_dict, kSCPropNetNetInfoServerTags, ni_tags); netinfo_done: my_CFRelease(&ni_addrs); my_CFRelease(&ni_tags); return (ni_dict); } static CFMutableDictionaryRef service_dict_copy(CFStringRef serviceID) { CFDictionaryRef d = NULL; CFMutableDictionaryRef service_dict; /* create a modifyable dictionary, a copy or a new one */ d = CFDictionaryGetValue(S_service_state_dict, serviceID); if (d == NULL) { service_dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); } else { service_dict = CFDictionaryCreateMutableCopy(NULL, 0, d); } return (service_dict); } static boolean_t service_dict_set(CFStringRef serviceID, CFStringRef entity, CFDictionaryRef new_dict) { boolean_t changed = FALSE; CFDictionaryRef old; CFMutableDictionaryRef service_dict; service_dict = service_dict_copy(serviceID); old = CFDictionaryGetValue(service_dict, entity); if (new_dict == NULL) { if (old != NULL) { SCLog(S_IPMonitor_debug, LOG_INFO, CFSTR("IPMonitor: serviceID %@ removed %@ dictionary = %@"), serviceID, entity, old); CFDictionaryRemoveValue(service_dict, entity); changed = TRUE; } } else { if (old == NULL || CFEqual(new_dict, old) == FALSE) { SCLog(S_IPMonitor_debug, LOG_INFO, CFSTR("IPMonitor: serviceID %@ changed %@" " dictionary\nold %@\nnew %@"), serviceID, entity, (old != NULL) ? (CFTypeRef)old : (CFTypeRef)CFSTR(""), new_dict); CFDictionarySetValue(service_dict, entity, new_dict); changed = TRUE; } } if (CFDictionaryGetCount(service_dict) == 0) { CFDictionaryRemoveValue(S_service_state_dict, serviceID); } else { CFDictionarySetValue(S_service_state_dict, serviceID, service_dict); } my_CFRelease(&service_dict); return (changed); } static CFDictionaryRef service_dict_get(CFStringRef serviceID, CFStringRef entity) { CFDictionaryRef service_dict; service_dict = CFDictionaryGetValue(S_service_state_dict, serviceID); if (service_dict == NULL) { return (NULL); } return (CFDictionaryGetValue(service_dict, entity)); } /** ** GetEntityChangesFunc functions **/ static __inline__ struct in_addr subnet_addr(struct in_addr addr, struct in_addr mask) { struct in_addr net; net.s_addr = htonl((uint32_t)ntohl(addr.s_addr) & (uint32_t)ntohl(mask.s_addr)); return (net); } static boolean_t get_ipv4_changes(CFStringRef serviceID, CFDictionaryRef state_dict, CFDictionaryRef setup_dict, CFDictionaryRef info) { struct in_addr addr = { 0 }; CFArrayRef addrs; boolean_t changed = FALSE; CFMutableDictionaryRef dict = NULL; struct in_addr mask = { 0 }; CFArrayRef masks; CFDictionaryRef new_dict = NULL; CFStringRef router = NULL; boolean_t valid_ip = FALSE; boolean_t valid_mask = FALSE; if (state_dict == NULL) { goto done; } addrs = isA_CFArray(CFDictionaryGetValue(state_dict, kSCPropNetIPv4Addresses)); if (addrs != NULL && CFArrayGetCount(addrs) > 0) { valid_ip = cfstring_to_ip(CFArrayGetValueAtIndex(addrs, 0), &addr); } masks = isA_CFArray(CFDictionaryGetValue(state_dict, kSCPropNetIPv4SubnetMasks)); if (masks != NULL && CFArrayGetCount(masks) > 0) { valid_mask = cfstring_to_ip(CFArrayGetValueAtIndex(masks, 0), &mask); } if (valid_ip == FALSE) { SCLog(S_IPMonitor_debug, LOG_INFO, CFSTR("IPMonitor: %@ has no valid IP address, ignoring"), serviceID); goto done; } dict = CFDictionaryCreateMutableCopy(NULL, 0, state_dict); if (setup_dict != NULL) { router = CFDictionaryGetValue(setup_dict, kSCPropNetIPv4Router); if (router != NULL) { CFDictionarySetValue(dict, kSCPropNetIPv4Router, router); } } /* check whether the router is direct, or non-local */ router = CFDictionaryGetValue(dict, kSCPropNetIPv4Router); if (router != NULL) { struct in_addr router_ip; if (cfstring_to_ip(router, &router_ip)) { if (router_ip.s_addr == addr.s_addr) { /* default route routes directly to the interface */ CFDictionarySetValue(dict, kRouterIsDirect, kCFBooleanTrue); } else if (valid_mask && subnet_addr(addr, mask).s_addr != subnet_addr(router_ip, mask).s_addr) { /* router is not on the same subnet */ CFDictionarySetValue(dict, kRouterNeedsLocalIP, CFArrayGetValueAtIndex(addrs, 0)); } } } new_dict = dict; done: changed = service_dict_set(serviceID, kSCEntNetIPv4, new_dict); my_CFRelease(&new_dict); return (changed); } static boolean_t get_ipv6_changes(CFStringRef serviceID, CFDictionaryRef state_dict, CFDictionaryRef setup_dict, CFDictionaryRef info) { struct in6_addr addr; CFArrayRef addrs; boolean_t changed = FALSE; CFMutableDictionaryRef dict = NULL; CFDictionaryRef new_dict = NULL; CFStringRef router = NULL; boolean_t valid_ip = FALSE; if (state_dict == NULL) { goto done; } addrs = isA_CFArray(CFDictionaryGetValue(state_dict, kSCPropNetIPv6Addresses)); if (addrs != NULL && CFArrayGetCount(addrs) > 0) { valid_ip = cfstring_to_ip6(CFArrayGetValueAtIndex(addrs, 0), &addr); } if (valid_ip == FALSE) { SCLog(S_IPMonitor_debug, LOG_INFO, CFSTR("IPMonitor: %@ has no valid IPv6 address, ignoring"), serviceID); goto done; } dict = CFDictionaryCreateMutableCopy(NULL, 0, state_dict); if (setup_dict != NULL) { router = CFDictionaryGetValue(setup_dict, kSCPropNetIPv6Router); if (router != NULL) { CFDictionarySetValue(dict, kSCPropNetIPv6Router, router); } } new_dict = dict; done: changed = service_dict_set(serviceID, kSCEntNetIPv6, new_dict); my_CFRelease(&new_dict); return (changed); } static boolean_t dns_has_supplemental(CFStringRef serviceID) { CFDictionaryRef dns_dict; CFDictionaryRef service_dict; service_dict = CFDictionaryGetValue(S_service_state_dict, serviceID); if (service_dict == NULL) { return FALSE; } dns_dict = CFDictionaryGetValue(service_dict, kSCEntNetDNS); if (dns_dict == NULL) { return FALSE; } return CFDictionaryContainsKey(dns_dict, kSCPropNetDNSSupplementalMatchDomains); } static void merge_dns_prop(CFMutableDictionaryRef dict, CFStringRef key, CFDictionaryRef state_dict, CFDictionaryRef setup_dict, Boolean append) { CFArrayRef setup_prop = NULL; CFArrayRef state_prop = NULL; if (setup_dict != NULL) { setup_prop = isA_CFArray(CFDictionaryGetValue(setup_dict, key)); } if (state_dict != NULL) { state_prop = isA_CFArray(CFDictionaryGetValue(state_dict, key)); } if ((setup_prop != NULL) && (state_prop != NULL)) { CFMutableArrayRef merge_prop; /* create a new list by merging the setup and state lists */ merge_prop = CFArrayCreateMutableCopy(NULL, 0, setup_prop); if (append) { CFRange state_range = CFRangeMake(0, CFArrayGetCount(state_prop)); CFArrayAppendArray(merge_prop, state_prop, state_range); } else { CFIndex i; CFIndex n; CFRange setup_range = CFRangeMake(0, CFArrayGetCount(setup_prop)); n = CFArrayGetCount(state_prop); for (i = 0; i < n; i++) { CFTypeRef val; val = CFArrayGetValueAtIndex(state_prop, i); if (!CFArrayContainsValue(setup_prop, setup_range, val)) { CFArrayAppendValue(merge_prop, val); } } } CFDictionarySetValue(dict, key, merge_prop); my_CFRelease(&merge_prop); } else if (setup_prop != NULL) { CFDictionarySetValue(dict, key, setup_prop); } else if (state_prop != NULL) { CFDictionarySetValue(dict, key, state_prop); } return; } static boolean_t get_dns_changes(CFStringRef serviceID, CFDictionaryRef state_dict, CFDictionaryRef setup_dict, CFDictionaryRef info) { boolean_t changed = FALSE; CFStringRef domain; int i; struct { CFStringRef key; Boolean append; } merge_list[] = { { kSCPropNetDNSSearchDomains, FALSE }, { kSCPropNetDNSServerAddresses, FALSE }, { kSCPropNetDNSSortList, FALSE }, { kSCPropNetDNSSupplementalMatchDomains, TRUE }, { kSCPropNetDNSSupplementalMatchOrders, TRUE }, { NULL, FALSE } }; CFMutableDictionaryRef new_dict = NULL; CFStringRef pick_list[] = { kSCPropNetDNSDomainName, kSCPropNetDNSOptions, kSCPropNetDNSSearchOrder, kSCPropNetDNSServerPort, kSCPropNetDNSServerTimeout, NULL }; if (state_dict == NULL && setup_dict == NULL) { /* there is no DNS */ goto done; } if (service_dict_get(serviceID, kSCEntNetIPv4) == NULL && service_dict_get(serviceID, kSCEntNetIPv6) == NULL) { /* no point in remembering the DNS */ goto done; } // merge DNS configuration new_dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); for (i = 0; merge_list[i].key != NULL; i++) { merge_dns_prop(new_dict, merge_list[i].key, state_dict, setup_dict, merge_list[i].append); } for (i = 0; pick_list[i]; i++) { CFTypeRef val = NULL; if (setup_dict != NULL) { val = CFDictionaryGetValue(setup_dict, pick_list[i]); } if (val == NULL && state_dict != NULL) { val = CFDictionaryGetValue(state_dict, pick_list[i]); } if (val != NULL) { CFDictionarySetValue(new_dict, pick_list[i], val); } } if (CFDictionaryGetCount(new_dict) == 0) { my_CFRelease(&new_dict); goto done; } /* * ensure any specified domain name (e.g. the domain returned by * a DHCP server) is in the search list. */ domain = CFDictionaryGetValue(new_dict, kSCPropNetDNSDomainName); if (isA_CFString(domain)) { CFArrayRef search; search = CFDictionaryGetValue(new_dict, kSCPropNetDNSSearchDomains); if (isA_CFArray(search) && !CFArrayContainsValue(search, CFRangeMake(0, CFArrayGetCount(search)), domain)) { CFMutableArrayRef new_search; new_search = CFArrayCreateMutableCopy(NULL, 0, search); CFArrayAppendValue(new_search, domain); CFDictionarySetValue(new_dict, kSCPropNetDNSSearchDomains, new_search); my_CFRelease(&new_search); } } done: changed = service_dict_set(serviceID, kSCEntNetDNS, new_dict); my_CFRelease(&new_dict); return (changed); } static boolean_t get_netinfo_changes(CFStringRef serviceID, CFDictionaryRef state_dict, CFDictionaryRef setup_dict, CFDictionaryRef info) { boolean_t changed = FALSE; CFDictionaryRef global_dict; CFDictionaryRef ipv4_dict; CFDictionaryRef new_dict = NULL; global_dict = my_CFDictionaryGetDictionary(info, S_setup_global_netinfo); ipv4_dict = service_dict_get(serviceID, kSCEntNetIPv4); new_dict = make_netinfo_dict(state_dict, global_dict, ipv4_dict); changed = service_dict_set(serviceID, kSCEntNetNetInfo, new_dict); my_CFRelease(&new_dict); return (changed); } static boolean_t get_proxies_changes(CFStringRef serviceID, CFDictionaryRef state_dict, CFDictionaryRef setup_dict, CFDictionaryRef info) { boolean_t changed = FALSE; CFDictionaryRef new_dict = NULL; if (service_dict_get(serviceID, kSCEntNetIPv4) == NULL && service_dict_get(serviceID, kSCEntNetIPv6) == NULL) { /* no point in remembering the Proxies */ goto done; } if (setup_dict != NULL) { new_dict = setup_dict; } else { new_dict = state_dict; } done: changed = service_dict_set(serviceID, kSCEntNetProxies, new_dict); return (changed); } static CFStringRef state_service_key(CFStringRef serviceID, CFStringRef entity) { return (SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, serviceID, entity)); } static CFStringRef setup_service_key(CFStringRef serviceID, CFStringRef entity) { return (SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, serviceID, entity)); } static CFDictionaryRef services_info_copy(SCDynamicStoreRef session, CFArrayRef service_list) { int count; CFMutableArrayRef get_keys; int i; int s; CFDictionaryRef info; count = CFArrayGetCount(service_list); get_keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); CFArrayAppendValue(get_keys, S_setup_global_netinfo); CFArrayAppendValue(get_keys, S_setup_global_proxies); CFArrayAppendValue(get_keys, S_setup_global_ipv4); for (s = 0; s < count; s++) { CFStringRef serviceID = CFArrayGetValueAtIndex(service_list, s); for (i = 0; i < ENTITY_TYPES_COUNT; i++) { CFStringRef setup_key; CFStringRef state_key; setup_key = setup_service_key(serviceID, entityTypeNames[i]); state_key = state_service_key(serviceID, entityTypeNames[i]); CFArrayAppendValue(get_keys, setup_key); CFArrayAppendValue(get_keys, state_key); my_CFRelease(&setup_key); my_CFRelease(&state_key); } } info = SCDynamicStoreCopyMultiple(session, get_keys, NULL); my_CFRelease(&get_keys); return (info); } static CFDictionaryRef get_service_setup_entity(CFDictionaryRef service_info, CFStringRef serviceID, CFStringRef entity) { CFStringRef setup_key; CFDictionaryRef setup_dict; setup_key = setup_service_key(serviceID, entity); setup_dict = my_CFDictionaryGetDictionary(service_info, setup_key); my_CFRelease(&setup_key); return (setup_dict); } static CFDictionaryRef get_service_state_entity(CFDictionaryRef service_info, CFStringRef serviceID, CFStringRef entity) { CFStringRef state_key; CFDictionaryRef state_dict; state_key = state_service_key(serviceID, entity); state_dict = my_CFDictionaryGetDictionary(service_info, state_key); my_CFRelease(&state_key); return (state_dict); } static int rtm_seq = 0; static boolean_t ipv4_route(int cmd, struct in_addr gateway, struct in_addr netaddr, struct in_addr netmask, char * ifname, boolean_t is_direct) { boolean_t default_route = (netaddr.s_addr == 0); int len; boolean_t ret = TRUE; struct { struct rt_msghdr hdr; struct sockaddr_in dst; struct sockaddr_in gway; struct sockaddr_in mask; struct sockaddr_dl link; } rtmsg; int sockfd = -1; if (default_route && S_netboot) { return (TRUE); } if ((sockfd = socket(PF_ROUTE, SOCK_RAW, AF_INET)) < 0) { SCLog(TRUE, LOG_INFO, CFSTR("IPMonitor: ipv4_route: open routing socket failed, %s"), strerror(errno)); return (FALSE); } memset(&rtmsg, 0, sizeof(rtmsg)); rtmsg.hdr.rtm_type = cmd; if (default_route) { if (is_direct) { /* if router is directly reachable, don't set the gateway flag */ rtmsg.hdr.rtm_flags = RTF_UP | RTF_STATIC; } else { rtmsg.hdr.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC; } } else { rtmsg.hdr.rtm_flags = RTF_UP | RTF_CLONING | RTF_STATIC; } rtmsg.hdr.rtm_version = RTM_VERSION; rtmsg.hdr.rtm_seq = ++rtm_seq; rtmsg.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK; rtmsg.dst.sin_len = sizeof(rtmsg.dst); rtmsg.dst.sin_family = AF_INET; rtmsg.dst.sin_addr = netaddr; rtmsg.gway.sin_len = sizeof(rtmsg.gway); rtmsg.gway.sin_family = AF_INET; rtmsg.gway.sin_addr = gateway; rtmsg.mask.sin_len = sizeof(rtmsg.mask); rtmsg.mask.sin_family = AF_INET; rtmsg.mask.sin_addr = netmask; len = sizeof(rtmsg); if (ifname) { rtmsg.link.sdl_len = sizeof(rtmsg.link); rtmsg.link.sdl_family = AF_LINK; rtmsg.link.sdl_nlen = strlen(ifname); rtmsg.hdr.rtm_addrs |= RTA_IFP; bcopy(ifname, rtmsg.link.sdl_data, rtmsg.link.sdl_nlen); } else { /* no link information */ len -= sizeof(rtmsg.link); } rtmsg.hdr.rtm_msglen = len; if (write(sockfd, &rtmsg, len) < 0) { if ((cmd == RTM_ADD) && (errno == EEXIST)) { /* no sense complaining about a route that already exists */ } else if ((cmd == RTM_DELETE) && (errno == ESRCH)) { /* no sense complaining about a route that isn't there */ } else { SCLog(S_IPMonitor_debug, LOG_INFO, CFSTR("IPMonitor ipv4_route: " "write routing socket failed, %s"), strerror(errno)); ret = FALSE; } } close(sockfd); return (ret); } static boolean_t ipv6_route(int cmd, struct in6_addr gateway, struct in6_addr netaddr, struct in6_addr netmask, char * ifname, boolean_t is_direct) { boolean_t default_route; int len; boolean_t ret = TRUE; struct { struct rt_msghdr hdr; struct sockaddr_in6 dst; struct sockaddr_in6 gway; struct sockaddr_in6 mask; struct sockaddr_dl link; } rtmsg; int sockfd = -1; struct in6_addr zeroes = IN6ADDR_ANY_INIT; default_route = (bcmp(&zeroes, &netaddr, sizeof(netaddr)) == 0); if (IN6_IS_ADDR_LINKLOCAL(&gateway) && ifname != NULL) { unsigned int index = if_nametoindex(ifname); /* add the scope id to the link local address */ gateway.__u6_addr.__u6_addr16[1] = (uint16_t)htons(index); } if ((sockfd = socket(PF_ROUTE, SOCK_RAW, AF_INET)) < 0) { SCLog(TRUE, LOG_INFO, CFSTR("IPMonitor ipv6_route: open routing socket failed, %s"), strerror(errno)); return (FALSE); } memset(&rtmsg, 0, sizeof(rtmsg)); rtmsg.hdr.rtm_type = cmd; if (default_route) { if (is_direct) { /* if router is directly reachable, don't set the gateway flag */ rtmsg.hdr.rtm_flags = RTF_UP | RTF_STATIC; } else { rtmsg.hdr.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC; } } else { rtmsg.hdr.rtm_flags = RTF_UP | RTF_CLONING | RTF_STATIC; } rtmsg.hdr.rtm_version = RTM_VERSION; rtmsg.hdr.rtm_seq = ++rtm_seq; rtmsg.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK; rtmsg.dst.sin6_len = sizeof(rtmsg.dst); rtmsg.dst.sin6_family = AF_INET6; rtmsg.dst.sin6_addr = netaddr; rtmsg.gway.sin6_len = sizeof(rtmsg.gway); rtmsg.gway.sin6_family = AF_INET6; rtmsg.gway.sin6_addr = gateway; rtmsg.mask.sin6_len = sizeof(rtmsg.mask); rtmsg.mask.sin6_family = AF_INET6; rtmsg.mask.sin6_addr = netmask; len = sizeof(rtmsg); if (ifname) { rtmsg.link.sdl_len = sizeof(rtmsg.link); rtmsg.link.sdl_family = AF_LINK; rtmsg.link.sdl_nlen = strlen(ifname); rtmsg.hdr.rtm_addrs |= RTA_IFP; bcopy(ifname, rtmsg.link.sdl_data, rtmsg.link.sdl_nlen); } else { /* no link information */ len -= sizeof(rtmsg.link); } rtmsg.hdr.rtm_msglen = len; if (write(sockfd, &rtmsg, len) < 0) { if ((cmd == RTM_ADD) && (errno == EEXIST)) { /* no sense complaining about a route that already exists */ } else if ((cmd == RTM_DELETE) && (errno == ESRCH)) { /* no sense complaining about a route that isn't there */ } else { SCLog(S_IPMonitor_debug, LOG_INFO, CFSTR("IPMonitor ipv6_route: write routing" " socket failed, %s"), strerror(errno)); ret = FALSE; } } close(sockfd); return (ret); } static boolean_t ipv4_subnet_route_add(struct in_addr local_ip, struct in_addr subnet, struct in_addr mask, char * ifname) { if (S_IPMonitor_debug) { SCLog(TRUE, LOG_INFO, CFSTR("IPMonitor: IPv4 route add -net " IP_FORMAT " -netmask %s interface %s"), IP_LIST(&subnet), inet_ntoa(mask), ifname); } return (ipv4_route(RTM_ADD, local_ip, subnet, mask, ifname, FALSE)); } static boolean_t ipv4_subnet_route_delete(struct in_addr subnet, struct in_addr mask) { if (S_IPMonitor_debug) { SCLog(TRUE, LOG_INFO, CFSTR("IPMonitor: IPv4 route delete -net " IP_FORMAT " %s"), IP_LIST(&subnet), inet_ntoa(mask)); } return (ipv4_route(RTM_DELETE, S_ip_zeros, subnet, mask, NULL, FALSE)); } static boolean_t ipv4_default_route_delete(void) { if (S_IPMonitor_debug) { SCLog(TRUE, LOG_INFO, CFSTR("IPMonitor: IPv4 route delete default")); } return (ipv4_route(RTM_DELETE, S_ip_zeros, S_ip_zeros, S_ip_zeros, NULL, FALSE)); } static boolean_t ipv4_default_route_add(struct in_addr router, char * ifname, boolean_t is_direct) { if (S_IPMonitor_debug) { SCLog(TRUE, LOG_INFO, CFSTR("IPMonitor: IPv4 route add default" " %s interface %s direct %d"), inet_ntoa(router), ifname, is_direct); } return (ipv4_route(RTM_ADD, router, S_ip_zeros, S_ip_zeros, ifname, is_direct)); } static boolean_t ipv4_default_route_change(struct in_addr router, char * ifname, boolean_t is_direct) { if (S_IPMonitor_debug) { SCLog(TRUE, LOG_INFO, CFSTR("IPMonitor: IPv4 route change default" " %s interface %s direct %d"), inet_ntoa(router), ifname, is_direct); } return (ipv4_route(RTM_CHANGE, router, S_ip_zeros, S_ip_zeros, ifname, is_direct)); } static boolean_t ipv6_default_route_delete(void) { if (S_IPMonitor_debug) { SCLog(TRUE, LOG_INFO, CFSTR("IPMonitor: IPv6 route delete default")); } return (ipv6_route(RTM_DELETE, S_ip6_zeros, S_ip6_zeros, S_ip6_zeros, NULL, FALSE)); } static boolean_t ipv6_default_route_add(struct in6_addr router, char * ifname, boolean_t is_direct) { if (S_IPMonitor_debug) { char str[128]; str[0] = '\0'; inet_ntop(AF_INET6, &router, str, sizeof(str)); SCLog(TRUE,LOG_INFO, CFSTR("IPMonitor: IPv6 route add default" " %s interface %s direct %d"), str, ifname, is_direct); } return (ipv6_route(RTM_ADD, router, S_ip6_zeros, S_ip6_zeros, ifname, is_direct)); } static boolean_t multicast_route_delete() { struct in_addr gateway = { htonl(INADDR_LOOPBACK) }; struct in_addr netaddr = { htonl(INADDR_UNSPEC_GROUP) }; struct in_addr netmask = { htonl(IN_CLASSD_NET) }; return (ipv4_route(RTM_DELETE, gateway, netaddr, netmask, "lo0", FALSE)); } static boolean_t multicast_route_add() { struct in_addr gateway = { htonl(INADDR_LOOPBACK) }; struct in_addr netaddr = { htonl(INADDR_UNSPEC_GROUP) }; struct in_addr netmask = { htonl(IN_CLASSD_NET) }; return (ipv4_route(RTM_ADD, gateway, netaddr, netmask, "lo0", FALSE)); } static void set_ipv4_router(struct in_addr * router, char * ifname, boolean_t is_direct) { if (S_router_subnet.s_addr != 0) { ipv4_subnet_route_delete(S_router_subnet, S_router_subnet_mask); S_router_subnet.s_addr = S_router_subnet_mask.s_addr = 0; } /* assign the new default route, ensure local multicast route available */ (void)ipv4_default_route_delete(); if (router != NULL) { (void)ipv4_default_route_add(*router, ifname, is_direct); (void)multicast_route_delete(); } else { (void)multicast_route_add(); } return; } static void set_ipv6_router(struct in6_addr * router, char * ifname, boolean_t is_direct) { /* assign the new default route, ensure local multicast route available */ (void)ipv6_default_route_delete(); if (router != NULL) { (void)ipv6_default_route_add(*router, ifname, is_direct); } return; } static __inline__ void empty_dns() { (void)unlink(VAR_RUN_RESOLV_CONF); } static void empty_netinfo(SCDynamicStoreRef session) { int fd = open(VAR_RUN_NICONFIG_LOCAL_XML "-", O_CREAT|O_TRUNC|O_WRONLY, 0644); if (fd >= 0) { close(fd); rename(VAR_RUN_NICONFIG_LOCAL_XML "-", VAR_RUN_NICONFIG_LOCAL_XML); } return; } static void set_dns(CFArrayRef val_search_domains, CFStringRef val_domain_name, CFArrayRef val_servers, CFArrayRef val_sortlist) { FILE * f = fopen(VAR_RUN_RESOLV_CONF "-", "w"); /* publish new resolv.conf */ if (f) { CFIndex i; CFIndex n; if (isA_CFString(val_domain_name)) { SCPrint(TRUE, f, CFSTR("domain %@\n"), val_domain_name); } if (isA_CFArray(val_search_domains)) { SCPrint(TRUE, f, CFSTR("search")); n = CFArrayGetCount(val_search_domains); for (i = 0; i < n; i++) { CFStringRef domain; domain = CFArrayGetValueAtIndex(val_search_domains, i); if (isA_CFString(domain)) { SCPrint(TRUE, f, CFSTR(" %@"), domain); } } SCPrint(TRUE, f, CFSTR("\n")); } if (isA_CFArray(val_servers)) { n = CFArrayGetCount(val_servers); for (i = 0; i < n; i++) { CFStringRef nameserver; nameserver = CFArrayGetValueAtIndex(val_servers, i); if (isA_CFString(nameserver)) { SCPrint(TRUE, f, CFSTR("nameserver %@\n"), nameserver); } } } if (isA_CFArray(val_sortlist)) { SCPrint(TRUE, f, CFSTR("sortlist")); n = CFArrayGetCount(val_sortlist); for (i = 0; i < n; i++) { CFStringRef address; address = CFArrayGetValueAtIndex(val_sortlist, i); if (isA_CFString(address)) { SCPrint(TRUE, f, CFSTR(" %@"), address); } } SCPrint(TRUE, f, CFSTR("\n")); } fclose(f); rename(VAR_RUN_RESOLV_CONF "-", VAR_RUN_RESOLV_CONF); } return; } static void set_netinfo(CFDictionaryRef dict) { int fd = open(VAR_RUN_NICONFIG_LOCAL_XML "-", O_CREAT|O_TRUNC|O_WRONLY, 0644); if (fd >= 0) { /* publish new netinfo config */ CFDataRef contents; contents = CFPropertyListCreateXMLData(NULL, dict); if (contents) { CFIndex len = CFDataGetLength(contents); write(fd, CFDataGetBytePtr(contents), len); CFRelease(contents); } close(fd); rename(VAR_RUN_NICONFIG_LOCAL_XML "-", VAR_RUN_NICONFIG_LOCAL_XML); } return; } static boolean_t router_is_our_ipv6_address(CFStringRef router, CFArrayRef addr_list) { CFIndex i; CFIndex n = CFArrayGetCount(addr_list); struct in6_addr r; (void)cfstring_to_ip6(router, &r); for (i = 0; i < n; i++) { struct in6_addr ip; if (cfstring_to_ip6(CFArrayGetValueAtIndex(addr_list, i), &ip) && bcmp(&r, &ip, sizeof(r)) == 0) { return (TRUE); } } return (FALSE); } static void update_ipv4(SCDynamicStoreRef session, CFDictionaryRef service_info, CFStringRef primary, keyChangeListRef keys) { CFDictionaryRef ipv4_dict = NULL; if (primary != NULL) { CFDictionaryRef service_dict; service_dict = CFDictionaryGetValue(S_service_state_dict, primary); if (service_dict != NULL) { ipv4_dict = CFDictionaryGetValue(service_dict, kSCEntNetIPv4); } } if (ipv4_dict != NULL) { CFMutableDictionaryRef dict = NULL; CFStringRef if_name = NULL; char ifn[IFNAMSIZ + 1] = { '\0' }; char * ifn_p = NULL; boolean_t is_direct = FALSE; struct in_addr local_ip = { 0 }; CFStringRef val_router = NULL; struct in_addr router = { 0 }; dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); val_router = CFDictionaryGetValue(ipv4_dict, kSCPropNetIPv4Router); if (val_router != NULL) { cfstring_to_ip(val_router, &router); CFDictionarySetValue(dict, kSCPropNetIPv4Router, val_router); if (CFDictionaryContainsKey(ipv4_dict, kRouterIsDirect)) { is_direct = TRUE; } else { CFStringRef local_ip_str; local_ip_str = CFDictionaryGetValue(ipv4_dict, kRouterNeedsLocalIP); if (local_ip_str != NULL) { cfstring_to_ip(local_ip_str, &local_ip); } } } else { CFArrayRef addrs; addrs = CFDictionaryGetValue(ipv4_dict, kSCPropNetIPv4Addresses); val_router = CFArrayGetValueAtIndex(addrs, 0); cfstring_to_ip(val_router, &router); is_direct = TRUE; } if_name = CFDictionaryGetValue(ipv4_dict, kSCPropInterfaceName); if (if_name) { CFDictionarySetValue(dict, kSCDynamicStorePropNetPrimaryInterface, if_name); if (CFStringGetCString(if_name, ifn, sizeof(ifn), kCFStringEncodingASCII)) { ifn_p = ifn; } } CFDictionarySetValue(dict, kSCDynamicStorePropNetPrimaryService, primary); keyChangeListSetValue(keys, S_state_global_ipv4, dict); CFRelease(dict); /* route add default ... */ if (local_ip.s_addr != 0) { struct in_addr m; m.s_addr = htonl(INADDR_BROADCAST); ipv4_subnet_route_add(local_ip, router, m, ifn_p); set_ipv4_router(&local_ip, ifn_p, FALSE); ipv4_default_route_change(router, ifn_p, FALSE); S_router_subnet = router; S_router_subnet_mask = m; } else { set_ipv4_router(&router, ifn_p, is_direct); } } else { keyChangeListRemoveValue(keys, S_state_global_ipv4); set_ipv4_router(NULL, NULL, FALSE); } return; } static void update_ipv6(SCDynamicStoreRef session, CFDictionaryRef service_info, CFStringRef primary, keyChangeListRef keys) { CFDictionaryRef ipv6_dict = NULL; if (primary != NULL) { CFDictionaryRef service_dict; service_dict = CFDictionaryGetValue(S_service_state_dict, primary); if (service_dict != NULL) { ipv6_dict = CFDictionaryGetValue(service_dict, kSCEntNetIPv6); } } if (ipv6_dict != NULL) { CFArrayRef addrs; CFMutableDictionaryRef dict = NULL; CFStringRef if_name = NULL; char ifn[IFNAMSIZ + 1] = { '\0' }; char * ifn_p = NULL; boolean_t is_direct = FALSE; CFStringRef val_router = NULL; dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); val_router = CFDictionaryGetValue(ipv6_dict, kSCPropNetIPv6Router); addrs = CFDictionaryGetValue(ipv6_dict, kSCPropNetIPv6Addresses); if (val_router != NULL) { /* no router if router is one of our IP addresses */ is_direct = router_is_our_ipv6_address(val_router, addrs); CFDictionarySetValue(dict, kSCPropNetIPv6Router, val_router); } else { val_router = CFArrayGetValueAtIndex(addrs, 0); is_direct = TRUE; } if_name = CFDictionaryGetValue(ipv6_dict, kSCPropInterfaceName); if (if_name) { CFDictionarySetValue(dict, kSCDynamicStorePropNetPrimaryInterface, if_name); if (CFStringGetCString(if_name, ifn, sizeof(ifn), kCFStringEncodingASCII)) { ifn_p = ifn; } } CFDictionarySetValue(dict, kSCDynamicStorePropNetPrimaryService, primary); keyChangeListSetValue(keys, S_state_global_ipv6, dict); CFRelease(dict); { /* route add default ... */ struct in6_addr router; (void)cfstring_to_ip6(val_router, &router); set_ipv6_router(&router, ifn_p, is_direct); } } else { keyChangeListRemoveValue(keys, S_state_global_ipv6); set_ipv6_router(NULL, NULL, FALSE); } return; } static void update_dns(SCDynamicStoreRef session, CFDictionaryRef service_info, CFStringRef primary, keyChangeListRef keys) { CFDictionaryRef dict = NULL; if (primary != NULL) { CFDictionaryRef service_dict; service_dict = CFDictionaryGetValue(S_service_state_dict, primary); if (service_dict != NULL) { dict = CFDictionaryGetValue(service_dict, kSCEntNetDNS); } } if (dict == NULL) { empty_dns(); keyChangeListRemoveValue(keys, S_state_global_dns); } else { set_dns(CFDictionaryGetValue(dict, kSCPropNetDNSSearchDomains), CFDictionaryGetValue(dict, kSCPropNetDNSDomainName), CFDictionaryGetValue(dict, kSCPropNetDNSServerAddresses), CFDictionaryGetValue(dict, kSCPropNetDNSSortList)); keyChangeListSetValue(keys, S_state_global_dns, dict); } return; } static void update_dnsinfo(CFStringRef primary, CFArrayRef service_order, keyChangeListRef keys) { CFDictionaryRef dict = NULL; if (primary != NULL) { CFDictionaryRef service_dict; service_dict = CFDictionaryGetValue(S_service_state_dict, primary); if (service_dict != NULL) { dict = CFDictionaryGetValue(service_dict, kSCEntNetDNS); } } if (dict == NULL) { /* update DNS configuration */ dns_configuration_set(NULL, NULL, NULL); } else { /* update DNS configuration */ dns_configuration_set(dict, S_service_state_dict, service_order); } keyChangeListNotifyKey(keys, S_notify_dnsinfo); return; } static void update_netinfo(SCDynamicStoreRef session, CFDictionaryRef service_info, CFStringRef primary, keyChangeListRef keys) { CFDictionaryRef dict = NULL; if (primary != NULL) { CFDictionaryRef ipv4_dict = NULL; CFDictionaryRef service_dict; CFDictionaryRef setup_dict; CFStringRef state_key; CFDictionaryRef state_dict; service_dict = CFDictionaryGetValue(S_service_state_dict, primary); if (service_dict != NULL) { ipv4_dict = CFDictionaryGetValue(service_dict, kSCEntNetIPv4); } state_key = state_service_key(primary, kSCEntNetNetInfo); state_dict = my_CFDictionaryGetDictionary(service_info, state_key); if (state_dict != NULL) { CFRetain(state_dict); } else { state_dict = my_SCDCopy(session, state_key); } setup_dict = my_CFDictionaryGetDictionary(service_info, S_setup_global_netinfo); dict = make_netinfo_dict(state_dict, setup_dict, ipv4_dict); my_CFRelease(&state_key); my_CFRelease(&state_dict); } if (dict == NULL) { empty_netinfo(session); keyChangeListRemoveValue(keys, S_state_global_netinfo); } else { set_netinfo(dict); keyChangeListSetValue(keys, S_state_global_netinfo, dict); my_CFRelease(&dict); } return; } static void update_proxies(SCDynamicStoreRef session, CFDictionaryRef service_info, CFStringRef primary, keyChangeListRef keys) { CFDictionaryRef dict = NULL; if (primary != NULL) { CFDictionaryRef service_dict; service_dict = CFDictionaryGetValue(S_service_state_dict, primary); if (service_dict != NULL) { dict = CFDictionaryGetValue(service_dict, kSCEntNetProxies); if (dict == NULL) { dict = my_CFDictionaryGetDictionary(service_info, S_setup_global_proxies); } } } if (dict == NULL) { keyChangeListRemoveValue(keys, S_state_global_proxies); } else { keyChangeListSetValue(keys, S_state_global_proxies, dict); } return; } static unsigned int get_service_rank(CFStringRef proto_key, CFArrayRef order, CFStringRef serviceID) { CFDictionaryRef d; CFIndex i; CFDictionaryRef proto_dict; if (serviceID == NULL) { goto done; } d = CFDictionaryGetValue(S_service_state_dict, serviceID); if (d == NULL) { goto done; } proto_dict = CFDictionaryGetValue(d, proto_key); if (proto_dict) { CFStringRef if_name; CFNumberRef override = NULL; if_name = CFDictionaryGetValue(proto_dict, kSCPropInterfaceName); if (S_ppp_override_primary == TRUE && if_name != NULL && CFStringHasPrefix(if_name, CFSTR("ppp"))) { /* PPP override: make ppp* look the best */ /* Hack: should use interface type, not interface name */ return (0); } /* check for the "OverridePrimary" property */ override = CFDictionaryGetValue(proto_dict, kSCPropNetOverridePrimary); if (isA_CFNumber(override) != NULL) { int val = 0; CFNumberGetValue(override, kCFNumberIntType, &val); if (val != 0) { return (0); } } } if (serviceID != NULL && order != NULL) { CFIndex n = CFArrayGetCount(order); for (i = 0; i < n; i++) { CFStringRef s = isA_CFString(CFArrayGetValueAtIndex(order, i)); if (s == NULL) { continue; } if (CFEqual(serviceID, s)) { return (i + 1); } } } done: return (UINT_MAX); } /** ** Service election: **/ typedef boolean_t (*routerCheckFunc)(CFStringRef str); static boolean_t check_ipv4_router(CFStringRef router) { struct in_addr ip; return (cfstring_to_ip(router, &ip)); } static boolean_t check_ipv6_router(CFStringRef router) { struct in6_addr ip6; return (cfstring_to_ip6(router, &ip6)); } struct election_state { routerCheckFunc router_check; CFStringRef proto_key; /* e.g. kSCEntNetIPv4 */ CFStringRef router_key;/* e.g. kSCPropNetIPv4Router */ CFArrayRef order; CFStringRef new_primary; boolean_t new_has_router; unsigned int new_primary_index; }; static void elect_protocol(const void * key, const void * value, void * context) { struct election_state * elect_p = (struct election_state *)context; CFDictionaryRef proto_dict = NULL; CFStringRef router; boolean_t router_valid = FALSE; CFStringRef serviceID = (CFStringRef)key; CFDictionaryRef service_dict = (CFDictionaryRef)value; unsigned int service_index; proto_dict = CFDictionaryGetValue(service_dict, elect_p->proto_key); if (proto_dict == NULL) { return; } router = CFDictionaryGetValue(proto_dict, elect_p->router_key); router_valid = (*elect_p->router_check)(router); if (router_valid == FALSE && elect_p->new_has_router == TRUE) { /* skip it */ return; } service_index = get_service_rank(elect_p->proto_key, elect_p->order, serviceID); if (elect_p->new_primary == NULL || service_index < elect_p->new_primary_index || (router_valid && elect_p->new_has_router == FALSE)) { my_CFRelease(&elect_p->new_primary); elect_p->new_primary = CFRetain(serviceID); elect_p->new_primary_index = service_index; elect_p->new_has_router = router_valid; } return; } static CFStringRef elect_new_primary(CFArrayRef order, CFStringRef proto_key, CFStringRef router_key) { struct election_state elect; if (CFEqual(proto_key, kSCEntNetIPv4)) { elect.router_check = check_ipv4_router; } else if (CFEqual(proto_key, kSCEntNetIPv6)) { elect.router_check = check_ipv6_router; } else { return (NULL); } elect.order = order; elect.new_primary = NULL; elect.new_primary_index = 0; elect.new_has_router = FALSE; elect.proto_key = proto_key; elect.router_key = router_key; CFDictionaryApplyFunction(S_service_state_dict, elect_protocol, &elect); return (elect.new_primary); } static uint32_t service_changed(CFDictionaryRef services_info, CFStringRef serviceID) { uint32_t changed = 0; int i; for (i = 0; i < ENTITY_TYPES_COUNT; i++) { GetEntityChangesFuncRef func = entityChangeFunc[i]; if ((*func)(serviceID, get_service_state_entity(services_info, serviceID, entityTypeNames[i]), get_service_setup_entity(services_info, serviceID, entityTypeNames[i]), services_info)) { changed |= (1 << i); } } return (changed); } static CFArrayRef service_order_get(CFDictionaryRef services_info) { CFArrayRef order = NULL; CFNumberRef ppp_override = NULL; int ppp_val = TRUE; CFDictionaryRef ipv4_dict = NULL; ipv4_dict = my_CFDictionaryGetDictionary(services_info, S_setup_global_ipv4); if (ipv4_dict != NULL) { order = CFDictionaryGetValue(ipv4_dict, kSCPropNetServiceOrder); order = isA_CFArray(order); /* get ppp override primary */ ppp_override = CFDictionaryGetValue(ipv4_dict, kSCPropNetPPPOverridePrimary); ppp_override = isA_CFNumber(ppp_override); if (ppp_override != NULL) { CFNumberGetValue(ppp_override, kCFNumberIntType, &ppp_val); } S_ppp_override_primary = (ppp_val != 0) ? TRUE : FALSE; } else { S_ppp_override_primary = TRUE; } return (order); } static boolean_t set_new_primary(CFStringRef * primary_p, CFStringRef new_primary, const char * entity) { boolean_t changed = FALSE; CFStringRef primary = *primary_p; if (new_primary != NULL) { if (primary != NULL && CFEqual(new_primary, primary)) { SCLog(S_IPMonitor_debug, LOG_INFO, CFSTR("IPMonitor: %@ is still primary %s"), new_primary, entity); } else { my_CFRelease(primary_p); *primary_p = CFRetain(new_primary); SCLog(S_IPMonitor_debug, LOG_INFO, CFSTR("IPMonitor: %@ is the new primary %s"), new_primary, entity); changed = TRUE; } } else if (primary != NULL) { SCLog(S_IPMonitor_debug, LOG_INFO, CFSTR("IPMonitor: %@ is no longer primary %s"), primary, entity); my_CFRelease(primary_p); changed = TRUE; } return (changed); } static unsigned int rank_service_entity(CFArrayRef order, CFStringRef primary, CFStringRef proto_key, CFStringRef entity) { CFDictionaryRef dict; dict = service_dict_get(primary, entity); if (dict == NULL) { return (UINT_MAX); } return (get_service_rank(proto_key, order, primary)); } static void IPMonitorNotify(SCDynamicStoreRef session, CFArrayRef changed_keys, void * not_used) { CFIndex count; boolean_t dnsinfo_changed = FALSE; boolean_t global_ipv4_changed = FALSE; boolean_t global_ipv6_changed = FALSE; keyChangeList keys; int i; CFIndex n; CFArrayRef service_order; CFMutableArrayRef service_changes = NULL; CFDictionaryRef services_info = NULL; count = CFArrayGetCount(changed_keys); if (count == 0) { return; } SCLog(S_IPMonitor_debug, LOG_INFO, CFSTR("IPMonitor: changes %@ (%d)"), changed_keys, count); keyChangeListInit(&keys); service_changes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); for (i = 0; i < count; i++) { CFStringRef change = CFArrayGetValueAtIndex(changed_keys, i); if (CFEqual(change, S_setup_global_ipv4)) { global_ipv4_changed = TRUE; global_ipv6_changed = TRUE; } else if (CFEqual(change, S_setup_global_netinfo)) { if (S_primary_ipv4 != NULL) { my_CFArrayAppendUniqueValue(service_changes, S_primary_ipv4); } } else if (CFEqual(change, S_setup_global_proxies)) { if (S_primary_proxies != NULL) { my_CFArrayAppendUniqueValue(service_changes, S_primary_proxies); } } else if (CFStringHasPrefix(change, S_state_service_prefix)) { CFStringRef serviceID = parse_component(change, S_state_service_prefix); if (serviceID) { my_CFArrayAppendUniqueValue(service_changes, serviceID); CFRelease(serviceID); } } else if (CFStringHasPrefix(change, S_setup_service_prefix)) { CFStringRef serviceID = parse_component(change, S_setup_service_prefix); if (serviceID) { my_CFArrayAppendUniqueValue(service_changes, serviceID); CFRelease(serviceID); } } } /* grab a snapshot of everything we need */ services_info = services_info_copy(session, service_changes); service_order = service_order_get(services_info); if (service_order != NULL) { SCLog(S_IPMonitor_debug, LOG_INFO, CFSTR("IPMonitor: service_order %@ "), service_order); } n = CFArrayGetCount(service_changes); for (i = 0; i < n; i++) { uint32_t changes; CFStringRef serviceID; Boolean wasSupplemental; serviceID = CFArrayGetValueAtIndex(service_changes, i); wasSupplemental = dns_has_supplemental(serviceID); changes = service_changed(services_info, serviceID); if (S_primary_ipv4 != NULL && CFEqual(S_primary_ipv4, serviceID)) { if ((changes & (1 << kEntityTypeIPv4)) != 0) { update_ipv4(session, services_info, serviceID, &keys); global_ipv4_changed = TRUE; } if ((changes & (1 << kEntityTypeNetInfo)) != 0) { update_netinfo(session, services_info, serviceID, &keys); } } else if ((changes & (1 << kEntityTypeIPv4)) != 0) { global_ipv4_changed = TRUE; } if ((changes & (1 << kEntityTypeIPv6)) != 0) { if (S_primary_ipv6 != NULL && CFEqual(S_primary_ipv6, serviceID)) { update_ipv6(session, services_info, serviceID, &keys); } global_ipv6_changed = TRUE; } if ((changes & (1 << kEntityTypeDNS)) != 0) { if (S_primary_dns != NULL && CFEqual(S_primary_dns, serviceID)) { update_dns(session, services_info, serviceID, &keys); dnsinfo_changed = TRUE; } else if (wasSupplemental || dns_has_supplemental(serviceID)) { dnsinfo_changed = TRUE; } } if ((changes & (1 << kEntityTypeProxies)) != 0) { if (S_primary_proxies != NULL && CFEqual(S_primary_proxies, serviceID)) { update_proxies(session, services_info, serviceID, &keys); } } } if (global_ipv4_changed) { CFStringRef new_primary; SCLog(S_IPMonitor_debug, LOG_INFO, CFSTR("IPMonitor: IPv4 service election")); new_primary = elect_new_primary(service_order, kSCEntNetIPv4, kSCPropNetIPv4Router); if (set_new_primary(&S_primary_ipv4, new_primary, "IPv4")) { update_ipv4(session, services_info, S_primary_ipv4, &keys); update_netinfo(session, services_info, S_primary_ipv4, &keys); } my_CFRelease(&new_primary); } if (global_ipv6_changed) { CFStringRef new_primary; SCLog(S_IPMonitor_debug, LOG_INFO, CFSTR("IPMonitor: IPv6 service election")); new_primary = elect_new_primary(service_order, kSCEntNetIPv6, kSCPropNetIPv6Router); if (set_new_primary(&S_primary_ipv6, new_primary, "IPv6")) { update_ipv6(session, services_info, S_primary_ipv6, &keys); } my_CFRelease(&new_primary); } if (global_ipv4_changed || global_ipv6_changed) { CFStringRef new_primary_dns; CFStringRef new_primary_proxies; if (S_primary_ipv4 != NULL && S_primary_ipv6 != NULL) { /* decide between IPv4 and IPv6 */ if (rank_service_entity(service_order, S_primary_ipv4, kSCEntNetIPv4, kSCEntNetDNS) <= rank_service_entity(service_order, S_primary_ipv6, kSCEntNetIPv6, kSCEntNetDNS)) { new_primary_dns = S_primary_ipv4; } else { new_primary_dns = S_primary_ipv6; } if (rank_service_entity(service_order, S_primary_ipv4, kSCEntNetIPv4, kSCEntNetProxies) <= rank_service_entity(service_order, S_primary_ipv6, kSCEntNetIPv6, kSCEntNetProxies)) { new_primary_proxies = S_primary_ipv4; } else { new_primary_proxies = S_primary_ipv6; } } else if (S_primary_ipv6 != NULL) { new_primary_dns = new_primary_proxies = S_primary_ipv6; } else if (S_primary_ipv4 != NULL) { new_primary_dns = new_primary_proxies = S_primary_ipv4; } else { new_primary_dns = new_primary_proxies = NULL; } if (set_new_primary(&S_primary_dns, new_primary_dns, "DNS")) { update_dns(session, services_info, S_primary_dns, &keys); dnsinfo_changed = TRUE; } if (set_new_primary(&S_primary_proxies, new_primary_proxies, "Proxies")) { update_proxies(session, services_info, S_primary_proxies, &keys); } } if (dnsinfo_changed) { update_dnsinfo(S_primary_dns, service_order, &keys); } my_CFRelease(&service_changes); my_CFRelease(&services_info); keyChangeListApplyToStore(&keys, session); keyChangeListFree(&keys); return; } static void initEntityNames(void) { entityTypeNames[0] = kSCEntNetIPv4; /* 0 */ entityTypeNames[1] = kSCEntNetIPv6; /* 1 */ entityTypeNames[2] = kSCEntNetDNS; /* 2 */ entityTypeNames[3] = kSCEntNetNetInfo; /* 3 */ entityTypeNames[4] = kSCEntNetProxies; /* 4 */ return; } static void ip_plugin_init() { int i; CFStringRef key; CFMutableArrayRef keys = NULL; CFMutableArrayRef patterns = NULL; CFRunLoopSourceRef rls = NULL; SCDynamicStoreRef session = NULL; initEntityNames(); if (S_netboot_root() != 0) { S_netboot = TRUE; } session = SCDynamicStoreCreate(NULL, CFSTR("IPMonitor"), IPMonitorNotify, NULL); if (session == NULL) { SCLog(TRUE, LOG_ERR, CFSTR("IPMonitor ip_plugin_init SCDynamicStoreCreate failed: %s"), SCErrorString(SCError())); return; } S_state_global_ipv4 = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4); S_state_global_ipv6 = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetIPv6); S_state_global_dns = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetDNS); S_state_global_netinfo = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetNetInfo); S_state_global_proxies = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetProxies); S_setup_global_ipv4 = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainSetup, kSCEntNetIPv4); S_setup_global_netinfo = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainSetup, kSCEntNetNetInfo); S_setup_global_proxies = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainSetup, kSCEntNetProxies); S_state_service_prefix = SCDynamicStoreKeyCreate(NULL, CFSTR("%@/%@/%@/"), kSCDynamicStoreDomainState, kSCCompNetwork, kSCCompService); S_setup_service_prefix = SCDynamicStoreKeyCreate(NULL, CFSTR("%@/%@/%@/"), kSCDynamicStoreDomainSetup, kSCCompNetwork, kSCCompService); S_service_state_dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); key = CFStringCreateWithCString(NULL, dns_configuration_notify_key(), kCFStringEncodingASCII); S_notify_dnsinfo = CFStringCreateWithFormat(NULL, NULL, CFSTR("Notify:%@"), key); CFRelease(key); keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); /* register for State: and Setup: per-service notifications */ for (i = 0; i < ENTITY_TYPES_COUNT; i++) { key = state_service_key(kSCCompAnyRegex, entityTypeNames[i]); CFArrayAppendValue(patterns, key); CFRelease(key); key = setup_service_key(kSCCompAnyRegex, entityTypeNames[i]); CFArrayAppendValue(patterns, key); CFRelease(key); } /* add notifier for setup global netinfo */ CFArrayAppendValue(keys, S_setup_global_netinfo); /* add notifier for ServiceOrder/PPPOverridePrimary changes for IPv4 */ key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainSetup, kSCEntNetIPv4); CFArrayAppendValue(keys, key); CFRelease(key); if (!SCDynamicStoreSetNotificationKeys(session, keys, patterns)) { SCLog(TRUE, LOG_ERR, CFSTR("IPMonitor ip_plugin_init " "SCDynamicStoreSetNotificationKeys failed: %s"), SCErrorString(SCError())); goto done; } rls = SCDynamicStoreCreateRunLoopSource(NULL, session, 0); if (rls == NULL) { SCLog(TRUE, LOG_ERR, CFSTR("IPMonitor ip_plugin_init " "SCDynamicStoreCreateRunLoopSource failed: %s"), SCErrorString(SCError())); goto done; } CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); CFRelease(rls); /* initialize dns configuration */ dns_configuration_set(NULL, NULL, NULL); empty_dns(); (void)SCDynamicStoreRemoveValue(session, S_state_global_dns); /* initialize netinfo state */ empty_netinfo(session); (void)SCDynamicStoreRemoveValue(session, S_state_global_netinfo); done: my_CFRelease(&keys); my_CFRelease(&patterns); my_CFRelease(&session); return; } __private_extern__ void prime_IPMonitor() { /* initialize multicast route */ set_ipv4_router(NULL, NULL, FALSE); } __private_extern__ void load_IPMonitor(CFBundleRef bundle, Boolean bundleVerbose) { if (bundleVerbose) { S_IPMonitor_debug = 1; } dns_configuration_init(bundle); ip_plugin_init(); load_hostname(S_IPMonitor_debug); return; } #ifdef MAIN #undef MAIN #include "dns-configuration.c" #include "set-hostname.c" int main(int argc, char **argv) { _sc_log = FALSE; _sc_verbose = (argc > 1) ? TRUE : FALSE; load_IPMonitor(CFBundleGetMainBundle(), (argc > 1) ? TRUE : FALSE); prime_IPMonitor(); CFRunLoopRun(); /* not reached */ exit(0); return 0; } #endif