#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/route.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/sysctl.h>
#include <limits.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCValidation.h>
#include <SystemConfiguration/SCPrivate.h>
#include <dnsinfo.h>
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]
static boolean_t S_IPMonitor_debug = FALSE;
static boolean_t S_netboot = FALSE;
static CFStringRef S_notify_dnsinfo = NULL;
static CFMutableDictionaryRef S_service_state_dict = NULL;
static boolean_t S_ppp_override_primary = TRUE;
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
#endif KERN_NETBOOT
#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,
get_ipv6_changes,
get_dns_changes,
get_netinfo_changes,
get_proxies_changes,
};
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)));
}
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);
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;
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("<none>"),
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));
}
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);
}
}
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) {
CFDictionarySetValue(dict, kRouterIsDirect, kCFBooleanTrue);
}
else if (valid_mask
&& subnet_addr(addr, mask).s_addr
!= subnet_addr(router_ip, mask).s_addr) {
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;
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) {
goto done;
}
if (service_dict_get(serviceID, kSCEntNetIPv4) == NULL
&& service_dict_get(serviceID, kSCEntNetIPv6) == NULL) {
goto done;
}
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;
}
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) {
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) {
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 {
len -= sizeof(rtmsg.link);
}
rtmsg.hdr.rtm_msglen = len;
if (write(sockfd, &rtmsg, len) < 0) {
if ((cmd == RTM_ADD) && (errno == EEXIST)) {
}
else if ((cmd == RTM_DELETE) && (errno == ESRCH)) {
}
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);
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) {
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 {
len -= sizeof(rtmsg.link);
}
rtmsg.hdr.rtm_msglen = len;
if (write(sockfd, &rtmsg, len) < 0) {
if ((cmd == RTM_ADD) && (errno == EEXIST)) {
}
else if ((cmd == RTM_DELETE) && (errno == ESRCH)) {
}
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;
}
(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)
{
(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");
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) {
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);
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) {
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);
{
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) {
dns_configuration_set(NULL, NULL, NULL);
} else {
CFDictionaryRef service_dict;
service_dict = CFDictionaryGetValue(S_service_state_dict, primary);
if (service_dict != NULL) {
dict = CFDictionaryGetValue(service_dict, kSCEntNetDNS);
}
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"))) {
return (0);
}
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);
}
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;
CFStringRef router_key;
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) {
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);
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);
}
}
}
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) {
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;
entityTypeNames[1] = kSCEntNetIPv6;
entityTypeNames[2] = kSCEntNetDNS;
entityTypeNames[3] = kSCEntNetNetInfo;
entityTypeNames[4] = kSCEntNetProxies;
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);
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);
}
CFArrayAppendValue(keys, S_setup_global_netinfo);
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);
dns_configuration_set(NULL, NULL, NULL);
empty_dns();
(void)SCDynamicStoreRemoveValue(session, S_state_global_dns);
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()
{
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();
exit(0);
return 0;
}
#endif