#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/fcntl.h>
#include <sys/ioctl.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 <netinet/icmp6.h>
#include <netinet6/in6_var.h>
#include <netinet6/nd6.h>
#include <nw/sa_compare.h>
#include <arpa/inet.h>
#include <sys/sysctl.h>
#include <limits.h>
#include <notify.h>
#include <mach/mach_time.h>
#include <dispatch/dispatch.h>
#include <CommonCrypto/CommonDigest.h>
#include "ip_plugin.h"
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCDynamicStoreCopyDHCPInfo.h>
#include <SystemConfiguration/SCValidation.h>
#include <SystemConfiguration/scprefs_observer.h>
#include <SystemConfiguration/SCPrivate.h>
#include "SCNetworkReachabilityInternal.h"
#include "SCNetworkSignaturePrivate.h"
#include <dnsinfo.h>
#include "dnsinfo_server.h"
#include <ppp/PPPControllerPriv.h>
#include <dns_sd.h>
#include <dns_sd_private.h>
#include <network_information.h>
#include "network_state_information_priv.h"
#include "network_state_information_logging.h"
#include "network_information_server.h"
#include <ppp/ppp_msg.h>
#if !TARGET_OS_SIMULATOR && !TEST_IPV4_ROUTELIST && !TEST_IPV6_ROUTELIST
#include "set-hostname.h"
#include "nat64-configuration.h"
#include "agent-monitor.h"
#endif
#include "dns-configuration.h"
#include "proxy-configuration.h"
#if !TARGET_OS_IPHONE
#include "smb-configuration.h"
#endif
#define kLoopbackInterface "lo0"
#define EROUTENOTAPPLIED 1001
typedef CF_ENUM(uint8_t, ProtocolFlags) {
kProtocolFlagsNone = 0x0,
kProtocolFlagsIPv4 = 0x1,
kProtocolFlagsIPv6 = 0x2
};
enum {
kDebugFlag1 = 0x00000001,
kDebugFlag2 = 0x00000002,
kDebugFlag4 = 0x00000004,
kDebugFlag8 = 0x00000008,
kDebugFlagDefault = kDebugFlag1,
kDebugFlagAll = 0xffffffff
};
typedef unsigned int IFIndex;
static dispatch_queue_t __network_change_queue(void);
#pragma mark -
#pragma mark Logging
__private_extern__ os_log_t
__log_IPMonitor(void)
{
static os_log_t log = NULL;
if (log == NULL) {
log = os_log_create("com.apple.SystemConfiguration", "IPMonitor");
}
return log;
}
#pragma mark -
#pragma mark interface index
#ifndef TEST_ROUTELIST
#define ROUTELIST_DEBUG(flag, fmt, ...)
static struct if_nameindex * S_if_nameindex_cache;
static dispatch_queue_t
__my_if_nametoindex_queue()
{
static dispatch_once_t once;
static dispatch_queue_t q;
dispatch_once(&once, ^{
q = dispatch_queue_create("my_if_nametoindex queue", NULL);
});
return q;
}
__private_extern__ IFIndex
my_if_nametoindex(const char * ifname)
{
__block IFIndex idx = 0;
dispatch_sync(__my_if_nametoindex_queue(), ^{
struct if_nameindex * scan;
if (S_if_nameindex_cache == NULL) {
idx = if_nametoindex(ifname);
return;
}
for (scan = S_if_nameindex_cache;
scan->if_index != 0 && scan->if_name != NULL;
scan++) {
if (strcmp(scan->if_name, ifname) == 0) {
idx = scan->if_index;
break;
}
}
});
return (idx);
}
__private_extern__ const char *
my_if_indextoname(IFIndex idx, char if_name[IFNAMSIZ])
{
__block const char * name = NULL;
dispatch_sync(__my_if_nametoindex_queue(), ^{
struct if_nameindex * scan;
if (S_if_nameindex_cache == NULL) {
name = if_indextoname(idx, if_name);
return;
}
for (scan = S_if_nameindex_cache;
scan->if_index != 0 && scan->if_name != NULL;
scan++) {
if (scan->if_index == idx) {
name = if_name;
strlcpy(if_name, scan->if_name, IFNAMSIZ);
break;
}
}
});
return (name);
}
static void
my_if_freenameindex(void)
{
dispatch_sync(__my_if_nametoindex_queue(), ^{
if (S_if_nameindex_cache != NULL) {
if_freenameindex(S_if_nameindex_cache);
S_if_nameindex_cache = NULL;
}
});
return;
}
static void
my_if_nameindex(void)
{
my_if_freenameindex();
dispatch_sync(__my_if_nametoindex_queue(), ^{
S_if_nameindex_cache = if_nameindex();
});
return;
}
#else
#define ROUTELIST_DEBUG(flags, format, ...) { if (((S_IPMonitor_debug & (flags)) != 0)) printf((format), ## __VA_ARGS__ ); }
static const char * * list;
static int list_count;
static int list_size;
__private_extern__ IFIndex
my_if_nametoindex(const char * ifname)
{
IFIndex ret;
if (list == NULL) {
list_size = 4;
list_count = 2;
list = (const char * *)malloc(sizeof(*list) * list_size);
list[0] = strdup("");
list[1] = strdup(kLoopbackInterface);
}
else {
int i;
for (i = 1; i < list_count; i++) {
if (strcmp(list[i], ifname) == 0) {
ret = i;
goto done;
}
}
}
if (list_count == list_size) {
list_size += 2;
list = (const char * *)realloc(list, sizeof(*list) * list_size);
}
list[list_count] = strdup(ifname);
ret = list_count;
list_count++;
done:
return (ret);
}
__private_extern__ const char *
my_if_indextoname(IFIndex idx, char if_name[IFNAMSIZ])
{
const char * name = NULL;
if (idx < list_count) {
name = if_name;
strlcpy(if_name, list[idx], IFNAMSIZ);
}
return (name);
}
static void
my_if_nameindex(void)
{
}
static void
my_if_freenameindex(void)
{
}
#endif
static const char *
my_if_indextoname2(IFIndex ifindex, char ifname[IFNAMSIZ])
{
if (ifindex == 0) {
return (NULL);
}
if (my_if_indextoname(ifindex, ifname) == NULL) {
snprintf(ifname, IFNAMSIZ, "[%d]", ifindex);
}
return (ifname);
}
static IFIndex
lo0_ifindex(void)
{
static IFIndex idx;
if (idx == 0) {
idx = my_if_nametoindex(kLoopbackInterface);
}
return (idx);
}
#pragma mark -
#define kServiceOptionRankAssertion CFSTR("RankAssertion")
#define kIPIsCoupled CFSTR("IPIsCoupled")
#define PPP_PREFIX "ppp"
#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 S_bundle_logging_verbose;
typedef CF_ENUM(uint16_t, RouteFlags) {
kRouteFlagsIsScoped = 0x0001,
kRouteFlagsHasGateway = 0x0002,
kRouteFlagsIsHost = 0x0004,
kRouteFlagsIsNULL = 0x0008,
kRouteFlagsKernelManaged = 0x0010
};
typedef CF_ENUM(uint16_t, ControlFlags) {
kControlFlagsProcessed = 0x0001,
kControlFlagsAdded = 0x0002,
kControlFlagsForce = 0x0004,
};
#if !TARGET_OS_SIMULATOR
static inline ControlFlags
ControlFlagsPreserve(ControlFlags flags)
{
return (flags & (kControlFlagsProcessed | kControlFlagsAdded));
}
#endif
#define ROUTE_COMMON \
int prefix_length; \
IFIndex ifindex; \
IFIndex exclude_ifindex; \
Rank rank; \
RouteFlags flags; \
ControlFlags control_flags;
typedef struct {
ROUTE_COMMON
} Route, * RouteRef;
#define PREFIX_LENGTH_IN_CLASSC 24
#define PREFIX_LENGTH_IN_CLASSD 4
typedef struct {
ROUTE_COMMON
struct in_addr dest;
struct in_addr mask;
struct in_addr gateway;
struct in_addr ifa;
} IPv4Route, * IPv4RouteRef;
typedef struct {
ROUTE_COMMON
struct in6_addr dest;
struct in6_addr gateway;
struct in6_addr ifa;
} IPv6Route, * IPv6RouteRef;
typedef CF_ENUM(uint16_t, RouteListFlags) {
kRouteListFlagsExcludeNWI = 0x0001,
kRouteListFlagsHasDefault = 0x0002,
kRouteListFlagsScopedOnly = 0x0004
};
#define ROUTELIST_COMMON \
int count; \
int size; \
RouteListFlags flags;
typedef struct {
ROUTELIST_COMMON
} RouteListCommon, * RouteListRef;
typedef struct {
ROUTELIST_COMMON
IPv4Route list[1];
} IPv4RouteList, * IPv4RouteListRef;
typedef struct {
ROUTELIST_COMMON
IPv6Route list[1];
} IPv6RouteList, * IPv6RouteListRef;
typedef union {
void * ptr;
RouteListRef common;
IPv4RouteListRef v4;
IPv6RouteListRef v6;
} RouteListUnion;
typedef enum {
kRouteCommandAdd,
kRouteCommandRemove
} RouteCommand;
typedef union {
struct in_addr v4;
struct in6_addr v6;
} in_addr;
typedef union {
struct sockaddr_in v4;
struct sockaddr_in6 v6;
} in_sockaddr;
typedef struct Candidate {
CFStringRef serviceID;
CFStringRef if_name;
Rank rank;
boolean_t ip_is_coupled;
boolean_t ineligible;
SCNetworkReachabilityFlags reachability_flags;
in_addr addr;
in_sockaddr vpn_server_addr;
CFStringRef signature;
} Candidate, * CandidateRef;
typedef struct ElectionResults {
int af;
int count;
int size;
Candidate candidates[1];
} ElectionResults, * ElectionResultsRef;
static __inline__ size_t
ElectionResultsComputeSize(unsigned int n)
{
return (offsetof(ElectionResults, candidates[n]));
}
static __inline__ Rank
RankMake(uint32_t service_index, Rank primary_rank)
{
return (RANK_INDEX_MASK(service_index) | RANK_ASSERTION_MASK(primary_rank));
}
static Rank
InterfaceRankGetRankAssertion(CFNumberRef rank_cf, Boolean * ret_is_set)
{
SCNetworkServicePrimaryRank if_rank;
Boolean is_set = FALSE;
Rank rank = kRankAssertionDefault;
if (rank_cf != NULL
&& CFNumberGetValue(rank_cf, kCFNumberSInt32Type, &if_rank)
&& if_rank != kSCNetworkServicePrimaryRankDefault) {
if (if_rank == kSCNetworkServicePrimaryRankFirst) {
rank = kRankAssertionFirst;
}
else {
rank = RANK_ASSERTION_MAKE(if_rank);
}
is_set = TRUE;
}
if (ret_is_set != NULL) {
*ret_is_set = is_set;
}
return (rank);
}
static Rank
PrimaryRankGetRankAssertion(CFStringRef rank_str, Boolean * is_set)
{
struct {
const CFStringRef * name;
Rank rank_assertion;
} values[] = {
{ &kSCValNetServicePrimaryRankFirst, kRankAssertionFirst },
{ &kSCValNetServicePrimaryRankLast, kRankAssertionLast },
{ &kSCValNetServicePrimaryRankNever, kRankAssertionNever },
{ &kSCValNetServicePrimaryRankScoped, kRankAssertionScoped }
};
if (rank_str != NULL) {
for (size_t i = 0; i < countof(values); i++) {
if (CFEqual(rank_str, *(values[i].name))) {
if (is_set != NULL) {
*is_set = TRUE;
}
return (values[i].rank_assertion);
}
}
}
if (is_set != NULL) {
*is_set = FALSE;
}
return (kRankAssertionDefault);
}
static SCDynamicStoreRef S_session = NULL;
static uint32_t S_IPMonitor_debug = 0;
static Boolean S_IPMonitor_verbose = FALSE;
static boolean_t S_netboot = FALSE;
static CFMutableDictionaryRef S_service_state_dict;
static CFMutableDictionaryRef S_ipv4_service_rank_dict;
static CFMutableDictionaryRef S_ipv6_service_rank_dict;
static CFDictionaryRef S_if_rank_dict;
static boolean_t S_ppp_override_primary = FALSE;
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 ElectionResultsRef S_ipv4_results;
static ElectionResultsRef S_ipv6_results;
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_proxies = NULL;
static CFStringRef S_state_service_prefix = NULL;
static CFStringRef S_setup_global_ipv4 = NULL;
static CFStringRef S_setup_service_prefix = NULL;
static CFStringRef S_interface_delegation_prefix = NULL;
static CFStringRef S_multicast_resolvers = NULL;
static CFStringRef S_private_resolvers = NULL;
#if !TARGET_OS_SIMULATOR
static IPv4RouteListRef S_ipv4_routelist = NULL;
static IPv6RouteListRef S_ipv6_routelist = NULL;
#endif
static boolean_t S_append_state = FALSE;
static CFDictionaryRef S_dns_dict = NULL;
static Boolean S_dnsinfo_synced = TRUE;
static nwi_state_t S_nwi_state = NULL;
static Boolean S_nwi_synced = TRUE;
static CFDictionaryRef S_proxies_dict = NULL;
static uint32_t S_network_change_needed = 0;
#define NETWORK_CHANGE_NET 1<<0
#define NETWORK_CHANGE_DNS 1<<1
#define NETWORK_CHANGE_PROXY 1<<2
#if !TARGET_OS_IPHONE
#define NETWORK_CHANGE_SMB 1<<3
#endif
#define NETWORK_CHANGE_NAT64 1<<4
static struct timeval S_network_change_start;
static Boolean S_network_change_timeout = FALSE;
static dispatch_source_t S_network_change_timer = NULL;
#if !TARGET_OS_IPHONE
static CFStringRef S_primary_smb = NULL;
static CFStringRef S_state_global_smb = NULL;
static CFDictionaryRef S_smb_dict = NULL;
#endif
#if !TARGET_OS_IPHONE
#define VAR_RUN_RESOLV_CONF "/var/run/resolv.conf"
#endif
#ifndef KERN_NETBOOT
#define KERN_NETBOOT 40
#endif
typedef enum {
kEntityTypeIPv4 = 0,
kEntityTypeIPv6,
kEntityTypeDNS,
kEntityTypeProxies,
#if !TARGET_OS_IPHONE
kEntityTypeSMB,
#endif
ENTITY_TYPES_COUNT,
kEntityTypeTransientStatus,
kEntityTypeServiceOptions = 31
} EntityType;
static const CFStringRef *entityTypeNames[ENTITY_TYPES_COUNT] = {
&kSCEntNetIPv4,
&kSCEntNetIPv6,
&kSCEntNetDNS,
&kSCEntNetProxies,
#if !TARGET_OS_IPHONE
&kSCEntNetSMB,
#endif
};
static Boolean
S_dict_get_boolean(CFDictionaryRef dict, CFStringRef key, Boolean def_value);
static __inline__ char
ipvx_char(int af)
{
return ((af == AF_INET) ? '4' : '6');
}
static __inline__ char
ipvx_other_char(int af)
{
return ((af == AF_INET) ? '6' : '4');
}
#define kIPDictRoutes CFSTR("Routes")
#define kIPDictService CFSTR("Service")
static CFDictionaryRef
ipdict_create(CFDictionaryRef dict, CFDataRef routes_data)
{
CFStringRef keys[2];
CFTypeRef values[2];
keys[0] = kIPDictService;
values[0] = dict;
keys[1] = kIPDictRoutes;
values[1] = routes_data;
return (CFDictionaryCreate(NULL,
(const void * *)keys,
values,
countof(keys),
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
}
static void *
ipdict_get_routelist(CFDictionaryRef dict)
{
void * routes_list = NULL;
if (dict != NULL) {
CFDataRef routes;
routes = CFDictionaryGetValue(dict, kIPDictRoutes);
if (routes != NULL) {
routes_list = (void *)CFDataGetBytePtr(routes);
}
}
return (routes_list);
}
static CFDictionaryRef
ipdict_get_service(CFDictionaryRef dict)
{
CFDictionaryRef ip_dict = NULL;
if (dict != NULL) {
ip_dict = CFDictionaryGetValue(dict, kIPDictService);
}
return (ip_dict);
}
static CFStringRef
ipdict_get_ifname(CFDictionaryRef dict)
{
CFStringRef ifname = NULL;
CFDictionaryRef ip_dict;
ip_dict = ipdict_get_service(dict);
if (ip_dict != NULL) {
ifname = CFDictionaryGetValue(ip_dict, kSCPropInterfaceName);
}
return (ifname);
}
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_proxies_changes;
#if !TARGET_OS_IPHONE
static GetEntityChangesFunc get_smb_changes;
#endif
static __inline__ void
my_CFRelease(void * t)
{
void * * obj = (void * *)t;
if (obj && *obj) {
CFRelease(*obj);
*obj = NULL;
}
return;
}
static void
my_CFArrayAppendUniqueValue(CFMutableArrayRef arr, CFTypeRef new);
static void
my_CFArrayRemoveValue(CFMutableArrayRef arr, CFStringRef key);
static const GetEntityChangesFuncRef entityChangeFunc[ENTITY_TYPES_COUNT] = {
get_ipv4_changes,
get_ipv6_changes,
get_dns_changes,
get_proxies_changes,
#if !TARGET_OS_IPHONE
get_smb_changes,
#endif
};
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 Boolean
keyChangeListActive(keyChangeListRef keys)
{
return ((CFDictionaryGetCount(keys->set) > 0) ||
(CFArrayGetCount(keys->remove) > 0) ||
(CFArrayGetCount(keys->notify) > 0));
}
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 & kDebugFlag1) != 0) {
if (set != NULL) {
my_log(LOG_DEBUG, "Setting:\n%@", set);
}
if (remove != NULL) {
my_log(LOG_DEBUG, "Removing:\n%@", remove);
}
if (notify != NULL) {
my_log(LOG_DEBUG, "Notifying:\n%@", notify);
}
}
(void)SCDynamicStoreSetMultiple(session, set, remove, notify);
return;
}
static boolean_t
S_is_network_boot()
{
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 int rtm_seq = 0;
#if !TARGET_OS_SIMULATOR
static int
open_routing_socket(void)
{
int sockfd;
if ((sockfd = socket(PF_ROUTE, SOCK_RAW, PF_ROUTE)) == -1) {
my_log(LOG_ERR, "socket() failed: %s", strerror(errno));
}
return (sockfd);
}
static __inline__ int
inet6_dgram_socket(void)
{
int sockfd;
sockfd = socket(AF_INET6, SOCK_DGRAM, 0);
if (sockfd == -1) {
my_log(LOG_ERR, "socket() failed: %s", strerror(errno));
}
return sockfd;
}
static int
siocdradd_in6(int s, int if_index, const struct in6_addr * addr, u_char flags)
{
struct in6_defrouter dr;
struct sockaddr_in6 * sin6;
memset(&dr, 0, sizeof(dr));
sin6 = &dr.rtaddr;
sin6->sin6_len = sizeof(struct sockaddr_in6);
sin6->sin6_family = AF_INET6;
sin6->sin6_addr = *addr;
dr.flags = flags;
dr.if_index = if_index;
return (ioctl(s, SIOCDRADD_IN6, &dr));
}
static int
siocdrdel_in6(int s, int if_index, const struct in6_addr * addr)
{
struct in6_defrouter dr;
struct sockaddr_in6 * sin6;
memset(&dr, 0, sizeof(dr));
sin6 = &dr.rtaddr;
sin6->sin6_len = sizeof(struct sockaddr_in6);
sin6->sin6_family = AF_INET6;
sin6->sin6_addr = *addr;
dr.if_index = if_index;
return (ioctl(s, SIOCDRDEL_IN6, &dr));
}
#endif
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 CFArrayRef
my_CFArrayCreateCombinedArray(CFArrayRef array1, CFArrayRef array2)
{
CFMutableArrayRef combined;
combined = CFArrayCreateMutableCopy(NULL, 0, array1);
CFArrayAppendArray(combined,
array2,
CFRangeMake(0, CFArrayGetCount(array2)));
return (combined);
}
static CFDictionaryRef
my_CFDictionaryGetDictionary(CFDictionaryRef dict, CFStringRef key)
{
if (isA_CFDictionary(dict) == NULL) {
return (NULL);
}
return (isA_CFDictionary(CFDictionaryGetValue(dict, key)));
}
static CFArrayRef
my_CFDictionaryGetArray(CFDictionaryRef dict, CFStringRef key)
{
if (isA_CFDictionary(dict) == NULL) {
return (NULL);
}
return (isA_CFArray(CFDictionaryGetValue(dict, key)));
}
static void
my_CFSetAddValue(CFMutableSetRef * set_p, CFTypeRef value)
{
if (*set_p == NULL) {
*set_p = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
}
CFSetAddValue(*set_p, value);
}
static Boolean
my_CFSetContainsValue(CFSetRef set, CFTypeRef value)
{
if (set == NULL) {
return (FALSE);
}
return (CFSetContainsValue(set, value));
}
#if !TARGET_OS_SIMULATOR
static void
my_CFSetRemoveValue(CFMutableSetRef * set_p, CFTypeRef value)
{
if (*set_p == NULL) {
return;
}
CFSetRemoveValue(*set_p, value);
if (CFSetGetCount(*set_p) == 0) {
my_CFRelease(set_p);
}
}
typedef CF_ENUM(uint16_t, PLATDiscoveryOption) {
kPLATDiscoveryOptionStart,
kPLATDiscoveryOptionUpdate,
kPLATDiscoveryOptionCancel
};
static CFMutableSetRef S_nat64_cancel_prefix_requests;
static CFMutableSetRef S_nat64_prefix_updates;
static CFMutableSetRef S_nat64_prefix_requests;
static void
set_plat_discovery_locked(PLATDiscoveryOption option, CFStringRef interface)
{
switch (option) {
case kPLATDiscoveryOptionStart:
my_log(LOG_DEBUG, "NAT64 Start %@", interface);
my_CFSetAddValue(&S_nat64_prefix_requests, interface);
my_CFSetRemoveValue(&S_nat64_prefix_updates, interface);
my_CFSetRemoveValue(&S_nat64_cancel_prefix_requests, interface);
break;
case kPLATDiscoveryOptionUpdate:
my_log(LOG_DEBUG, "NAT64 Update %@", interface);
if (!my_CFSetContainsValue(S_nat64_prefix_requests, interface)) {
my_CFSetAddValue(&S_nat64_prefix_updates, interface);
}
my_CFSetRemoveValue(&S_nat64_cancel_prefix_requests, interface);
break;
case kPLATDiscoveryOptionCancel:
my_log(LOG_DEBUG, "NAT64 Cancel %@", interface);
my_CFSetRemoveValue(&S_nat64_prefix_requests, interface);
my_CFSetRemoveValue(&S_nat64_prefix_updates, interface);
my_CFSetAddValue(&S_nat64_cancel_prefix_requests, interface);
break;
default:
break;
}
}
static void
set_plat_discovery(PLATDiscoveryOption option, CFStringRef interface)
{
CFRetain(interface);
dispatch_async(__network_change_queue(), ^{
set_plat_discovery_locked(option, interface);
CFRelease(interface);
});
return;
}
#endif
static boolean_t
cfstring_to_ipvx(int family, CFStringRef str, void * addr, size_t 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:
memset(addr, 0, addr_size);
return (FALSE);
}
__private_extern__
boolean_t
cfstring_to_ip(CFStringRef str, struct in_addr * ip_p)
{
return (cfstring_to_ipvx(AF_INET, str, ip_p, sizeof(*ip_p)));
}
__private_extern__
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 boolean_t
cfnumber_to_int(CFNumberRef num, int * int_val)
{
if (isA_CFNumber(num) == NULL) {
return (FALSE);
}
return (CFNumberGetValue(num, kCFNumberIntType, int_val));
}
static CF_RETURNS_RETAINED CFStringRef
setup_service_key(CFStringRef serviceID, CFStringRef entity)
{
return (SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
serviceID,
entity));
}
static CF_RETURNS_RETAINED CFStringRef
state_service_key(CFStringRef serviceID, CFStringRef entity)
{
return (SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainState,
serviceID,
entity));
}
static CFStringRef
interface_entity_key_copy(CFStringRef ifname, CFStringRef entity)
{
return (SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
kSCDynamicStoreDomainState,
ifname,
entity));
}
static CFDictionaryRef
get_service_setup_entity(CFDictionaryRef services_info, CFStringRef serviceID,
CFStringRef entity)
{
CFStringRef setup_key;
CFDictionaryRef setup_dict;
setup_key = setup_service_key(serviceID, entity);
setup_dict = my_CFDictionaryGetDictionary(services_info, setup_key);
my_CFRelease(&setup_key);
return (setup_dict);
}
static CFDictionaryRef
get_service_state_entity(CFDictionaryRef services_info, CFStringRef serviceID,
CFStringRef entity)
{
CFStringRef state_key;
CFDictionaryRef state_dict;
state_key = state_service_key(serviceID, entity);
state_dict = my_CFDictionaryGetDictionary(services_info, state_key);
my_CFRelease(&state_key);
return (state_dict);
}
static boolean_t
dict_get_first_ip(CFDictionaryRef dict, CFStringRef prop, struct in_addr * ip_p)
{
CFArrayRef ip_list;
ip_list = CFDictionaryGetValue(dict, prop);
if (isA_CFArray(ip_list) != NULL
&& CFArrayGetCount(ip_list) > 0
&& cfstring_to_ip(CFArrayGetValueAtIndex(ip_list, 0), ip_p)) {
return (TRUE);
}
return (FALSE);
}
static boolean_t
dict_get_first_ipv6(CFDictionaryRef dict, CFStringRef prop,
struct in6_addr * ip_p)
{
CFArrayRef ip_list;
ip_list = CFDictionaryGetValue(dict, prop);
if (isA_CFArray(ip_list) != NULL
&& CFArrayGetCount(ip_list) > 0
&& cfstring_to_ip6(CFArrayGetValueAtIndex(ip_list, 0), ip_p)) {
return (TRUE);
}
return (FALSE);
}
static boolean_t
dict_get_first_int(CFDictionaryRef dict, CFStringRef prop,
int * val)
{
CFArrayRef list;
list = CFDictionaryGetValue(dict, prop);
if (isA_CFArray(list) != NULL
&& CFArrayGetCount(list) > 0
&& cfnumber_to_int(CFArrayGetValueAtIndex(list, 0), val)) {
return (TRUE);
}
return (FALSE);
}
static boolean_t
dict_get_ip(CFDictionaryRef dict, CFStringRef prop, struct in_addr * ip_p)
{
CFStringRef val;
val = CFDictionaryGetValue(dict, prop);
return (cfstring_to_ip(val, ip_p));
}
static boolean_t
dict_get_ipv6(CFDictionaryRef dict, CFStringRef prop, struct in6_addr * ip_p)
{
CFStringRef val;
val = CFDictionaryGetValue(dict, prop);
return (cfstring_to_ip6(val, ip_p));
}
static boolean_t
dict_get_int(CFDictionaryRef dict, CFStringRef prop, int * intval)
{
CFNumberRef val;
val = CFDictionaryGetValue(dict, prop);
return (cfnumber_to_int(val, intval));
}
static boolean_t
get_override_primary(CFDictionaryRef dict)
{
CFTypeRef override;
override = CFDictionaryGetValue(dict, kSCPropNetOverridePrimary);
if (isA_CFNumber(override) != NULL) {
int val = 0;
CFNumberGetValue((CFNumberRef)override, kCFNumberIntType, &val);
if (val != 0) {
return (TRUE);
}
}
else if (isA_CFBoolean(override) != NULL) {
if (CFBooleanGetValue(override)) {
return (TRUE);
}
}
return (FALSE);
}
typedef size_t
(*RouteListComputeSize)(CFIndex n);
typedef boolean_t
(*RouteIsEqual)(RouteRef a, RouteRef b);
typedef int
(*RouteApply)(RouteRef route, int cmd, int sockfd);
typedef const void *
(*RouteGateway)(RouteRef route);
typedef void
(*RouteSetGateway)(RouteRef route, const void * address);
typedef const void *
(*RouteDestination)(RouteRef route);
typedef boolean_t
(*RouteSameSubnet)(RouteRef route, const void * address);
typedef CFStringRef
(*RouteCopyDescription)(RouteRef route);
typedef void
(*RouteLog)(int priority, RouteRef route, const char * msg);
typedef struct {
RouteListComputeSize list_compute_size;
RouteIsEqual route_equal;
RouteApply route_apply;
RouteGateway route_gateway;
RouteSetGateway route_set_gateway;
RouteDestination route_destination;
RouteSameSubnet route_same_subnet;
RouteLog route_log;
RouteCopyDescription route_copy_description;
int element_size;
int address_size;
int all_bits_set;
} RouteListInfo;
typedef const RouteListInfo * RouteListInfoRef;
typedef struct {
RouteListInfoRef info;
RouteListRef old_routes;
RouteListRef new_routes;
int sockfd;
int depth;
} RouteListApplyContext, * RouteListApplyContextRef;
static int
RouteAddressCompare(RouteListInfoRef info,
const void * addr1,
const void * addr2)
{
return (memcmp(addr1, addr2, info->address_size));
}
static int
RouteCompare(RouteListInfoRef info,
RouteRef a, Rank a_rank,
RouteRef b, Rank b_rank, boolean_t * same_dest)
{
int cmp;
RouteDestination route_destination;
RouteCopyDescription route_copy_description;
*same_dest = FALSE;
route_destination = info->route_destination;
route_copy_description = info->route_copy_description;
cmp = RouteAddressCompare(info,
(*route_destination)(a),
(*route_destination)(b));
if (cmp == 0) {
cmp = a->prefix_length - b->prefix_length;
if (cmp == 0) {
int index_cmp = a->ifindex - b->ifindex;
if (index_cmp == 0) {
cmp = 0;
}
else if ((a->ifindex == 0 || b->ifindex == 0)
&& (a->flags & kRouteFlagsIsScoped) == 0
&& (b->flags & kRouteFlagsIsScoped) == 0) {
cmp = 0;
}
else {
*same_dest = TRUE;
cmp = RankCompare(a_rank, b_rank);
if (cmp == 0) {
cmp = index_cmp;
}
}
}
}
if ((S_IPMonitor_debug & kDebugFlag8) != 0) {
CFStringRef a_str;
CFStringRef b_str;
char ch;
if (cmp < 0) {
ch = '<';
}
else if (cmp == 0) {
ch = '=';
}
else {
ch = '>';
}
a_str = (*route_copy_description)(a);
b_str = (*route_copy_description)(b);
my_log(LOG_DEBUG, "%@ rank 0x%x %c %@ rank 0x%x",
a_str, a_rank, ch, b_str, b_rank);
CFRelease(a_str);
CFRelease(b_str);
}
return (cmp);
}
static RouteRef
RouteListGetRouteAtIndexSimple(RouteListInfoRef info, RouteListRef routes,
CFIndex where)
{
return ((void *)routes + (*info->list_compute_size)(where));
}
static RouteRef
RouteListGetRouteAtIndex(RouteListInfoRef info, RouteListRef routes,
CFIndex where)
{
if (routes->count == 0
|| where >= routes->count) {
return (NULL);
}
return (RouteListGetRouteAtIndexSimple(info, routes, where));
}
static RouteRef
RouteListGetFirstRoute(RouteListInfoRef info, RouteListRef routes)
{
return (RouteListGetRouteAtIndexSimple(info, routes, 0));
}
#if !TARGET_OS_SIMULATOR
static CFIndex
RouteListRouteIndex(RouteListInfoRef info, RouteListRef routes,
RouteRef route)
{
return (((void *)route
- (void *)RouteListGetFirstRoute(info, routes))
/ info->element_size);
}
#endif
static RouteRef
RouteGetNextRoute(RouteListInfoRef info, RouteRef route)
{
return ((RouteRef)(((void *)route) + info->element_size));
}
static RouteRef
RouteListAddRouteAtIndex(RouteListInfoRef info, RouteListRef routes,
RouteRef this_route, CFIndex where)
{
RouteRef insert_route;
if (where == kCFNotFound) {
insert_route
= RouteListGetRouteAtIndexSimple(info, routes, routes->count);
}
else {
insert_route = RouteListGetRouteAtIndexSimple(info, routes, where);
memcpy((void *)insert_route + info->element_size,
insert_route,
info->element_size * (routes->count - where));
}
memcpy(insert_route, this_route, info->element_size);
routes->count++;
return (insert_route);
}
static void
RouteListRemoveRouteAtIndex(RouteListInfoRef info, RouteListRef routes,
CFIndex where)
{
if (routes->count == 0
|| where >= routes->count) {
return;
}
routes->count--;
if (where == routes->count) {
}
else {
RouteRef remove_route;
remove_route = RouteListGetRouteAtIndexSimple(info, routes, where);
memcpy(remove_route,
(void *)remove_route + info->element_size,
info->element_size * (routes->count - where));
}
return;
}
typedef enum {
kScopeNone = 0,
kScopeThis = 1,
kScopeNext = 2
} Scope;
static RouteListRef
RouteListAddRoute(RouteListInfoRef info,
RouteListRef routes, int init_size,
RouteRef this_route, Rank this_rank,
boolean_t force)
{
CFIndex i;
RouteRef first_scan = NULL;
RouteFlags flags;
RouteRef scan;
Scope scope_which = kScopeNone;
CFIndex where = kCFNotFound;
if (routes == NULL) {
size_t alloc_size = (*info->list_compute_size)(init_size);
routes = (RouteListRef)malloc(alloc_size);
memset(routes, 0, alloc_size);
routes->size = init_size;
}
for (i = 0, scan = RouteListGetFirstRoute(info, routes);
i < routes->count;
i++, scan = RouteGetNextRoute(info, scan)) {
int cmp;
boolean_t same_dest;
cmp = RouteCompare(info, this_route, this_rank, scan, scan->rank,
&same_dest);
if (same_dest && (first_scan == NULL)) {
first_scan = scan;
}
if (cmp < 0) {
if (where == kCFNotFound) {
if (same_dest
&& (first_scan != NULL)
&& (first_scan->flags & kRouteFlagsIsScoped) == 0) {
if ((scan->flags & kRouteFlagsIsScoped) != 0) {
ROUTELIST_DEBUG(kDebugFlag8,
"Hit 1: set scope on self\n");
scope_which = kScopeThis;
}
else {
ROUTELIST_DEBUG(kDebugFlag8,
"Hit 2: set scope on next\n");
scope_which = kScopeNext;
}
}
where = i;
}
}
else if (cmp == 0) {
if (where != kCFNotFound
&& scan->ifindex == this_route->ifindex
&& scan->exclude_ifindex == 0
&& this_route->exclude_ifindex == 0) {
ROUTELIST_DEBUG(kDebugFlag8, "Hit 3: removing [%ld]\n", i);
RouteListRemoveRouteAtIndex(info, routes, i);
break;
}
if (this_route->exclude_ifindex == 0
&& (scan->exclude_ifindex != 0 || this_rank < scan->rank)) {
IFIndex ifindex = 0;
boolean_t is_scoped = FALSE;
if (scan->flags & kRouteFlagsIsScoped) {
is_scoped = TRUE;
}
if (this_rank < scan->rank) {
ROUTELIST_DEBUG(kDebugFlag8,
"Hit 4a: replacing [%ld]"
" rank 0x%x < 0x%x\n",
i, this_rank, scan->rank);
}
else {
ROUTELIST_DEBUG(kDebugFlag8,
"Hit 4b: replacing [%ld] excluded route\n",
i);
}
if (scan->ifindex != 0) {
ifindex = scan->ifindex;
}
else if (this_route->ifindex != 0) {
ifindex = this_route->ifindex;
}
memcpy(scan, this_route, info->element_size);
scan->rank = this_rank;
scan->ifindex = ifindex;
scan->exclude_ifindex = 0;
if (is_scoped) {
ROUTELIST_DEBUG(kDebugFlag8, "Hit 5: preserved scope\n");
scan->flags |= kRouteFlagsIsScoped;
}
if (force) {
scan->control_flags |= kControlFlagsForce;
}
}
goto done;
}
else {
if (same_dest) {
if (scope_which == kScopeNone) {
ROUTELIST_DEBUG(kDebugFlag8, "Hit 6: set scope on self\n");
scope_which = kScopeThis;
}
}
#ifdef TEST_ROUTELIST
else if (where != kCFNotFound) {
fprintf(stderr,
"Hit 7: moved past routes - can't happen\n");
exit(2);
break;
}
#endif
}
}
if (routes->size == routes->count) {
int how_many;
RouteListRef new_routes;
int old_size;
old_size = routes->size;
how_many = old_size * 2;
new_routes = (RouteListRef)
reallocf(routes, (*info->list_compute_size)(how_many));
if (new_routes == NULL) {
routes = NULL;
goto done;
}
ROUTELIST_DEBUG(kDebugFlag8, "increasing size from %d to %d\n",
old_size, how_many);
new_routes->size = how_many;
routes = new_routes;
}
this_route = RouteListAddRouteAtIndex(info, routes, this_route, where);
this_route->rank = this_rank;
if (force) {
this_route->control_flags |= kControlFlagsForce;
}
flags = 0;
if (RANK_ASSERTION_MASK(this_rank) == kRankAssertionNever) {
flags |= kRouteFlagsIsScoped;
}
switch (scope_which) {
case kScopeThis:
flags |= kRouteFlagsIsScoped;
break;
case kScopeNext:
this_route = RouteListGetRouteAtIndex(info, routes, where + 1);
flags |= kRouteFlagsIsScoped;
break;
default:
case kScopeNone:
break;
}
if (this_route != NULL && flags != 0) {
this_route->flags |= flags;
}
done:
return (routes);
}
static RouteListRef
RouteListAddRouteList(RouteListInfoRef info,
RouteListRef routes, int init_size,
RouteListRef service_routes, Rank rank,
boolean_t force)
{
int i;
RouteRef scan;
for (i = 0, scan = RouteListGetFirstRoute(info, service_routes);
i < service_routes->count;
i++, scan = RouteGetNextRoute(info, scan)) {
Rank this_rank;
if (i == 0
&& (service_routes->flags & kRouteListFlagsHasDefault) != 0) {
this_rank = rank;
}
else {
this_rank = RANK_INDEX_MASK(rank) | RANK_ASSERTION_MASK(scan->rank);
}
routes = RouteListAddRoute(info, routes, init_size, scan, this_rank,
force);
}
return (routes);
}
static void
RouteAddInterfaceToDescription(RouteRef r, CFMutableStringRef str)
{
char if_name[IFNAMSIZ];
if (my_if_indextoname2(r->ifindex, if_name) != NULL) {
CFStringAppendFormat(str, NULL,
CFSTR(" Ifp %s"),
if_name);
}
if (my_if_indextoname2(r->exclude_ifindex, if_name) != NULL) {
CFStringAppendFormat(str, NULL,
CFSTR(" !Ifp %s"),
if_name);
}
return;
}
static void
RouteAddFlagsToDescription(RouteRef r, CFMutableStringRef str)
{
if ((r->flags & kRouteFlagsIsNULL) != 0) {
CFStringAppend(str, CFSTR(" [null]"));
}
else {
Rank rank_assertion = RANK_ASSERTION_MASK(r->rank);
switch (rank_assertion) {
case kRankAssertionFirst:
CFStringAppend(str, CFSTR(" [first]"));
break;
case kRankAssertionLast:
CFStringAppend(str, CFSTR(" [last]"));
break;
case kRankAssertionNever:
CFStringAppend(str, CFSTR(" [never]"));
break;
default:
break;
}
if ((r->flags & kRouteFlagsKernelManaged) != 0) {
CFStringAppend(str, CFSTR(" [kern]"));
}
if ((r->flags & kRouteFlagsIsScoped) != 0) {
CFStringAppend(str, CFSTR(" [SCOPED]"));
}
#ifndef TEST_ROUTELIST
if ((r->control_flags & kControlFlagsForce) != 0) {
CFStringAppend(str, CFSTR(" [force]"));
}
#endif
}
return;
}
#if !TARGET_OS_SIMULATOR
static RouteRef
RouteListFindRoute(RouteListInfoRef info, RouteListRef routes, RouteRef route)
{
int i;
RouteRef match = NULL;
RouteRef scan;
for (i = 0, scan = RouteListGetFirstRoute(info, routes);
i < routes->count;
i++, scan = RouteGetNextRoute(info, scan)) {
if ((*info->route_equal)(scan, route)) {
match = scan;
break;
}
}
return (match);
}
typedef enum {
kRouteLookupFlagsNone = 0x0,
kRouteLookupFlagsExcludeInterface = 0x1
} RouteLookupFlags;
static RouteRef
RouteListLookup(RouteListInfoRef info,
RouteListRef routes,
const void * address,
int n_bits,
IFIndex ifindex,
RouteLookupFlags lookup_flags)
{
RouteRef best_match = NULL;
int i;
RouteRef scan;
for (i = 0, scan = RouteListGetFirstRoute(info, routes);
i < routes->count;
i++, scan = RouteGetNextRoute(info, scan)) {
if (scan->ifindex == 0 || scan->exclude_ifindex != 0) {
continue;
}
if ((lookup_flags & kRouteLookupFlagsExcludeInterface) != 0) {
if (ifindex == scan->ifindex) {
continue;
}
}
else if (ifindex != scan->ifindex) {
continue;
}
if ((scan->flags & kRouteFlagsHasGateway) != 0
&& RouteAddressCompare(info,
(*info->route_gateway)(scan),
address) == 0) {
continue;
}
if ((scan->flags & kRouteFlagsIsHost) != 0) {
if (n_bits == info->all_bits_set
&& RouteAddressCompare(info,
(*info->route_destination)(scan),
address) == 0) {
best_match = scan;
break;
}
continue;
}
if ((*info->route_same_subnet)(scan, address) == FALSE) {
continue;
}
if (scan->prefix_length == n_bits) {
best_match = scan;
break;
}
if (scan->prefix_length > n_bits) {
continue;
}
if (best_match == NULL
|| scan->prefix_length > best_match->prefix_length) {
best_match = scan;
}
}
return (best_match);
}
static boolean_t
RouteProcess(RouteRef route,
RouteCommand cmd,
RouteListApplyContextRef context)
{
RouteLog route_log = context->info->route_log;
RouteApply route_apply = context->info->route_apply;
RouteGateway route_gateway = context->info->route_gateway;
int retval;
switch (cmd) {
case kRouteCommandAdd:
if ((route->control_flags & kControlFlagsProcessed) != 0) {
return ((route->control_flags & kControlFlagsAdded) != 0);
}
route->control_flags |= kControlFlagsProcessed;
if ((route->flags & kRouteFlagsHasGateway) != 0) {
boolean_t added;
RouteRef gateway_route;
gateway_route
= RouteListLookup(context->info,
context->new_routes,
(*route_gateway)(route),
context->info->all_bits_set,
route->ifindex,
kRouteLookupFlagsNone);
if (gateway_route == NULL) {
(*route_log)(LOG_NOTICE, route, "no gateway route");
}
else {
#define MAX_RECURSE_DEPTH 10
if (context->depth == MAX_RECURSE_DEPTH) {
(*route_log)(LOG_NOTICE, route, "routing loop detected, not adding");
return (FALSE);
}
context->depth++;
added = RouteProcess(gateway_route,
kRouteCommandAdd,
context);
context->depth--;
if (!added) {
(*route_log)(LOG_NOTICE, route, "failed to add");
return (FALSE);
}
}
}
retval = (*route_apply)(route, RTM_ADD, context->sockfd);
if (retval == EEXIST) {
(void)(*route_apply)(route, RTM_DELETE, context->sockfd);
retval = (*route_apply)(route, RTM_ADD, context->sockfd);
}
switch (retval) {
default:
my_log(LOG_NOTICE,
"failed to add route, %s:",
strerror(retval));
(*route_log)(LOG_NOTICE, route, NULL);
break;
case 0:
case EROUTENOTAPPLIED:
if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
char buf[64];
const char * str;
str = (retval == EROUTENOTAPPLIED) ? "!" : "";
snprintf(buf, sizeof(buf), "%sAdd new[%ld]",
str,
RouteListRouteIndex(context->info,
context->new_routes,
route));
(*route_log)(LOG_DEBUG, route, buf);
}
route->control_flags |= kControlFlagsAdded;
break;
}
break;
case kRouteCommandRemove:
retval = (*route_apply)(route, RTM_DELETE, context->sockfd);
switch (retval) {
case 0:
case ESRCH:
case EROUTENOTAPPLIED:
if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
char buf[64];
const char * str;
str = (retval == EROUTENOTAPPLIED) ? "!" : "";
snprintf(buf, sizeof(buf), "%sRemove old[%ld]%s",
str,
RouteListRouteIndex(context->info,
context->old_routes,
route),
(retval == ESRCH) ? "(ESRCH)" : "");
(*route_log)(LOG_DEBUG, route, buf);
}
break;
default:
my_log(LOG_NOTICE,
"failed to remove route, %s",
strerror(retval));
(*route_log)(LOG_NOTICE, route, NULL);
break;
}
break;
default:
break;
}
return (TRUE);
}
static void
RouteListApply(RouteListInfoRef info,
RouteListRef old_routes, RouteListRef new_routes,
int sockfd)
{
RouteListApplyContext context;
int i;
RouteRef scan;
if (old_routes == new_routes && old_routes == NULL) {
return;
}
memset(&context, 0, sizeof(context));
context.old_routes = old_routes;
context.new_routes = new_routes;
context.sockfd = sockfd;
context.info = info;
if (old_routes != NULL) {
for (i = 0, scan = RouteListGetFirstRoute(info, old_routes);
i < old_routes->count;
i++, scan = RouteGetNextRoute(info, scan)) {
RouteRef new_route = NULL;
if (new_routes != NULL) {
new_route = RouteListFindRoute(info, new_routes, scan);
}
if (new_route == NULL) {
if ((scan->control_flags & kControlFlagsAdded) != 0) {
RouteProcess(scan, kRouteCommandRemove, &context);
}
}
}
}
if (new_routes != NULL) {
if (old_routes != NULL) {
for (i = 0, scan = RouteListGetFirstRoute(info, new_routes);
i < new_routes->count;
i++, scan = RouteGetNextRoute(info, scan)) {
RouteRef old_route = NULL;
old_route = RouteListFindRoute(info, old_routes, scan);
if (old_route != NULL) {
if ((scan->control_flags & kControlFlagsForce) == 0) {
scan->control_flags
= ControlFlagsPreserve(old_route->control_flags);
}
else {
(*info->route_log)(LOG_NOTICE, (RouteRef)scan,
"Re-applying route");
}
}
}
}
for (i = 0, scan = RouteListGetFirstRoute(info, new_routes);
i < new_routes->count;
i++, scan = RouteGetNextRoute(info, scan)) {
if ((scan->control_flags & kControlFlagsProcessed) != 0) {
continue;
}
RouteProcess(scan, kRouteCommandAdd, &context);
}
}
return;
}
static void
RouteListFinalize(RouteListInfoRef info, RouteListRef routes)
{
int i;
RouteRef scan;
if (routes == NULL) {
return;
}
for (i = 0, scan = RouteListGetFirstRoute(info, routes);
i < routes->count;
i++, scan = RouteGetNextRoute(info, scan)) {
RouteRef route;
IFIndex ifindex;
RouteLookupFlags flags;
if (scan->exclude_ifindex == 0) {
continue;
}
if (scan->ifindex == 0) {
ifindex = scan->exclude_ifindex;
flags = kRouteLookupFlagsExcludeInterface;
}
else {
ifindex = scan->ifindex;
flags = kRouteLookupFlagsNone;
}
route = RouteListLookup(info, routes,
(*info->route_destination)(scan),
scan->prefix_length, ifindex, flags);
if (route == NULL) {
(*info->route_log)(LOG_NOTICE, (RouteRef)scan,
"can't resolve excluded route");
}
else {
if ((S_IPMonitor_debug & kDebugFlag8) != 0) {
(*info->route_log)(LOG_DEBUG, (RouteRef)scan, "Excluded route");
(*info->route_log)(LOG_DEBUG, (RouteRef)route, "Resolved to");
}
scan->ifindex = route->ifindex;
if ((route->flags & kRouteFlagsHasGateway) != 0) {
(*info->route_set_gateway)(scan, (*info->route_gateway)(route));
scan->flags |= kRouteFlagsHasGateway;
if (scan->prefix_length == info->all_bits_set) {
scan->flags |= kRouteFlagsIsHost;
}
}
else {
scan->flags &= ~(kRouteFlagsHasGateway | kRouteFlagsIsHost);
}
}
}
return;
}
#endif
#define IPV4_ROUTE_ALL_BITS_SET 32
static __inline__ struct in_addr
subnet_addr(struct in_addr addr, struct in_addr mask)
{
struct in_addr net;
net.s_addr = addr.s_addr & mask.s_addr;
return (net);
}
static void
IPv4RouteCopyDescriptionWithString(IPv4RouteRef r, CFMutableStringRef str)
{
if ((r->flags & kRouteFlagsIsHost) != 0) {
CFStringAppendFormat(str, NULL,
CFSTR("Host " IP_FORMAT),
IP_LIST(&r->dest));
}
else {
CFStringAppendFormat(str, NULL,
CFSTR("Net " IP_FORMAT),
IP_LIST(&r->dest));
CFStringAppendFormat(str, NULL, CFSTR("/%d"),
r->prefix_length);
}
if ((r->flags & kRouteFlagsHasGateway) != 0) {
CFStringAppendFormat(str, NULL,
CFSTR(" Gate " IP_FORMAT),
IP_LIST(&r->gateway));
}
RouteAddInterfaceToDescription((RouteRef)r, str);
if (r->ifa.s_addr != 0) {
CFStringAppendFormat(str, NULL,
CFSTR(" Ifa " IP_FORMAT),
IP_LIST(&r->ifa));
}
RouteAddFlagsToDescription((RouteRef)r, str);
return;
}
static CFStringRef
IPv4RouteCopyDescription(RouteRef r)
{
CFMutableStringRef str;
str = CFStringCreateMutable(NULL, 0);
IPv4RouteCopyDescriptionWithString((IPv4RouteRef)r, str);
return (str);
}
#ifdef TEST_IPV4_ROUTELIST
static CFMutableStringRef
IPv4RouteListCopyDescription(IPv4RouteListRef routes);
static void
IPv4RouteLog(int level, RouteRef route, const char * msg)
{
CFStringRef str = IPv4RouteCopyDescription(route);
if (msg == NULL) {
SCPrint(TRUE, stdout, CFSTR("%@\n"), str);
}
else {
SCPrint(TRUE, stdout, CFSTR("%s: %@\n"), msg, str);
}
CFRelease(str);
return;
}
static __inline__ void
IPv4RouteListPrint(IPv4RouteListRef routes)
{
CFStringRef str = IPv4RouteListCopyDescription(routes);
SCPrint(TRUE, stdout, CFSTR("%@\n"), str);
CFRelease(str);
return;
}
#else
static __inline__ void
IPv4RouteLog(int level, RouteRef route, const char * msg)
{
CFStringRef str = IPv4RouteCopyDescription(route);
if (msg == NULL) {
my_log(level, "%@", str);
}
else {
my_log(level, "%s: %@", msg, str);
}
CFRelease(str);
return;
}
#endif
static boolean_t
IPv4RouteIsEqual(RouteRef r_scan, RouteRef r_route)
{
IPv4RouteRef route = (IPv4RouteRef)r_route;
IPv4RouteRef scan = (IPv4RouteRef)r_scan;
return ((scan->dest.s_addr == route->dest.s_addr)
&& (scan->mask.s_addr == route->mask.s_addr)
&& (scan->ifindex == route->ifindex)
&& (scan->ifa.s_addr == route->ifa.s_addr)
&& (scan->gateway.s_addr == route->gateway.s_addr)
&& (scan->flags == route->flags));
}
static CFMutableStringRef
IPv4RouteListCopyDescription(IPv4RouteListRef routes)
{
int i;
IPv4RouteRef r;
CFMutableStringRef str;
str = CFStringCreateMutable(NULL, 0);
CFStringAppendFormat(str, NULL, CFSTR("<IPv4RouteList[%d]> = {"),
routes->count);
for (i = 0, r = routes->list; i < routes->count; i++, r++) {
CFStringAppendFormat(str, NULL, CFSTR("\n%2d. "), i);
IPv4RouteCopyDescriptionWithString(r, str);
}
CFStringAppend(str, CFSTR("\n}"));
return (str);
}
static size_t
IPv4RouteListComputeSize(CFIndex n)
{
return (offsetof(IPv4RouteList, list[n]));
}
static int
count_prefix_bits_set(uint32_t n)
{
int count;
const static int8_t bits[16] = {
0,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
1,
-1,
-1,
-1,
2,
-1,
3,
4,
};
for (count = 0; n != 0; n >>= 4) {
int nbits = bits[n & 0x0f];
if (nbits < 0) {
return (-1);
}
count += nbits;
}
return (count);
}
static uint32_t
prefix_to_mask32(unsigned int prefix_length)
{
if (prefix_length > 32 || prefix_length == 0) {
return (0);
}
return (0xffffffff << (32 - prefix_length));
}
static int
mask_get_prefix_length(struct in_addr mask)
{
int count;
count = count_prefix_bits_set(mask.s_addr);
if (count >= 0) {
uint32_t val;
val = prefix_to_mask32(count);
if (ntohl(mask.s_addr) != val) {
return (-1);
}
}
return (count);
}
static boolean_t
IPv4RouteSetPrefixLength(IPv4RouteRef route)
{
int length;
length = mask_get_prefix_length(route->mask);
if (length < 0) {
return (FALSE);
}
route->prefix_length = length;
return (TRUE);
}
static const void *
IPv4RouteGateway(RouteRef r_route)
{
IPv4RouteRef route = (IPv4RouteRef)r_route;
return (&route->gateway);
}
static void
IPv4RouteSetGateway(RouteRef r_route, const void * address)
{
IPv4RouteRef route = (IPv4RouteRef)r_route;
route->gateway = *((struct in_addr *)address);
return;
}
static const void *
IPv4RouteDestination(RouteRef r_route)
{
IPv4RouteRef route = (IPv4RouteRef)r_route;
return (&route->dest);
}
static boolean_t
IPv4RouteSameSubnet(RouteRef r_route, const void * addr)
{
const struct in_addr * address;
IPv4RouteRef route = (IPv4RouteRef)r_route;
address = (const struct in_addr *)addr;
return ((address->s_addr & route->mask.s_addr) == route->dest.s_addr);
}
#define ROUTE_MSG_ADDRS_SPACE (3 * sizeof(struct sockaddr_in) \
+ 2 * sizeof(struct sockaddr_dl) \
+ 128)
typedef struct {
struct rt_msghdr hdr;
char addrs[ROUTE_MSG_ADDRS_SPACE];
} route_msg;
static int
IPv4RouteApply(RouteRef r_route, int cmd, int sockfd)
{
size_t len;
int ret = 0;
IPv4RouteRef route = (IPv4RouteRef)r_route;
route_msg rtmsg;
union {
struct sockaddr_in * in_p;
struct sockaddr_dl * dl_p;
void * ptr;
} rtaddr;
if (S_netboot && route->dest.s_addr == 0) {
return (EROUTENOTAPPLIED);
}
if ((route->flags & kRouteFlagsIsNULL) != 0) {
return (EROUTENOTAPPLIED);
}
if (route->ifindex == 0) {
my_log(LOG_NOTICE,
IP_FORMAT " no interface specified, ignoring",
IP_LIST(&route->dest));
return (ENXIO);
}
if (sockfd == -1) {
#ifdef TEST_IPV4_ROUTELIST
return (0);
#else
return (EBADF);
#endif
}
memset(&rtmsg, 0, sizeof(rtmsg));
rtmsg.hdr.rtm_type = cmd;
rtmsg.hdr.rtm_version = RTM_VERSION;
rtmsg.hdr.rtm_seq = ++rtm_seq;
rtmsg.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_IFP;
if (route->ifa.s_addr != 0) {
rtmsg.hdr.rtm_addrs |= RTA_IFA;
}
rtmsg.hdr.rtm_flags = RTF_UP | RTF_STATIC;
if ((route->flags & kRouteFlagsIsHost) != 0) {
rtmsg.hdr.rtm_flags |= RTF_HOST;
}
else {
rtmsg.hdr.rtm_addrs |= RTA_NETMASK;
if ((route->flags & kRouteFlagsHasGateway) == 0) {
rtmsg.hdr.rtm_flags |= RTF_CLONING;
}
}
if ((route->flags & kRouteFlagsHasGateway) != 0) {
rtmsg.hdr.rtm_flags |= RTF_GATEWAY;
}
if ((route->flags & kRouteFlagsIsScoped) != 0) {
rtmsg.hdr.rtm_index = route->ifindex;
rtmsg.hdr.rtm_flags |= RTF_IFSCOPE;
}
rtaddr.ptr = rtmsg.addrs;
rtaddr.in_p->sin_len = sizeof(*rtaddr.in_p);
rtaddr.in_p->sin_family = AF_INET;
rtaddr.in_p->sin_addr = route->dest;
rtaddr.ptr += sizeof(*rtaddr.in_p);
if ((rtmsg.hdr.rtm_flags & RTF_GATEWAY) != 0) {
rtaddr.in_p->sin_len = sizeof(*rtaddr.in_p);
rtaddr.in_p->sin_family = AF_INET;
rtaddr.in_p->sin_addr = route->gateway;
rtaddr.ptr += sizeof(*rtaddr.in_p);
}
else {
rtaddr.dl_p->sdl_len = sizeof(*rtaddr.dl_p);
rtaddr.dl_p->sdl_family = AF_LINK;
rtaddr.dl_p->sdl_index = route->ifindex;
rtaddr.ptr += sizeof(*rtaddr.dl_p);
}
if ((rtmsg.hdr.rtm_addrs & RTA_NETMASK) != 0) {
rtaddr.in_p->sin_len = sizeof(*rtaddr.in_p);
rtaddr.in_p->sin_family = AF_INET;
rtaddr.in_p->sin_addr = route->mask;
rtaddr.ptr += sizeof(*rtaddr.in_p);
}
if ((rtmsg.hdr.rtm_addrs & RTA_IFP) != 0) {
rtaddr.dl_p->sdl_len = sizeof(*rtaddr.dl_p);
rtaddr.dl_p->sdl_family = AF_LINK;
rtaddr.dl_p->sdl_index = route->ifindex;
rtaddr.ptr += sizeof(*rtaddr.dl_p);
}
if ((rtmsg.hdr.rtm_addrs & RTA_IFA) != 0) {
rtaddr.in_p->sin_len = sizeof(*rtaddr.in_p);
rtaddr.in_p->sin_family = AF_INET;
rtaddr.in_p->sin_addr = route->ifa;
rtaddr.ptr += sizeof(*rtaddr.in_p);
}
len = (int)(sizeof(rtmsg.hdr) + (rtaddr.ptr - (void *)rtmsg.addrs));
rtmsg.hdr.rtm_msglen = len;
if (write(sockfd, &rtmsg, len) == -1) {
ret = errno;
}
return (ret);
}
static const RouteListInfo IPv4RouteListInfo = {
IPv4RouteListComputeSize,
IPv4RouteIsEqual,
IPv4RouteApply,
IPv4RouteGateway,
IPv4RouteSetGateway,
IPv4RouteDestination,
IPv4RouteSameSubnet,
IPv4RouteLog,
IPv4RouteCopyDescription,
sizeof(IPv4Route),
sizeof(struct in_addr),
IPV4_ROUTE_ALL_BITS_SET
};
#if !TARGET_OS_SIMULATOR
static __inline__ void
IPv4RouteListLog(int level, IPv4RouteListRef routes)
{
CFStringRef str = IPv4RouteListCopyDescription(routes);
my_log(level, "%@", str);
CFRelease(str);
return;
}
static void
IPv4RouteListApply(IPv4RouteListRef old_routes, IPv4RouteListRef new_routes,
int sockfd)
{
RouteListApply(&IPv4RouteListInfo,
(RouteListRef)old_routes, (RouteListRef)new_routes,
sockfd);
return;
}
static void
IPv4RouteListFinalize(IPv4RouteListRef routes)
{
RouteListFinalize(&IPv4RouteListInfo, (RouteListRef)routes);
return;
}
#endif
#if TEST_IPV4_ROUTELIST
static IPv4RouteListRef
IPv4RouteListAddRouteList(IPv4RouteListRef routes, int init_size,
IPv4RouteListRef service_routes, Rank rank)
{
return ((IPv4RouteListRef)
RouteListAddRouteList(&IPv4RouteListInfo,
(RouteListRef)routes, init_size,
(RouteListRef)service_routes, rank,
FALSE));
}
#endif
static CFStringRef
plist_get_string(CFDictionaryRef dict, CFStringRef prop_name,
char * buf, int buf_size)
{
CFStringRef val;
val = CFDictionaryGetValue(dict, prop_name);
if (isA_CFString(val) == NULL) {
return (NULL);
}
if (!CFStringGetCString(val, buf, buf_size, kCFStringEncodingUTF8)) {
return (NULL);
}
return (val);
}
typedef struct {
struct in_addr addr;
int * count_p;
IFIndex ifindex;
IFIndex exclude_ifindex;
IPv4RouteRef * route_p;
Rank rank;
const char * descr;
} AddIPv4RouteContext, * AddIPv4RouteContextRef;
static void
AddIPv4Route(const void * value, void * context)
{
AddIPv4RouteContextRef ctx = (AddIPv4RouteContextRef)context;
CFDictionaryRef dict = (CFDictionaryRef)value;
IPv4RouteRef r = *ctx->route_p;
dict = isA_CFDictionary(dict);
if (dict == NULL
|| !dict_get_ip(dict, kSCPropNetIPv4RouteDestinationAddress, &r->dest)
|| !dict_get_ip(dict, kSCPropNetIPv4RouteSubnetMask, &r->mask)) {
if (dict == NULL) {
my_log(LOG_NOTICE, "%s route is not a dictionary",
ctx->descr);
}
else {
my_log(LOG_NOTICE, "%s route is invalid, %@",
ctx->descr, dict);
}
goto skip;
}
if (!IPv4RouteSetPrefixLength(r)) {
my_log(LOG_NOTICE, "%s route has invalid subnet mask, %@",
ctx->descr, dict);
goto skip;
}
r->rank = ctx->rank;
r->exclude_ifindex = ctx->exclude_ifindex;
if (ctx->ifindex != 0) {
r->ifindex = ctx->ifindex;
r->ifa = ctx->addr;
if (ctx->exclude_ifindex == 0
&& dict_get_ip(dict,
kSCPropNetIPv4RouteGatewayAddress,
&r->gateway)) {
r->flags |= kRouteFlagsHasGateway;
if (r->prefix_length == IPV4_ROUTE_ALL_BITS_SET) {
r->flags |= kRouteFlagsIsHost;
}
}
}
else {
char ifname[IFNAMSIZ];
if (plist_get_string(dict, kSCPropNetIPv4RouteInterfaceName,
ifname, sizeof(ifname)) != NULL) {
IFIndex ifindex;
ifindex = my_if_nametoindex(ifname);
if (ifindex == 0) {
my_log(LOG_NOTICE,
"%s: interface %s does not exist, %@",
ctx->descr, ifname, dict);
goto skip;
}
else if (ifindex == ctx->ifindex) {
my_log(LOG_NOTICE,
"%s: interface %s unexpected, %@",
ctx->descr, ifname, dict);
goto skip;
}
r->ifindex = ifindex;
}
}
(*ctx->route_p)++;
return;
skip:
(*ctx->count_p)--;
return;
}
static boolean_t
confirm_interface_name(CFDictionaryRef dict, CFStringRef ifname)
{
CFStringRef confirmed_ifname;
boolean_t confirmed;
confirmed_ifname
= CFDictionaryGetValue(dict, kSCPropConfirmedInterfaceName);
if (isA_CFString(confirmed_ifname) != NULL) {
confirmed = CFEqual(confirmed_ifname, ifname);
}
else {
confirmed = TRUE;
}
return (confirmed);
}
static IPv4RouteListRef
IPv4RouteListCreateWithDictionary(IPv4RouteListRef routes,
CFDictionaryRef dict,
CFNumberRef rank_assertion)
{
boolean_t add_broadcast_multicast = FALSE;
boolean_t add_default = FALSE;
boolean_t add_router_subnet = FALSE;
boolean_t add_subnet = FALSE;
struct in_addr addr = { 0 };
CFArrayRef additional_routes = NULL;
CFIndex additional_routes_count;
boolean_t allow_additional_routes = FALSE;
boolean_t exclude_from_nwi = FALSE;
CFArrayRef excluded_routes = NULL;
CFIndex excluded_routes_count;
RouteFlags flags = 0;
IFIndex ifindex;
char ifname[IFNAMSIZ];
CFStringRef ifname_cf;
struct in_addr mask = { 0 };
int n = 0;
int prefix_length = 0;
Rank primary_rank = kRankAssertionDefault;
IPv4RouteRef r;
Rank rank = kRankAssertionDefault;
struct in_addr router = { 0 };
boolean_t scoped_only = FALSE;
struct in_addr subnet = { 0 };
if (dict == NULL) {
return (NULL);
}
ifname_cf = plist_get_string(dict, kSCPropInterfaceName,
ifname, sizeof(ifname));
if (ifname_cf == NULL) {
return (NULL);
}
ifindex = my_if_nametoindex(ifname);
if (ifindex == 0) {
return (NULL);
}
allow_additional_routes = confirm_interface_name(dict, ifname_cf);
if (!dict_get_ip(dict, kSCPropNetIPv4Router, &router)) {
(void)dict_get_first_ip(dict, kSCPropNetIPv4DestAddresses, &router);
}
if (dict_get_first_ip(dict, kSCPropNetIPv4Addresses, &addr)
&& dict_get_first_ip(dict, kSCPropNetIPv4SubnetMasks, &mask)) {
subnet = subnet_addr(addr, mask);
prefix_length = mask_get_prefix_length(mask);
if (prefix_length < 0) {
my_log(LOG_NOTICE,
"ignoring bad subnet mask "
IP_FORMAT " on %s",
IP_LIST(&mask), ifname);
}
else {
add_subnet = TRUE;
n++;
}
}
if (addr.s_addr == 0) {
return (NULL);
}
if (rank_assertion != NULL) {
(void)CFNumberGetValue(rank_assertion, kCFNumberSInt32Type,
&primary_rank);
}
if (router.s_addr == 0) {
switch (primary_rank) {
case kRankAssertionLast:
case kRankAssertionNever:
case kRankAssertionScoped:
break;
default:
primary_rank = kRankAssertionLast;
break;
}
}
else {
if (addr.s_addr == router.s_addr
&& mask.s_addr != INADDR_BROADCAST) {
;
}
else {
flags |= kRouteFlagsHasGateway;
}
if (rank_assertion == NULL && get_override_primary(dict)) {
primary_rank = kRankAssertionFirst;
}
}
if (S_dict_get_boolean(dict, kIsNULL, FALSE)) {
exclude_from_nwi = TRUE;
flags |= kRouteFlagsIsNULL;
}
switch (primary_rank) {
case kRankAssertionScoped:
primary_rank = rank = kRankAssertionNever;
flags |= kRouteFlagsIsScoped;
scoped_only = TRUE;
break;
case kRankAssertionNever:
rank = kRankAssertionLast;
flags |= kRouteFlagsIsScoped;
break;
default:
rank = primary_rank;
break;
}
if ((flags & kRouteFlagsHasGateway) != 0) {
add_router_subnet = TRUE;
n++;
}
if (ifindex != lo0_ifindex()) {
if (router.s_addr != 0) {
add_default = TRUE;
n++;
}
add_broadcast_multicast = TRUE;
n += 2;
}
if (allow_additional_routes) {
additional_routes
= CFDictionaryGetValue(dict, kSCPropNetIPv4AdditionalRoutes);
additional_routes = isA_CFArray(additional_routes);
if (additional_routes != NULL) {
additional_routes_count = CFArrayGetCount(additional_routes);
n += additional_routes_count;
}
excluded_routes
= CFDictionaryGetValue(dict, kSCPropNetIPv4ExcludedRoutes);
excluded_routes = isA_CFArray(excluded_routes);
if (excluded_routes != NULL) {
excluded_routes_count = CFArrayGetCount(excluded_routes);
n += excluded_routes_count;
}
}
if (routes == NULL || routes->size < n) {
routes = (IPv4RouteListRef)malloc(IPv4RouteListComputeSize(n));
memset(routes, 0, IPv4RouteListComputeSize(n));
routes->size = n;
}
else {
memset(routes->list, 0, sizeof(routes->list[0]) * n);
}
routes->count = n;
if (exclude_from_nwi) {
routes->flags |= kRouteListFlagsExcludeNWI;
}
else if (scoped_only) {
routes->flags |= kRouteListFlagsScopedOnly;
}
r = routes->list;
if (add_default) {
routes->flags |= kRouteListFlagsHasDefault;
r->ifindex = ifindex;
r->ifa = addr;
r->flags = flags;
if ((flags & kRouteFlagsHasGateway) != 0) {
r->gateway = router;
}
else {
r->gateway = addr;
}
r->rank = primary_rank;
r++;
}
if (add_broadcast_multicast) {
if ((flags & kRouteFlagsIsNULL) != 0) {
r->flags |= kRouteFlagsIsNULL;
}
r->dest.s_addr = INADDR_BROADCAST;
r->mask.s_addr = INADDR_BROADCAST;
r->prefix_length = IPV4_ROUTE_ALL_BITS_SET;
r->ifindex = ifindex;
r->ifa = addr;
r->rank = rank;
r++;
if ((flags & kRouteFlagsIsNULL) != 0) {
r->flags |= kRouteFlagsIsNULL;
}
r->dest.s_addr = htonl(INADDR_UNSPEC_GROUP);
r->mask.s_addr = htonl(IN_CLASSD_NET);
r->prefix_length = PREFIX_LENGTH_IN_CLASSD;
r->ifindex = ifindex;
r->ifa = addr;
r->rank = rank;
r++;
}
if (add_subnet) {
if ((flags & kRouteFlagsIsNULL) != 0) {
r->flags |= kRouteFlagsIsNULL;
}
r->ifindex = ifindex;
r->gateway = addr;
r->dest = subnet;
r->mask = mask;
r->prefix_length = prefix_length;
r->ifa = addr;
r->rank = rank;
r++;
}
if (add_router_subnet) {
if ((flags & kRouteFlagsIsNULL) != 0) {
r->flags |= kRouteFlagsIsNULL;
}
r->ifindex = ifindex;
r->gateway = addr;
r->dest = router;
r->mask.s_addr = INADDR_BROADCAST;
r->prefix_length = IPV4_ROUTE_ALL_BITS_SET;
r->ifa = addr;
r->rank = rank;
r++;
}
if (additional_routes != NULL || excluded_routes != NULL) {
AddIPv4RouteContext context;
memset(&context, 0, sizeof(context));
context.count_p = &routes->count;
context.route_p = &r;
context.rank = rank;
if (additional_routes != NULL) {
context.ifindex = ifindex;
context.addr = addr;
context.descr = "AdditionalRoutes";
CFArrayApplyFunction(additional_routes,
CFRangeMake(0, additional_routes_count),
AddIPv4Route, &context);
}
if (excluded_routes != NULL) {
context.descr = "ExcludedRoutes";
context.ifindex = 0;
context.exclude_ifindex = ifindex;
CFArrayApplyFunction(excluded_routes,
CFRangeMake(0, excluded_routes_count),
AddIPv4Route, &context);
}
}
return (routes);
}
#if !TARGET_OS_SIMULATOR
static IPv4RouteListRef
IPv4RouteListCopyMulticastLoopback(void)
{
IPv4RouteRef r;
IPv4RouteListRef routes;
routes = (IPv4RouteListRef)malloc(IPv4RouteListComputeSize(1));
memset(routes, 0, IPv4RouteListComputeSize(1));
routes->count = routes->size = 1;
r = routes->list;
r->dest.s_addr = htonl(INADDR_UNSPEC_GROUP);
r->mask.s_addr = htonl(IN_CLASSC_NET);
r->prefix_length = PREFIX_LENGTH_IN_CLASSC;
r->ifindex = lo0_ifindex();
return (routes);
}
#endif
#define IPV6_ROUTE_ALL_BITS_SET 128
static boolean_t
ipv6_prefix_length_is_valid(int prefix_length)
{
if (prefix_length < 0 || prefix_length > IPV6_ROUTE_ALL_BITS_SET) {
return (FALSE);
}
return (TRUE);
}
static void
in6_len2mask(struct in6_addr * mask, int len)
{
int i;
memset(mask, 0, sizeof(*mask));
for (i = 0; i < len / 8; i++)
mask->s6_addr[i] = 0xff;
if (len % 8)
mask->s6_addr[i] = (0xff00 >> (len % 8)) & 0xff;
}
static void
in6_maskaddr(struct in6_addr * addr, const struct in6_addr * mask)
{
for (size_t i = 0; i < sizeof(addr->s6_addr); i++) {
addr->s6_addr[i] &= mask->s6_addr[i];
}
return;
}
static void
in6_netaddr(struct in6_addr * addr, int len)
{
struct in6_addr mask;
in6_len2mask(&mask, len);
in6_maskaddr(addr, &mask);
return;
}
static void
in6_addr_scope_linklocal(struct in6_addr * addr, IFIndex ifindex)
{
if (IN6_IS_ADDR_LINKLOCAL(addr)) {
addr->__u6_addr.__u6_addr16[1] = htons(ifindex);
}
return;
}
static void
string_append_in6_addr(CFMutableStringRef str, const struct in6_addr * addr)
{
char ntopbuf[INET6_ADDRSTRLEN];
CFStringAppendCString(str,
inet_ntop(AF_INET6, addr, ntopbuf, sizeof(ntopbuf)),
kCFStringEncodingASCII);
return;
}
static void
IPv6RouteCopyDescriptionWithString(IPv6RouteRef r, CFMutableStringRef str)
{
if ((r->flags & kRouteFlagsIsHost) != 0) {
CFStringAppend(str, CFSTR("Host "));
string_append_in6_addr(str, &r->dest);
}
else {
CFStringAppend(str, CFSTR("Net "));
string_append_in6_addr(str, &r->dest);
CFStringAppendFormat(str, NULL, CFSTR("/%d"),
r->prefix_length);
}
if ((r->flags & kRouteFlagsHasGateway) != 0) {
CFStringAppend(str, CFSTR(" Gate "));
string_append_in6_addr(str, &r->gateway);
}
RouteAddInterfaceToDescription((RouteRef)r, str);
if (!IN6_ARE_ADDR_EQUAL(&r->ifa, &in6addr_any)) {
CFStringAppend(str, CFSTR(" Ifa "));
string_append_in6_addr(str, &r->ifa);
}
RouteAddFlagsToDescription((RouteRef)r, str);
return;
}
static CFStringRef
IPv6RouteCopyDescription(RouteRef r)
{
CFMutableStringRef str;
str = CFStringCreateMutable(NULL, 0);
IPv6RouteCopyDescriptionWithString((IPv6RouteRef)r, str);
return (str);
}
static CFMutableStringRef
IPv6RouteListCopyDescription(IPv6RouteListRef routes)
{
int i;
IPv6RouteRef r;
CFMutableStringRef str;
str = CFStringCreateMutable(NULL, 0);
CFStringAppendFormat(str, NULL, CFSTR("<IPv6RouteList[%d]> = {"),
routes->count);
for (i = 0, r = routes->list; i < routes->count; i++, r++) {
CFStringAppendFormat(str, NULL, CFSTR("\n%2d. "), i);
IPv6RouteCopyDescriptionWithString(r, str);
}
CFStringAppend(str, CFSTR("\n}"));
return (str);
}
#if TEST_IPV6_ROUTELIST
static void
IPv6RouteLog(int level, RouteRef route, const char * msg)
{
CFStringRef str = IPv6RouteCopyDescription(route);
if (msg == NULL) {
SCPrint(TRUE, stdout, CFSTR("%@\n"), str);
}
else {
SCPrint(TRUE, stdout, CFSTR("%s: %@\n"), msg, str);
}
CFRelease(str);
return;
}
static __inline__ void
IPv6RouteListPrint(IPv6RouteListRef routes)
{
CFStringRef str = IPv6RouteListCopyDescription(routes);
SCPrint(TRUE, stdout, CFSTR("%@\n"), str);
CFRelease(str);
return;
}
#else
static __inline__ void
IPv6RouteLog(int level, RouteRef route, const char * msg)
{
CFStringRef str = IPv6RouteCopyDescription(route);
if (msg == NULL) {
my_log(level, "%@", str);
}
else {
my_log(level, "%s: %@", msg, str);
}
CFRelease(str);
return;
}
#endif
static size_t
IPv6RouteListComputeSize(CFIndex n)
{
return (offsetof(IPv6RouteList, list[n]));
}
typedef struct {
struct in6_addr * addr;
int * count_p;
IFIndex ifindex;
IFIndex exclude_ifindex;
IPv6RouteRef * route_p;
Rank rank;
const char * descr;
} AddIPv6RouteContext, * AddIPv6RouteContextRef;
static void
AddIPv6Route(const void * value, void * context)
{
AddIPv6RouteContextRef ctx = (AddIPv6RouteContextRef)context;
CFDictionaryRef dict = (CFDictionaryRef)value;
IPv6RouteRef r = *ctx->route_p;
dict = isA_CFDictionary(dict);
if (dict == NULL
|| !dict_get_ipv6(dict, kSCPropNetIPv6RouteDestinationAddress, &r->dest)
|| !dict_get_int(dict, kSCPropNetIPv6RoutePrefixLength,
&r->prefix_length)
|| !ipv6_prefix_length_is_valid(r->prefix_length)) {
if (dict == NULL) {
my_log(LOG_NOTICE, "%s route is not a dictionary",
ctx->descr);
}
else {
my_log(LOG_NOTICE, "%s route is invalid, %@",
ctx->descr, dict);
}
goto skip;
}
r->rank = ctx->rank;
r->exclude_ifindex = ctx->exclude_ifindex;
if (ctx->ifindex != 0) {
r->ifindex = ctx->ifindex;
r->ifa = *ctx->addr;
if (ctx->exclude_ifindex == 0
&& dict_get_ipv6(dict,
kSCPropNetIPv6RouteGatewayAddress,
&r->gateway)) {
r->flags |= kRouteFlagsHasGateway;
if (r->prefix_length == IPV6_ROUTE_ALL_BITS_SET) {
r->flags |= kRouteFlagsIsHost;
}
}
}
else {
char ifname[IFNAMSIZ];
if (plist_get_string(dict, kSCPropNetIPv6RouteInterfaceName,
ifname, sizeof(ifname)) != NULL) {
IFIndex ifindex;
ifindex = my_if_nametoindex(ifname);
if (ifindex == 0) {
my_log(LOG_NOTICE,
"%s: interface %s does not exist, %@",
ctx->descr, ifname, dict);
goto skip;
}
else if (ifindex == ctx->ifindex) {
my_log(LOG_NOTICE,
"%s: interface %s unexpected, %@",
ctx->descr, ifname, dict);
goto skip;
}
r->ifindex = ifindex;
}
}
(*ctx->route_p)++;
return;
skip:
(*ctx->count_p)--;
return;
}
static IPv6RouteListRef
IPv6RouteListCreateWithDictionary(IPv6RouteListRef routes,
CFDictionaryRef dict,
CFNumberRef rank_assertion)
{
boolean_t add_default = FALSE;
boolean_t add_prefix = FALSE;
struct in6_addr addr;
CFArrayRef additional_routes = NULL;
CFIndex additional_routes_count;
boolean_t allow_additional_routes = FALSE;
boolean_t exclude_from_nwi = FALSE;
CFArrayRef excluded_routes = NULL;
CFIndex excluded_routes_count;
RouteFlags flags = 0;
IFIndex ifindex;
char ifname[IFNAMSIZ];
CFStringRef ifname_cf;
int n = 0;
int prefix_length = 0;
Rank primary_rank = kRankAssertionDefault;
IPv6RouteRef r;
Rank rank = kRankAssertionDefault;
struct in6_addr router = in6addr_any;
boolean_t scoped_only = FALSE;
if (dict == NULL) {
return (NULL);
}
ifname_cf = plist_get_string(dict, kSCPropInterfaceName,
ifname, sizeof(ifname));
if (ifname_cf == NULL) {
return (NULL);
}
ifindex = my_if_nametoindex(ifname);
if (ifindex == 0) {
return (NULL);
}
allow_additional_routes = confirm_interface_name(dict, ifname_cf);
if (!dict_get_ipv6(dict, kSCPropNetIPv6Router, &router)) {
(void)dict_get_first_ipv6(dict, kSCPropNetIPv6DestAddresses, &router);
}
if (dict_get_first_ipv6(dict, kSCPropNetIPv6Addresses, &addr)) {
if (IN6_IS_ADDR_UNSPECIFIED(&addr)) {
return (NULL);
}
if (dict_get_first_int(dict, kSCPropNetIPv6PrefixLength,
&prefix_length)
&& !IN6_IS_ADDR_LINKLOCAL(&addr)
&& ipv6_prefix_length_is_valid(prefix_length)) {
add_prefix = TRUE;
n++;
}
else {
prefix_length = 0;
}
}
else {
return (NULL);
}
if (rank_assertion != NULL) {
(void)CFNumberGetValue(rank_assertion, kCFNumberSInt32Type,
&primary_rank);
}
if (!IN6_IS_ADDR_UNSPECIFIED(&router)) {
if (ifindex != lo0_ifindex()) {
add_default = TRUE;
n++;
}
if (IN6_ARE_ADDR_EQUAL(&router, &addr)
&& prefix_length != IPV6_ROUTE_ALL_BITS_SET) {
;
}
else {
flags |= kRouteFlagsHasGateway;
}
if (rank_assertion == NULL && get_override_primary(dict)) {
primary_rank = kRankAssertionFirst;
}
}
if (S_dict_get_boolean(dict, kIsNULL, FALSE)) {
exclude_from_nwi = TRUE;
flags |= kRouteFlagsIsNULL;
}
switch (primary_rank) {
case kRankAssertionScoped:
primary_rank = rank = kRankAssertionNever;
flags |= kRouteFlagsIsScoped;
scoped_only = TRUE;
break;
case kRankAssertionNever:
rank = kRankAssertionLast;
flags |= kRouteFlagsIsScoped;
break;
default:
rank = primary_rank;
break;
}
if (allow_additional_routes) {
additional_routes
= CFDictionaryGetValue(dict, kSCPropNetIPv6AdditionalRoutes);
additional_routes = isA_CFArray(additional_routes);
if (additional_routes != NULL) {
additional_routes_count = CFArrayGetCount(additional_routes);
n += additional_routes_count;
}
excluded_routes = CFDictionaryGetValue(dict,
kSCPropNetIPv6ExcludedRoutes);
excluded_routes = isA_CFArray(excluded_routes);
if (excluded_routes != NULL) {
excluded_routes_count = CFArrayGetCount(excluded_routes);
n += excluded_routes_count;
}
}
if (n == 0) {
return (NULL);
}
n++;
if (routes == NULL || routes->size < n) {
routes = (IPv6RouteListRef)malloc(IPv6RouteListComputeSize(n));
memset(routes, 0, IPv6RouteListComputeSize(n));
routes->size = n;
}
else {
memset(routes->list, 0, sizeof(routes->list[0]) * n);
}
routes->count = n;
if (exclude_from_nwi) {
routes->flags |= kRouteListFlagsExcludeNWI;
}
else if (scoped_only) {
routes->flags |= kRouteListFlagsScopedOnly;
}
r = routes->list;
if (add_default) {
routes->flags |= kRouteListFlagsHasDefault;
r->ifindex = ifindex;
r->ifa = addr;
r->flags = flags;
if ((flags & kRouteFlagsHasGateway) != 0) {
r->gateway = router;
}
else {
r->gateway = addr;
}
r->rank = primary_rank;
r->flags |= kRouteFlagsKernelManaged;
r++;
}
r->ifindex = ifindex;
r->dest.s6_addr[0] = 0xfe;
r->dest.s6_addr[1] = 0x80;
r->prefix_length = 64;
r->rank = rank;
r->flags |= kRouteFlagsKernelManaged;
r++;
if (add_prefix) {
r->flags |= kRouteFlagsKernelManaged;
if ((flags & kRouteFlagsIsNULL) != 0) {
r->flags |= kRouteFlagsIsNULL;
}
r->ifindex = ifindex;
r->gateway = addr;
r->dest = addr;
in6_netaddr(&r->dest, prefix_length);
r->prefix_length = prefix_length;
r->ifa = addr;
r->rank = rank;
r++;
}
if (additional_routes != NULL || excluded_routes != NULL) {
AddIPv6RouteContext context;
memset(&context, 0, sizeof(context));
context.count_p = &routes->count;
context.route_p = &r;
context.rank = rank;
if (additional_routes != NULL) {
context.ifindex = ifindex;
context.addr = &addr;
context.descr = "AdditionalRoutes";
CFArrayApplyFunction(additional_routes,
CFRangeMake(0, additional_routes_count),
AddIPv6Route, &context);
}
if (excluded_routes != NULL) {
context.descr = "ExcludedRoutes";
context.ifindex = 0;
context.exclude_ifindex = ifindex;
context.addr = NULL;
CFArrayApplyFunction(excluded_routes,
CFRangeMake(0, excluded_routes_count),
AddIPv6Route, &context);
}
}
return (routes);
}
static const void *
IPv6RouteGateway(RouteRef r_route)
{
IPv6RouteRef route = (IPv6RouteRef)r_route;
return (&route->gateway);
}
static void
IPv6RouteSetGateway(RouteRef r_route, const void * address)
{
IPv6RouteRef route = (IPv6RouteRef)r_route;
route->gateway = *((struct in6_addr *)address);
return;
}
static const void *
IPv6RouteDestination(RouteRef r_route)
{
IPv6RouteRef route = (IPv6RouteRef)r_route;
return (&route->dest);
}
static __inline__ int
in6_addr_cmp(const struct in6_addr * a, const struct in6_addr * b)
{
return (memcmp(a->s6_addr, b->s6_addr, sizeof(struct in6_addr)));
}
static boolean_t
IPv6RouteIsEqual(RouteRef r_route1, RouteRef r_route2)
{
IPv6RouteRef route1 = (IPv6RouteRef)r_route1;
IPv6RouteRef route2 = (IPv6RouteRef)r_route2;
return (route1->prefix_length == route2->prefix_length
&& route1->ifindex == route2->ifindex
&& route1->flags == route2->flags
&& in6_addr_cmp(&route1->dest, &route2->dest) == 0
&& in6_addr_cmp(&route1->ifa, &route2->ifa) == 0
&& in6_addr_cmp(&route1->gateway, &route2->gateway) == 0);
}
static boolean_t
IPv6RouteSameSubnet(RouteRef r_route, const void * addr)
{
const struct in6_addr * address = (const struct in6_addr *)addr;
struct in6_addr netaddr;
IPv6RouteRef route = (IPv6RouteRef)r_route;
netaddr = *address;
in6_netaddr(&netaddr, route->prefix_length);
return (in6_addr_cmp(&netaddr, &route->dest) == 0);
}
#define V6_ROUTE_MSG_ADDRS_SPACE (5 * sizeof(struct sockaddr_dl) + 128)
typedef struct {
struct rt_msghdr hdr;
char addrs[V6_ROUTE_MSG_ADDRS_SPACE];
} v6_route_msg;
static int
IPv6RouteApply(RouteRef r_route, int cmd, int sockfd)
{
int len;
int ret = 0;
IPv6RouteRef route = (IPv6RouteRef)r_route;
v6_route_msg rtmsg;
union {
struct sockaddr_in6 * in_p;
struct sockaddr_dl * dl_p;
void * ptr;
} rtaddr;
if ((route->flags & kRouteFlagsKernelManaged) != 0) {
return (EROUTENOTAPPLIED);
}
if ((route->flags & kRouteFlagsIsNULL) != 0) {
return (EROUTENOTAPPLIED);
}
if (route->ifindex == 0) {
IPv6RouteLog(LOG_NOTICE, (RouteRef)route,
"no interface specified");
return (ENXIO);
}
if (sockfd == -1) {
#if TEST_IPV6_ROUTELIST
return (0);
#else
return (EBADF);
#endif
}
memset(&rtmsg, 0, sizeof(rtmsg));
rtmsg.hdr.rtm_type = cmd;
rtmsg.hdr.rtm_version = RTM_VERSION;
rtmsg.hdr.rtm_seq = ++rtm_seq;
rtmsg.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_IFP;
if (!IN6_IS_ADDR_UNSPECIFIED(&route->ifa)) {
rtmsg.hdr.rtm_addrs |= RTA_IFA;
}
rtmsg.hdr.rtm_flags = RTF_UP | RTF_STATIC;
if ((route->flags & kRouteFlagsIsHost) != 0) {
rtmsg.hdr.rtm_flags |= RTF_HOST;
}
else {
rtmsg.hdr.rtm_addrs |= RTA_NETMASK;
if ((route->flags & kRouteFlagsHasGateway) == 0) {
rtmsg.hdr.rtm_flags |= RTF_CLONING;
}
}
if ((route->flags & kRouteFlagsHasGateway) != 0) {
rtmsg.hdr.rtm_flags |= RTF_GATEWAY;
}
if ((route->flags & kRouteFlagsIsScoped) != 0) {
rtmsg.hdr.rtm_index = route->ifindex;
rtmsg.hdr.rtm_flags |= RTF_IFSCOPE;
}
rtaddr.ptr = rtmsg.addrs;
rtaddr.in_p->sin6_len = sizeof(*rtaddr.in_p);
rtaddr.in_p->sin6_family = AF_INET6;
rtaddr.in_p->sin6_addr = route->dest;
in6_addr_scope_linklocal(&rtaddr.in_p->sin6_addr, route->ifindex);
rtaddr.ptr += sizeof(*rtaddr.in_p);
if ((rtmsg.hdr.rtm_flags & RTF_GATEWAY) != 0) {
rtaddr.in_p->sin6_len = sizeof(*rtaddr.in_p);
rtaddr.in_p->sin6_family = AF_INET6;
rtaddr.in_p->sin6_addr = route->gateway;
in6_addr_scope_linklocal(&rtaddr.in_p->sin6_addr, route->ifindex);
rtaddr.ptr += sizeof(*rtaddr.in_p);
}
else {
rtaddr.dl_p->sdl_len = sizeof(*rtaddr.dl_p);
rtaddr.dl_p->sdl_family = AF_LINK;
rtaddr.dl_p->sdl_index = route->ifindex;
rtaddr.ptr += sizeof(*rtaddr.dl_p);
}
if ((rtmsg.hdr.rtm_addrs & RTA_NETMASK) != 0) {
rtaddr.in_p->sin6_len = sizeof(*rtaddr.in_p);
rtaddr.in_p->sin6_family = AF_INET6;
in6_len2mask(&rtaddr.in_p->sin6_addr, route->prefix_length);
rtaddr.ptr += sizeof(*rtaddr.in_p);
}
if ((rtmsg.hdr.rtm_addrs & RTA_IFP) != 0) {
rtaddr.dl_p->sdl_len = sizeof(*rtaddr.dl_p);
rtaddr.dl_p->sdl_family = AF_LINK;
rtaddr.dl_p->sdl_index = route->ifindex;
rtaddr.ptr += sizeof(*rtaddr.dl_p);
}
if ((rtmsg.hdr.rtm_addrs & RTA_IFA) != 0) {
rtaddr.in_p->sin6_len = sizeof(*rtaddr.in_p);
rtaddr.in_p->sin6_family = AF_INET6;
rtaddr.in_p->sin6_addr = route->ifa;
rtaddr.ptr += sizeof(*rtaddr.in_p);
}
len = (int)(sizeof(rtmsg.hdr) + (rtaddr.ptr - (void *)rtmsg.addrs));
rtmsg.hdr.rtm_msglen = len;
if (write(sockfd, &rtmsg, len) == -1) {
ret = errno;
}
return (ret);
}
static const RouteListInfo IPv6RouteListInfo = {
IPv6RouteListComputeSize,
IPv6RouteIsEqual,
IPv6RouteApply,
IPv6RouteGateway,
IPv6RouteSetGateway,
IPv6RouteDestination,
IPv6RouteSameSubnet,
IPv6RouteLog,
IPv6RouteCopyDescription,
sizeof(IPv6Route),
sizeof(struct in6_addr),
IPV6_ROUTE_ALL_BITS_SET
};
#if TEST_IPV6_ROUTELIST
static IPv6RouteListRef
IPv6RouteListAddRouteList(IPv6RouteListRef routes, int init_size,
IPv6RouteListRef service_routes, Rank rank)
{
return ((IPv6RouteListRef)
RouteListAddRouteList(&IPv6RouteListInfo,
(RouteListRef)routes, init_size,
(RouteListRef)service_routes, rank,
FALSE));
}
#endif
#if !TARGET_OS_SIMULATOR
static __inline__ void
IPv6RouteListLog(int level, IPv6RouteListRef routes)
{
CFStringRef str = IPv6RouteListCopyDescription(routes);
my_log(level, "%@", str);
CFRelease(str);
return;
}
static void
IPv6RouteListFinalize(IPv6RouteListRef routes)
{
RouteListFinalize(&IPv6RouteListInfo, (RouteListRef)routes);
return;
}
static void
IPv6RouteListApply(IPv6RouteListRef old_routes, IPv6RouteListRef new_routes,
int sockfd)
{
RouteListApply(&IPv6RouteListInfo,
(RouteListRef)old_routes, (RouteListRef)new_routes,
sockfd);
return;
}
#endif
static CF_RETURNS_RETAINED CFStringRef
parseNetworkServiceString(CFStringRef str, CFStringRef *ret_protocol)
{
CFArrayRef components;
CFIndex count;
CFStringRef protocol = NULL;
CFStringRef serviceID = NULL;
components = CFStringCreateArrayBySeparatingStrings(NULL, str, CFSTR("/"));
count = CFArrayGetCount(components);
if (count >= 4) {
serviceID = CFArrayGetValueAtIndex(components, 3);
CFRetain(serviceID);
if (count >= 5 && ret_protocol != NULL) {
protocol = CFArrayGetValueAtIndex(components, 4);
CFRetain(protocol);
}
}
if (ret_protocol != NULL) {
*ret_protocol = protocol;
}
my_CFRelease(&components);
return serviceID;
}
static boolean_t
ipdict_is_routable(CFDictionaryRef entity_dict)
{
RouteListRef routes;
routes = ipdict_get_routelist(entity_dict);
if (routes == NULL) {
return FALSE;
}
if ((routes->flags & kRouteListFlagsHasDefault) == 0) {
return FALSE;
}
if ((routes->flags & kRouteListFlagsExcludeNWI) != 0) {
return FALSE;
}
return TRUE;
}
__private_extern__ boolean_t
service_is_routable(CFDictionaryRef service_dict, int af)
{
boolean_t contains_protocol;
CFStringRef entity;
CFDictionaryRef entity_dict;
entity = (af == AF_INET) ? kSCEntNetIPv4 : kSCEntNetIPv6;
entity_dict = CFDictionaryGetValue(service_dict, entity);
if (entity_dict == NULL) {
return FALSE;
}
contains_protocol = ipdict_is_routable(entity_dict);
return contains_protocol;
}
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);
}
__private_extern__ boolean_t
service_is_scoped_only(CFDictionaryRef service_dict)
{
nwi_ifstate_t alias;
CFDictionaryRef dict;
char ifname[IFNAMSIZ];
nwi_ifstate_t ifstate;
CFStringRef interface = NULL;
dict = CFDictionaryGetValue(service_dict, kSCEntNetIPv4);
if (dict == NULL) {
dict = CFDictionaryGetValue(service_dict, kSCEntNetIPv6);
}
if (dict == NULL) {
return FALSE;
}
interface = ipdict_get_ifname(dict);
if ((interface == NULL) ||
!CFStringGetCString(interface, ifname, sizeof(ifname), kCFStringEncodingUTF8)) {
return FALSE;
}
#ifdef TEST_DNS
if (S_nwi_state == NULL) {
S_nwi_state = nwi_state_copy();
}
#endif // TEST_DNS
ifstate = nwi_state_get_ifstate(S_nwi_state, ifname);
if (ifstate == NULL) {
return FALSE;
} else if ((ifstate->flags & NWI_IFSTATE_FLAGS_NOT_IN_LIST) != 0) {
return TRUE;
}
alias = nwi_ifstate_get_alias(ifstate, ifstate->af == AF_INET ? AF_INET6 : AF_INET);
if (alias == NULL) {
return FALSE;
} else if ((alias->flags & NWI_IFSTATE_FLAGS_NOT_IN_LIST) != 0) {
return TRUE;
}
return FALSE;
}
static void
log_service_entity(int level, CFStringRef serviceID, CFStringRef entity,
CFStringRef operation, CFTypeRef val)
{
CFMutableStringRef this_val = NULL;
if (val != NULL) {
boolean_t is_ipv4;
boolean_t is_ipv6;
if ((is_ipv4 = CFEqual(entity, kSCEntNetIPv4))
|| (is_ipv6 = CFEqual(entity, kSCEntNetIPv6))) {
RouteListUnion routes;
routes.ptr = ipdict_get_routelist(val);
if (routes.ptr != NULL) {
CFDictionaryRef service_dict = NULL;
if (is_ipv4) {
this_val = IPv4RouteListCopyDescription(routes.v4);
}
else {
this_val = IPv6RouteListCopyDescription(routes.v6);
}
service_dict = ipdict_get_service(val);
if (service_dict != NULL) {
CFStringAppendFormat(this_val, NULL,
CFSTR("\n<Service> = %@"),
service_dict);
}
val = this_val;
}
}
}
if (val == NULL) {
val = CFSTR("<none>");
}
my_log(level, "serviceID %@ %@ %@ value = %@",
serviceID, operation, entity, val);
my_CFRelease(&this_val);
return;
}
static boolean_t
service_dict_set(CFStringRef serviceID, CFStringRef entity,
CFTypeRef new_val)
{
boolean_t changed = FALSE;
CFTypeRef old_val;
CFMutableDictionaryRef service_dict;
service_dict = service_dict_copy(serviceID);
old_val = CFDictionaryGetValue(service_dict, entity);
if (new_val == NULL) {
if (old_val != NULL) {
if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
log_service_entity(LOG_DEBUG, serviceID, entity,
CFSTR("Removed:"), old_val);
}
CFDictionaryRemoveValue(service_dict, entity);
changed = TRUE;
}
}
else {
if (old_val == NULL || !CFEqual(new_val, old_val)) {
if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
log_service_entity(LOG_DEBUG, serviceID, entity,
CFSTR("Changed: old"), old_val);
log_service_entity(LOG_DEBUG, serviceID, entity,
CFSTR("Changed: new"), new_val);
}
CFDictionarySetValue(service_dict, entity, new_val);
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;
if (S_service_state_dict == NULL) {
return (NULL);
}
service_dict = CFDictionaryGetValue(S_service_state_dict, serviceID);
if (service_dict == NULL) {
return (NULL);
}
return (CFDictionaryGetValue(service_dict, entity));
}
#if !TARGET_OS_SIMULATOR
static CFStringRef
service_copy_interface(CFStringRef serviceID, CFDictionaryRef new_service)
{
CFDictionaryRef dict;
CFStringRef interface = NULL;
if (new_service != NULL) {
interface = ipdict_get_ifname(new_service);
}
if (interface == NULL) {
dict = service_dict_get(serviceID, kSCEntNetIPv4);
if (dict != NULL) {
interface = ipdict_get_ifname(dict);
}
}
if (interface == NULL) {
dict = service_dict_get(serviceID, kSCEntNetIPv6);
if (dict != NULL) {
interface = ipdict_get_ifname(dict);
}
}
if (interface != NULL) {
CFRetain(interface);
}
return interface;
}
#endif
static boolean_t
service_has_clat46_address(CFStringRef serviceID)
{
CFDictionaryRef ip_dict;
ip_dict = service_dict_get(serviceID, kSCEntNetIPv4);
if (ip_dict != NULL) {
CFBooleanRef clat46 = NULL;
CFDictionaryRef ipv4;
ipv4 = ipdict_get_service(ip_dict);
if (isA_CFDictionary(ipv4) &&
CFDictionaryGetValueIfPresent(ipv4,
kSCPropNetIPv4CLAT46,
(const void **)&clat46) &&
isA_CFBoolean(clat46)) {
return CFBooleanGetValue(clat46);
}
}
return FALSE;
}
#ifndef kSCPropNetHostname
#define kSCPropNetHostname CFSTR("Hostname")
#endif
__private_extern__
CFStringRef
copy_dhcp_hostname(CFStringRef serviceID)
{
CFDictionaryRef dict = NULL;
CFStringRef hostname = NULL;
CFDictionaryRef service_dict = NULL;
dict = service_dict_get(serviceID, kSCEntNetIPv4);
if (dict == NULL) {
return (NULL);
}
service_dict = ipdict_get_service(dict);
if (service_dict == NULL) {
return (NULL);
}
hostname = CFDictionaryGetValue(service_dict, kSCPropNetHostname);
if (hostname != NULL) {
CFRetain(hostname);
}
return (hostname);
}
#if !TARGET_OS_SIMULATOR
static struct in6_addr *
ipv6_service_get_router(CFDictionaryRef service,
IFIndex * ifindex_p, CFStringRef * ifname_p)
{
IPv6RouteListRef routes;
struct in6_addr * router = NULL;
routes = ipdict_get_routelist(service);
if (routes != NULL
&& (routes->flags & kRouteListFlagsExcludeNWI) == 0
&& (routes->flags & kRouteListFlagsHasDefault) != 0) {
router = &routes->list[0].gateway;
if (*ifindex_p == 0) {
*ifindex_p = routes->list[0].ifindex;
}
if (*ifname_p == NULL) {
*ifname_p = ipdict_get_ifname(service);
}
}
return (router);
}
static void
ipv6_service_update_router(CFStringRef serviceID, CFDictionaryRef new_service)
{
IFIndex ifindex = 0;
CFStringRef ifname = NULL;
char ntopbuf[INET6_ADDRSTRLEN];
CFDictionaryRef old_service;
struct in6_addr * old_router;
struct in6_addr * new_router;
int s = -1;
old_service = service_dict_get(serviceID, kSCEntNetIPv6);
old_router = ipv6_service_get_router(old_service, &ifindex, &ifname);
new_router = ipv6_service_get_router(new_service, &ifindex, &ifname);
if (ifname == NULL || ifindex == 0) {
return;
}
s = inet6_dgram_socket();
if (s < 0) {
goto done;
}
if (old_router != NULL
&& (new_router == NULL
|| !IN6_ARE_ADDR_EQUAL(old_router, new_router))) {
if (siocdrdel_in6(s, ifindex, old_router) < 0) {
my_log((errno == EINVAL) ? LOG_DEBUG : LOG_ERR,
"siocdrdel_in6(%@, %s) failed: %s",
ifname,
inet_ntop(AF_INET6, old_router,
ntopbuf, sizeof(ntopbuf)),
strerror(errno));
}
else {
my_log(LOG_INFO,
"%@ removed default route %s",
ifname,
inet_ntop(AF_INET6, old_router, ntopbuf, sizeof(ntopbuf)));
}
}
if (new_router != NULL
&& (old_router == NULL
|| !IN6_ARE_ADDR_EQUAL(old_router, new_router))) {
if (siocdradd_in6(s, ifindex, new_router, 0) < 0) {
my_log((errno == EINVAL) ? LOG_DEBUG : LOG_ERR,
"siocdradd_in6(%@, %s) failed: %s",
ifname,
inet_ntop(AF_INET6, new_router,
ntopbuf, sizeof(ntopbuf)),
strerror(errno));
}
else {
my_log(LOG_INFO,
"%@ added default route %s",
ifname,
inet_ntop(AF_INET6, new_router, ntopbuf, sizeof(ntopbuf)));
}
}
close(s);
done:
return;
}
#endif
#define ALLOW_EMPTY_STRING 0x1
static CF_RETURNS_RETAINED CFTypeRef
sanitize_prop(CFTypeRef val, uint32_t flags)
{
if (val != NULL) {
if (isA_CFString(val)) {
CFMutableStringRef str;
str = CFStringCreateMutableCopy(NULL, 0, (CFStringRef)val);
CFStringTrimWhitespace(str);
if (!(flags & ALLOW_EMPTY_STRING) && (CFStringGetLength(str) == 0)) {
CFRelease(str);
str = NULL;
}
val = str;
} else {
CFRetain(val);
}
}
return val;
}
static void
merge_array_prop(CFMutableDictionaryRef dict,
CFStringRef key,
CFDictionaryRef state_dict,
CFDictionaryRef setup_dict,
uint32_t flags,
Boolean append)
{
CFMutableArrayRef merge_prop;
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)) {
return;
}
merge_prop = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
if (setup_prop != NULL) {
CFIndex i;
CFIndex n;
n = CFArrayGetCount(setup_prop);
for (i = 0; i < n; i++) {
CFTypeRef val;
val = CFArrayGetValueAtIndex(setup_prop, i);
val = sanitize_prop(val, flags);
if (val != NULL) {
CFArrayAppendValue(merge_prop, val);
CFRelease(val);
}
}
}
if (state_prop != NULL
&& (setup_prop == NULL || S_append_state)) {
CFIndex i;
CFIndex n;
CFRange setup_range = CFRangeMake(0, CFArrayGetCount(merge_prop));
n = CFArrayGetCount(state_prop);
for (i = 0; i < n; i++) {
CFTypeRef val;
val = CFArrayGetValueAtIndex(state_prop, i);
val = sanitize_prop(val, flags);
if (val != NULL) {
if (append || !CFArrayContainsValue(merge_prop, setup_range, val)) {
CFArrayAppendValue(merge_prop, val);
}
CFRelease(val);
}
}
}
if (CFArrayGetCount(merge_prop) > 0) {
CFDictionarySetValue(dict, key, merge_prop);
}
CFRelease(merge_prop);
return;
}
static void
pick_prop(CFMutableDictionaryRef dict,
CFStringRef key,
CFDictionaryRef state_dict,
CFDictionaryRef setup_dict,
uint32_t flags)
{
CFTypeRef val = NULL;
if (setup_dict != NULL) {
val = CFDictionaryGetValue(setup_dict, key);
val = sanitize_prop(val, flags);
}
if (val == NULL && state_dict != NULL) {
val = CFDictionaryGetValue(state_dict, key);
val = sanitize_prop(val, flags);
}
if (val != NULL) {
CFDictionarySetValue(dict, key, val);
CFRelease(val);
}
return;
}
#define IPV4_ROUTES_N_STATIC 5
#define IPV4_ROUTES_ALIGN_BUF_SIZE_UINT32 \
(roundup(IPv4RouteListComputeSize(IPV4_ROUTES_N_STATIC), \
sizeof(uint32_t)) \
/ sizeof(uint32_t))
#define IPV4_ROUTES_BUF_DECL(routes) \
IPv4RouteListRef routes; \
uint32_t routes_buf[IPV4_ROUTES_ALIGN_BUF_SIZE_UINT32]; \
\
routes = (IPv4RouteListRef)(void *)routes_buf; \
routes->size = IPV4_ROUTES_N_STATIC; \
routes->count = 0; \
routes->flags = 0;
static CFDataRef
IPv4RouteListDataCreate(CFDictionaryRef dict, CFNumberRef rank_assertion)
{
IPv4RouteListRef r;
CFDataRef routes_data;
IPV4_ROUTES_BUF_DECL(routes);
r = IPv4RouteListCreateWithDictionary(routes, dict, rank_assertion);
if (r != NULL) {
routes_data = CFDataCreate(NULL,
(const void *)r,
IPv4RouteListComputeSize(r->count));
if (r != routes) {
free(r);
}
}
else {
routes_data = NULL;
}
return (routes_data);
}
#define IPV6_ROUTES_N_STATIC 3
#define IPV6_ROUTES_ALIGN_BUF_SIZE_UINT32 \
(roundup(IPv6RouteListComputeSize(IPV6_ROUTES_N_STATIC), \
sizeof(uint32_t)) \
/ sizeof(uint32_t))
#define IPV6_ROUTES_BUF_DECL(routes) \
IPv6RouteListRef routes; \
uint32_t routes_buf[IPV6_ROUTES_ALIGN_BUF_SIZE_UINT32]; \
\
routes = (IPv6RouteListRef)(void *)routes_buf; \
routes->size = IPV6_ROUTES_N_STATIC; \
routes->count = 0; \
routes->flags = 0;
static CFDataRef
IPv6RouteListDataCreate(CFDictionaryRef dict, CFNumberRef rank_assertion)
{
IPv6RouteListRef r;
CFDataRef routes_data;
IPV6_ROUTES_BUF_DECL(routes);
r = IPv6RouteListCreateWithDictionary(routes, dict, rank_assertion);
if (r != NULL) {
routes_data = CFDataCreate(NULL,
(const void *)r,
IPv6RouteListComputeSize(r->count));
if (r != routes) {
free(r);
}
}
else {
routes_data = NULL;
}
return (routes_data);
}
static CFDictionaryRef
IPDictCreate(int af, _Nonnull CFDictionaryRef state_dict,
CFDictionaryRef setup_dict,
CFNumberRef rank_assertion)
{
CFDictionaryRef aggregated_dict = NULL;
CFDictionaryRef dict;
CFMutableDictionaryRef modified_dict = NULL;
CFDataRef routes_data;
dict = state_dict;
if (setup_dict != NULL) {
CFArrayRef additional_routes;
CFStringRef route_list_prop;
CFStringRef router;
in_addr router_ip;
CFStringRef router_prop;
switch (af) {
case AF_INET:
router_prop = kSCPropNetIPv4Router;
route_list_prop = kSCPropNetIPv4AdditionalRoutes;
break;
default:
case AF_INET6:
router_prop = kSCPropNetIPv6Router;
route_list_prop = kSCPropNetIPv6AdditionalRoutes;
break;
}
router = CFDictionaryGetValue(setup_dict, router_prop);
if (router != NULL
&& !cfstring_to_ipvx(af, router, &router_ip, sizeof(router_ip))) {
router = NULL;
}
additional_routes
= CFDictionaryGetValue(setup_dict, route_list_prop);
additional_routes = isA_CFArray(additional_routes);
if (router != NULL || additional_routes != NULL) {
modified_dict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
if (router != NULL) {
CFDictionarySetValue(modified_dict,
router_prop,
router);
}
if (additional_routes != NULL) {
CFArrayRef combined_routes = NULL;
CFArrayRef state_routes;
state_routes
= CFDictionaryGetValue(state_dict,
route_list_prop);
if (isA_CFArray(state_routes) != NULL) {
combined_routes
= my_CFArrayCreateCombinedArray(additional_routes,
state_routes);
additional_routes = combined_routes;
}
CFDictionarySetValue(modified_dict,
route_list_prop,
additional_routes);
if (combined_routes != NULL) {
CFRelease(combined_routes);
}
}
dict = modified_dict;
}
}
switch (af) {
case AF_INET:
routes_data = IPv4RouteListDataCreate(dict, rank_assertion);
break;
default:
case AF_INET6:
routes_data = IPv6RouteListDataCreate(dict, rank_assertion);
break;
}
if (routes_data != NULL) {
aggregated_dict = ipdict_create(dict, routes_data);
CFRelease(routes_data);
}
if (modified_dict != NULL) {
CFRelease(modified_dict);
}
return (aggregated_dict);
}
static boolean_t
get_ipv4_changes(CFStringRef serviceID, CFDictionaryRef state_dict,
CFDictionaryRef setup_dict, CFDictionaryRef info)
{
#pragma unused(info)
CFDictionaryRef dict = NULL;
boolean_t changed = FALSE;
CFNumberRef rank_assertion = NULL;
CFDictionaryRef service_options;
if (state_dict == NULL) {
goto done;
}
service_options = service_dict_get(serviceID, kSCEntNetService);
if (service_options != NULL) {
rank_assertion
= CFDictionaryGetValue(service_options,
kServiceOptionRankAssertion);
}
dict = IPDictCreate(AF_INET, state_dict, setup_dict, rank_assertion);
done:
changed = service_dict_set(serviceID, kSCEntNetIPv4, dict);
if (dict == NULL) {
CFDictionaryRemoveValue(S_ipv4_service_rank_dict, serviceID);
}
my_CFRelease(&dict);
return (changed);
}
static boolean_t
get_ipv6_changes(CFStringRef serviceID, CFDictionaryRef state_dict,
CFDictionaryRef setup_dict, CFDictionaryRef info)
{
#pragma unused(info)
CFDictionaryRef dict = NULL;
boolean_t changed = FALSE;
#if !TARGET_OS_SIMULATOR
CFStringRef interface;
#endif
CFNumberRef rank_assertion = NULL;
CFDictionaryRef service_options;
if (state_dict == NULL) {
goto done;
}
service_options = service_dict_get(serviceID, kSCEntNetService);
if (service_options != NULL) {
rank_assertion
= CFDictionaryGetValue(service_options,
kServiceOptionRankAssertion);
}
dict = IPDictCreate(AF_INET6, state_dict, setup_dict, rank_assertion);
done:
#if !TARGET_OS_SIMULATOR
interface = service_copy_interface(serviceID, dict);
ipv6_service_update_router(serviceID, dict);
#endif
changed = service_dict_set(serviceID, kSCEntNetIPv6, dict);
#if !TARGET_OS_SIMULATOR
if (interface != NULL) {
if (changed) {
CFBooleanRef needs_plat = NULL;
if (dict == NULL) {
set_plat_discovery(kPLATDiscoveryOptionCancel, interface);
} else if ((state_dict != NULL) &&
CFDictionaryGetValueIfPresent(state_dict,
kSCPropNetIPv6PerformPLATDiscovery,
(const void **)&needs_plat) &&
isA_CFBoolean(needs_plat) &&
CFBooleanGetValue(needs_plat)) {
set_plat_discovery(kPLATDiscoveryOptionStart, interface);
} else {
set_plat_discovery(kPLATDiscoveryOptionUpdate, interface);
}
}
CFRelease(interface);
}
#endif
if (dict == NULL) {
CFDictionaryRemoveValue(S_ipv6_service_rank_dict, serviceID);
}
my_CFRelease(&dict);
return (changed);
}
#ifdef TEST_DNS
__private_extern__ CFDictionaryRef
ipv4_dict_create(CFDictionaryRef state_dict)
{
return (IPDictCreate(AF_INET, state_dict, NULL, NULL, NULL));
}
__private_extern__ CFDictionaryRef
ipv6_dict_create(CFDictionaryRef state_dict)
{
return (IPDictCreate(AF_INET6, state_dict, NULL, NULL, NULL));
}
#endif
static void
accumulate_dns_servers(CFArrayRef in_servers, ProtocolFlags active_protos,
CFMutableArrayRef out_servers, CFStringRef interface)
{
CFIndex count;
CFIndex i;
count = CFArrayGetCount(in_servers);
for (i = 0; i < count; i++) {
CFStringRef addr;
struct in6_addr ipv6_addr;
struct in_addr ip_addr;
addr = CFArrayGetValueAtIndex(in_servers, i);
assert(addr != NULL);
if (cfstring_to_ip(addr, &ip_addr)) {
if ((active_protos & kProtocolFlagsIPv4) == 0
&& ntohl(ip_addr.s_addr) != INADDR_LOOPBACK) {
my_log(LOG_INFO,
"no IPv4 connectivity, "
"ignoring DNS server address " IP_FORMAT,
IP_LIST(&ip_addr));
continue;
}
CFRetain(addr);
}
else if (cfstring_to_ip6(addr, &ipv6_addr)) {
if ((active_protos & kProtocolFlagsIPv6) == 0
&& !IN6_IS_ADDR_LOOPBACK(&ipv6_addr)) {
char ntopbuf[INET6_ADDRSTRLEN];
my_log(LOG_INFO,
"no IPv6 connectivity, "
"ignoring DNS server address %s",
inet_ntop(AF_INET6, &ipv6_addr,
ntopbuf, sizeof(ntopbuf)));
continue;
}
if ((IN6_IS_ADDR_LINKLOCAL(&ipv6_addr) ||
IN6_IS_ADDR_MC_LINKLOCAL(&ipv6_addr))
&& (interface != NULL)
&& (CFStringFind(addr, CFSTR("%"), 0).location == kCFNotFound)) {
addr = CFStringCreateWithFormat(NULL, NULL,
CFSTR("%@%%%@"),
addr,
interface);
} else {
CFRetain(addr);
}
}
else {
my_log(LOG_NOTICE, "ignoring bad DNS server address '%@'", addr);
continue;
}
CFArrayAppendValue(out_servers, addr);
CFRelease(addr);
}
return;
}
static CF_RETURNS_RETAINED CFArrayRef
order_dns_servers(CFArrayRef servers, ProtocolFlags active_protos)
{
Boolean favor_v4 = FALSE;
CFMutableArrayRef ordered_servers;
ProtocolFlags proto_last = kProtocolFlagsIPv4;
struct sockaddr_in v4_dns1 = { .sin_family = AF_INET,
.sin_len = sizeof(struct sockaddr_in) };
CFIndex v4_n = 0;
struct sockaddr_in6 v6_dns1 = { .sin6_family = AF_INET6,
.sin6_len = sizeof(struct sockaddr_in6),
.sin6_scope_id = 0 };
CFIndex v6_n = 0;
if (((active_protos & kProtocolFlagsIPv4) == 0) ||
((active_protos & kProtocolFlagsIPv6) == 0)) {
#ifdef TEST_DNS_ORDER
printf("only one protocol\n");
#endif // TEST_DNS_ORDER
return CFRetain(servers);
}
ordered_servers = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
for (CFIndex i = 0, n = CFArrayGetCount(servers); i < n; i++) {
struct in_addr ia;
struct in6_addr ia6;
ProtocolFlags proto;
CFStringRef server;
server = CFArrayGetValueAtIndex(servers, i);
if (cfstring_to_ip(server, &ia)) {
proto = kProtocolFlagsIPv4;
if (v4_n++ == 0) {
v4_dns1.sin_addr = ia;
}
} else if (cfstring_to_ip6(server, &ia6)) {
proto = kProtocolFlagsIPv6;
if (v6_n++ == 0) {
memcpy(&v6_dns1.sin6_addr, &ia6, sizeof(ia6));
}
} else {
CFRelease(ordered_servers);
return CFRetain(servers);
}
if ((i > 0) && (proto != proto_last)) {
if (((proto == kProtocolFlagsIPv4) && (v4_n == 1)) ||
((proto == kProtocolFlagsIPv6) && (v6_n == 1))) {
favor_v4 = (sa_dst_compare_no_dependencies((struct sockaddr *)&v4_dns1,
(struct sockaddr *)&v6_dns1) >= 0);
#ifdef TEST_DNS_ORDER
char v4_buf[INET_ADDRSTRLEN];
char v6_buf[INET6_ADDRSTRLEN];
printf("comparing %s vs %s, favoring %s\n",
inet_ntop(v4_dns1.sin_family, &v4_dns1.sin_addr, v4_buf, sizeof(v4_buf)),
inet_ntop(v6_dns1.sin6_family, &v6_dns1.sin6_addr, v6_buf, sizeof(v6_buf)),
favor_v4 ? "v4" : "v6");
#endif // TEST_DNS_ORDER
} else {
#ifdef TEST_DNS_ORDER
printf("v4/v6 not ordered\n");
#endif // TEST_DNS_ORDER
CFRelease(ordered_servers);
return CFRetain(servers);
}
}
proto_last = proto;
if ((proto == kProtocolFlagsIPv4) && favor_v4) {
CFArrayInsertValueAtIndex(ordered_servers, v4_n - 1, server);
} else if ((proto == kProtocolFlagsIPv6) && !favor_v4) {
CFArrayInsertValueAtIndex(ordered_servers, v6_n - 1, server);
} else {
CFArrayAppendValue(ordered_servers, server);
}
}
return ordered_servers;
}
static void
merge_dns_servers(CFMutableDictionaryRef new_dict,
CFArrayRef state_servers,
CFArrayRef setup_servers,
Boolean have_setup,
Boolean trust_state,
ProtocolFlags active_protos,
CFStringRef interface)
{
CFMutableArrayRef dns_servers;
Boolean have_dns_setup = FALSE;
if (state_servers == NULL && setup_servers == NULL) {
return;
}
dns_servers = CFArrayCreateMutable(NULL, 0,
&kCFTypeArrayCallBacks);
if (setup_servers != NULL) {
accumulate_dns_servers(setup_servers, active_protos,
dns_servers, interface);
if (CFArrayGetCount(dns_servers) > 0) {
have_dns_setup = TRUE;
}
}
if ((CFArrayGetCount(dns_servers) == 0 || S_append_state)
&& state_servers != NULL) {
CFArrayRef ordered_servers;
ordered_servers = order_dns_servers(state_servers, active_protos);
accumulate_dns_servers(ordered_servers, active_protos,
dns_servers, NULL);
CFRelease(ordered_servers);
}
if (CFArrayGetCount(dns_servers) != 0) {
CFDictionarySetValue(new_dict,
kSCPropNetDNSServerAddresses, dns_servers);
if ((have_setup && !have_dns_setup) || (!have_setup && trust_state)) {
CFDictionarySetValue(new_dict, DNS_CONFIGURATION_SCOPED_QUERY_KEY, kCFBooleanTrue);
}
}
my_CFRelease(&dns_servers);
return;
}
static boolean_t
get_dns_changes(CFStringRef serviceID, CFDictionaryRef state_dict,
CFDictionaryRef setup_dict, CFDictionaryRef info)
{
ProtocolFlags active_protos = kProtocolFlagsNone;
boolean_t changed = FALSE;
CFStringRef domain;
Boolean have_setup = FALSE;
CFStringRef interface = NULL;
CFDictionaryRef ipv4;
CFDictionaryRef ipv6;
const struct {
CFStringRef key;
uint32_t flags;
Boolean append;
} merge_list[] = {
{ kSCPropNetDNSSearchDomains, 0, FALSE },
{ kSCPropNetDNSSortList, 0, FALSE },
{ kSCPropNetDNSSupplementalMatchDomains, ALLOW_EMPTY_STRING, TRUE },
{ kSCPropNetDNSSupplementalMatchOrders, 0, TRUE },
};
CFMutableDictionaryRef new_dict = NULL;
const CFStringRef pick_list[] = {
kSCPropNetDNSDomainName,
kSCPropNetDNSOptions,
kSCPropNetDNSSearchOrder,
kSCPropNetDNSServerPort,
kSCPropNetDNSServerTimeout,
kSCPropNetDNSServiceIdentifier,
kSCPropNetDNSSupplementalMatchDomainsNoSearch,
};
Boolean trust_state = FALSE;
if ((state_dict == NULL) && (setup_dict == NULL)) {
goto done;
}
ipv4 = service_dict_get(serviceID, kSCEntNetIPv4);
if (ipdict_is_routable(ipv4)) {
if (get_service_setup_entity(info, serviceID, kSCEntNetIPv4) != NULL) {
have_setup = TRUE;
}
active_protos |= kProtocolFlagsIPv4;
interface = ipdict_get_ifname(ipv4);
}
ipv6 = service_dict_get(serviceID, kSCEntNetIPv6);
if (ipdict_is_routable(ipv6)) {
if (!have_setup &&
(get_service_setup_entity(info, serviceID, kSCEntNetIPv6) != NULL)) {
have_setup = TRUE;
}
active_protos |= kProtocolFlagsIPv6;
if (interface == NULL) {
interface = ipdict_get_ifname(ipv6);
}
}
if (active_protos == kProtocolFlagsNone) {
if (state_dict == NULL) {
goto done;
}
setup_dict = NULL;
}
if (state_dict != NULL) {
CFStringRef state_serviceID = NULL;
if (CFDictionaryGetValueIfPresent(state_dict,
kSCPropNetDNSConfirmedServiceID,
(const void **)&state_serviceID) &&
isA_CFString(state_serviceID) &&
CFEqual(serviceID, state_serviceID)) {
trust_state = TRUE;
}
}
new_dict = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (active_protos == kProtocolFlagsNone) {
merge_dns_servers(new_dict,
my_CFDictionaryGetArray(state_dict,
kSCPropNetDNSServerAddresses),
NULL,
FALSE,
trust_state,
kProtocolFlagsIPv4 | kProtocolFlagsIPv6,
NULL);
}
else {
merge_dns_servers(new_dict,
my_CFDictionaryGetArray(state_dict,
kSCPropNetDNSServerAddresses),
my_CFDictionaryGetArray(setup_dict,
kSCPropNetDNSServerAddresses),
have_setup,
trust_state,
active_protos,
interface);
}
for (size_t i = 0; i < countof(merge_list); i++) {
merge_array_prop(new_dict,
merge_list[i].key,
state_dict,
setup_dict,
merge_list[i].flags,
merge_list[i].append);
}
for (size_t i = 0; i < countof(pick_list); i++) {
pick_prop(new_dict,
pick_list[i],
state_dict,
setup_dict,
0);
}
if (active_protos == kProtocolFlagsNone) {
if (CFDictionaryContainsKey(new_dict,
kSCPropNetDNSSupplementalMatchDomains)) {
CFDictionaryRemoveValue(new_dict, kSCPropNetDNSDomainName);
CFDictionaryRemoveValue(new_dict, kSCPropNetDNSSearchDomains);
CFDictionaryRemoveValue(new_dict, kSCPropNetDNSSearchOrder);
CFDictionaryRemoveValue(new_dict, kSCPropNetDNSSortList);
if ((interface == NULL) && (setup_dict == NULL) && (state_dict != NULL)) {
interface = CFDictionaryGetValue(state_dict, kSCPropInterfaceName);
}
} else if (CFDictionaryContainsKey(new_dict, kSCPropNetDNSServiceIdentifier) &&
(interface == NULL) &&
(state_dict != NULL)) {
interface = CFDictionaryGetValue(state_dict, kSCPropInterfaceName);
} else {
goto done;
}
}
if (CFDictionaryGetCount(new_dict) == 0) {
my_CFRelease(&new_dict);
goto done;
}
if (interface != NULL) {
CFDictionarySetValue(new_dict, kSCPropInterfaceName, interface);
}
if (S_append_state) {
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:
#if !TARGET_OS_SIMULATOR
if (interface != NULL) {
CFRetain(interface);
}
#endif
changed = service_dict_set(serviceID, kSCEntNetDNS, new_dict);
#if !TARGET_OS_SIMULATOR
if (interface != NULL) {
if (changed) {
if ((active_protos & kProtocolFlagsIPv6) != 0) {
set_plat_discovery(kPLATDiscoveryOptionUpdate, interface);
}
}
CFRelease(interface);
}
#endif
my_CFRelease(&new_dict);
return (changed);
}
static void
merge_dict(const void *key, const void *value, void *context)
{
CFMutableDictionaryRef dict = (CFMutableDictionaryRef)context;
CFDictionarySetValue(dict, key, value);
return;
}
#define PROXY_AUTO_DISCOVERY_URL 252
static CF_RETURNS_RETAINED CFStringRef
wpadURL_dhcp(CFDictionaryRef dhcp_options)
{
CFStringRef urlString = NULL;
if (dhcp_options != NULL) {
CFDataRef data;
data = DHCPInfoGetOptionData(dhcp_options, PROXY_AUTO_DISCOVERY_URL);
if (data != NULL) {
CFURLRef url;
const UInt8 *urlBytes;
CFIndex urlLen;
urlBytes = CFDataGetBytePtr(data);
urlLen = CFDataGetLength(data);
while ((urlLen > 0) && (urlBytes[urlLen - 1] == 0)) {
urlLen--;
}
if (urlLen <= 0) {
return NULL;
}
url = CFURLCreateWithBytes(NULL, urlBytes, urlLen, kCFStringEncodingUTF8, NULL);
if (url != NULL) {
urlString = CFURLGetString(url);
if (urlString != NULL) {
CFRetain(urlString);
}
CFRelease(url);
}
}
}
return urlString;
}
static CF_RETURNS_RETAINED CFStringRef
wpadURL_dns(void)
{
CFURLRef url;
CFStringRef urlString = NULL;
url = CFURLCreateWithString(NULL, CFSTR("http://wpad/wpad.dat"), NULL);
if (url != NULL) {
urlString = CFURLGetString(url);
if (urlString != NULL) {
CFRetain(urlString);
}
CFRelease(url);
}
return urlString;
}
static boolean_t
get_proxies_changes(CFStringRef serviceID, CFDictionaryRef state_dict,
CFDictionaryRef setup_dict, CFDictionaryRef info)
{
ProtocolFlags active_protos = kProtocolFlagsNone;
boolean_t changed = FALSE;
CFStringRef interface = NULL;
CFDictionaryRef ipv4;
CFDictionaryRef ipv6;
CFMutableDictionaryRef new_dict = NULL;
const struct {
CFStringRef key;
uint32_t flags;
Boolean append;
} merge_list[] = {
{ kSCPropNetProxiesSupplementalMatchDomains, ALLOW_EMPTY_STRING, TRUE },
{ kSCPropNetProxiesSupplementalMatchOrders, 0, TRUE },
};
const struct {
CFStringRef key1;
CFStringRef key2;
CFStringRef key3;
} pick_list[] = {
{ kSCPropNetProxiesFTPEnable, kSCPropNetProxiesFTPProxy, kSCPropNetProxiesFTPPort },
{ kSCPropNetProxiesGopherEnable, kSCPropNetProxiesGopherProxy, kSCPropNetProxiesGopherPort },
{ kSCPropNetProxiesHTTPEnable, kSCPropNetProxiesHTTPProxy, kSCPropNetProxiesHTTPPort },
{ kSCPropNetProxiesHTTPSEnable, kSCPropNetProxiesHTTPSProxy, kSCPropNetProxiesHTTPSPort },
{ kSCPropNetProxiesRTSPEnable, kSCPropNetProxiesRTSPProxy, kSCPropNetProxiesRTSPPort },
{ kSCPropNetProxiesSOCKSEnable, kSCPropNetProxiesSOCKSProxy, kSCPropNetProxiesSOCKSPort },
{ kSCPropNetProxiesProxyAutoConfigEnable,
kSCPropNetProxiesProxyAutoConfigURLString,
kSCPropNetProxiesProxyAutoConfigJavaScript, },
{ kSCPropNetProxiesProxyAutoDiscoveryEnable,
NULL,
NULL, }
};
if ((state_dict == NULL) && (setup_dict == NULL)) {
goto done;
}
ipv4 = service_dict_get(serviceID, kSCEntNetIPv4);
if (ipdict_is_routable(ipv4)) {
active_protos |= kProtocolFlagsIPv4;
interface = ipdict_get_ifname(ipv4);
}
ipv6 = service_dict_get(serviceID, kSCEntNetIPv6);
if (ipdict_is_routable(ipv6)) {
active_protos |= kProtocolFlagsIPv6;
if (interface == NULL) {
interface = ipdict_get_ifname(ipv6);
}
}
if (active_protos == kProtocolFlagsNone) {
if (state_dict == NULL) {
goto done;
}
setup_dict = NULL;
}
if ((setup_dict != NULL) && (state_dict != NULL)) {
CFMutableDictionaryRef setup_copy;
new_dict = CFDictionaryCreateMutableCopy(NULL, 0, state_dict);
for (size_t i = 0; i < countof(merge_list); i++) {
merge_array_prop(new_dict,
merge_list[i].key,
state_dict,
setup_dict,
merge_list[i].flags,
merge_list[i].append);
}
setup_copy = CFDictionaryCreateMutableCopy(NULL, 0, setup_dict);
for (size_t i = 0; i < countof(pick_list); i++) {
if (CFDictionaryContainsKey(setup_copy, pick_list[i].key1)) {
CFDictionaryRemoveValue(new_dict, pick_list[i].key1);
if (pick_list[i].key2 != NULL) {
CFDictionaryRemoveValue(new_dict, pick_list[i].key2);
}
if (pick_list[i].key3 != NULL) {
CFDictionaryRemoveValue(new_dict, pick_list[i].key3);
}
} else if (CFDictionaryContainsKey(state_dict, pick_list[i].key1) ||
((pick_list[i].key2 != NULL) && CFDictionaryContainsKey(state_dict, pick_list[i].key2)) ||
((pick_list[i].key3 != NULL) && CFDictionaryContainsKey(state_dict, pick_list[i].key3))) {
CFDictionaryRemoveValue(setup_copy, pick_list[i].key1);
if (pick_list[i].key2 != NULL) {
CFDictionaryRemoveValue(setup_copy, pick_list[i].key2);
}
if (pick_list[i].key3 != NULL) {
CFDictionaryRemoveValue(setup_copy, pick_list[i].key3);
}
}
}
CFDictionaryApplyFunction(setup_copy, merge_dict, new_dict);
CFRelease(setup_copy);
}
else if (setup_dict != NULL) {
new_dict = CFDictionaryCreateMutableCopy(NULL, 0, setup_dict);
}
else if (state_dict != NULL) {
new_dict = CFDictionaryCreateMutableCopy(NULL, 0, state_dict);
}
if ((new_dict != NULL) && (CFDictionaryGetCount(new_dict) == 0)) {
CFRelease(new_dict);
new_dict = NULL;
}
if ((new_dict != NULL) && (interface != NULL)) {
CFDictionarySetValue(new_dict, kSCPropInterfaceName, interface);
}
if (new_dict != NULL) {
CFDictionaryRef dhcp_options;
CFNumberRef num;
CFNumberRef wpad = NULL;
int wpadEnabled = 0;
CFStringRef wpadURL = NULL;
if (CFDictionaryGetValueIfPresent(new_dict,
kSCPropNetProxiesProxyAutoDiscoveryEnable,
(const void **)&num) &&
isA_CFNumber(num)) {
wpad = num;
if (!CFNumberGetValue(num, kCFNumberIntType, &wpadEnabled)) {
wpadEnabled = 0;
}
}
if (wpadEnabled) {
int pacEnabled = 0;
num = CFDictionaryGetValue(new_dict, kSCPropNetProxiesProxyAutoConfigEnable);
if (!isA_CFNumber(num) ||
!CFNumberGetValue(num, kCFNumberIntType, &pacEnabled)) {
pacEnabled = 0;
}
if (pacEnabled) {
CFStringRef pacURL;
pacURL = CFDictionaryGetValue(new_dict, kSCPropNetProxiesProxyAutoConfigURLString);
if (pacURL != NULL) {
if (!isA_CFString(pacURL) || (CFStringGetLength(pacURL) == 0)) {
pacEnabled = 0;
}
} else {
CFStringRef pacJS;
pacJS = CFDictionaryGetValue(new_dict, kSCPropNetProxiesProxyAutoConfigJavaScript);
if (!isA_CFString(pacJS) || (CFStringGetLength(pacJS) == 0)) {
pacEnabled = 0;
}
}
}
if (pacEnabled) {
wpadEnabled = 0;
goto setWPAD;
}
dhcp_options = get_service_state_entity(info, serviceID, kSCEntNetDHCP);
wpadURL = wpadURL_dhcp(dhcp_options);
if (wpadURL == NULL) {
wpadURL = wpadURL_dns();
}
if (wpadURL == NULL) {
wpadEnabled = 0;
goto setWPAD;
}
pacEnabled = 1;
num = CFNumberCreate(NULL, kCFNumberIntType, &pacEnabled);
CFDictionarySetValue(new_dict,
kSCPropNetProxiesProxyAutoConfigEnable,
num);
CFRelease(num);
CFDictionarySetValue(new_dict,
kSCPropNetProxiesProxyAutoConfigURLString,
wpadURL);
CFRelease(wpadURL);
}
setWPAD:
if (wpad != NULL) {
num = CFNumberCreate(NULL, kCFNumberIntType, &wpadEnabled);
CFDictionarySetValue(new_dict,
kSCPropNetProxiesProxyAutoDiscoveryEnable,
num);
CFRelease(num);
}
}
done:
changed = service_dict_set(serviceID, kSCEntNetProxies, new_dict);
my_CFRelease(&new_dict);
return (changed);
}
#if !TARGET_OS_IPHONE
static boolean_t
get_smb_changes(CFStringRef serviceID, CFDictionaryRef state_dict,
CFDictionaryRef setup_dict, CFDictionaryRef info)
{
#pragma unused(info)
boolean_t changed = FALSE;
CFMutableDictionaryRef new_dict = NULL;
const CFStringRef pick_list[] = {
kSCPropNetSMBNetBIOSName,
kSCPropNetSMBNetBIOSNodeType,
#ifdef ADD_NETBIOS_SCOPE
kSCPropNetSMBNetBIOSScope,
#endif // ADD_NETBIOS_SCOPE
kSCPropNetSMBWorkgroup,
};
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);
merge_array_prop(new_dict,
kSCPropNetSMBWINSAddresses,
state_dict,
setup_dict,
0,
FALSE);
for (size_t i = 0; i < countof(pick_list); i++) {
pick_prop(new_dict,
pick_list[i],
state_dict,
setup_dict,
0);
}
if (CFDictionaryGetCount(new_dict) == 0) {
my_CFRelease(&new_dict);
goto done;
}
done:
changed = service_dict_set(serviceID, kSCEntNetSMB, new_dict);
my_CFRelease(&new_dict);
return (changed);
}
#endif
static CFStringRef
services_info_get_interface(CFDictionaryRef services_info,
CFStringRef serviceID)
{
CFStringRef interface = NULL;
CFDictionaryRef ipv4_dict;
ipv4_dict = get_service_state_entity(services_info, serviceID,
kSCEntNetIPv4);
if (ipv4_dict != NULL) {
interface = CFDictionaryGetValue(ipv4_dict, kSCPropInterfaceName);
}
else {
CFDictionaryRef ipv6_dict;
ipv6_dict = get_service_state_entity(services_info, serviceID,
kSCEntNetIPv6);
if (ipv6_dict != NULL) {
interface = CFDictionaryGetValue(ipv6_dict, kSCPropInterfaceName);
}
}
return (interface);
}
static const struct {
const CFStringRef * entityName;
const CFStringRef * statusKey;
} transientServiceInfo[] = {
{ &kSCEntNetIPSec, &kSCPropNetIPSecStatus },
{ &kSCEntNetPPP, &kSCPropNetPPPStatus },
{ &kSCEntNetVPN, &kSCPropNetVPNStatus },
};
static Boolean
get_transient_status_changes(CFStringRef serviceID,
CFDictionaryRef services_info)
{
boolean_t changed = FALSE;
for (size_t i = 0; i < countof(transientServiceInfo); i++) {
CFDictionaryRef dict;
CFNumberRef status = NULL;
CFMutableDictionaryRef ts_dict = NULL;
dict = get_service_state_entity(services_info, serviceID,
*transientServiceInfo[i].entityName);
if (dict != NULL) {
status = CFDictionaryGetValue(dict,
*transientServiceInfo[i].statusKey);
}
if (isA_CFNumber(status) != NULL) {
ts_dict = CFDictionaryCreateMutable(NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(ts_dict,
*transientServiceInfo[i].statusKey,
status);
}
if (service_dict_set(serviceID, *transientServiceInfo[i].entityName,
ts_dict)) {
changed = TRUE;
}
if (ts_dict != NULL) {
CFRelease(ts_dict);
}
}
return (changed);
}
static boolean_t
if_dict_is_expensive(CFDictionaryRef if_dict)
{
boolean_t is_expensive = FALSE;
if (isA_CFDictionary(if_dict) != NULL) {
CFBooleanRef expensive;
expensive = CFDictionaryGetValue(if_dict, kSCPropNetLinkExpensive);
if (isA_CFBoolean(expensive) != NULL
&& CFBooleanGetValue(expensive)) {
is_expensive = TRUE;
}
}
return is_expensive;
}
static boolean_t
service_is_expensive(CFStringRef serviceID, CFDictionaryRef services_info)
{
CFStringRef ifname;
boolean_t is_expensive = FALSE;
ifname = services_info_get_interface(services_info, serviceID);
if (ifname != NULL) {
CFDictionaryRef if_dict;
CFStringRef key;
key = interface_entity_key_copy(ifname, kSCEntNetLink);
if_dict = CFDictionaryGetValue(services_info, key);
CFRelease(key);
is_expensive = if_dict_is_expensive(if_dict);
}
return (is_expensive);
}
static boolean_t
interface_is_expensive(CFStringRef ifname)
{
boolean_t is_expensive = FALSE;
if (ifname != NULL) {
CFDictionaryRef if_dict;
CFStringRef key;
key = interface_entity_key_copy(ifname, kSCEntNetLink);
if_dict = SCDynamicStoreCopyValue(S_session, key);
CFRelease(key);
if (if_dict != NULL) {
is_expensive = if_dict_is_expensive(if_dict);
CFRelease(if_dict);
}
}
return (is_expensive);
}
static CFNumberRef
service_rank_entity_get_index(CFDictionaryRef dict, CFStringRef serviceID,
CFStringRef which, uint32_t * ret_val)
{
CFNumberRef service_index = NULL;
if (dict != NULL) {
service_index = CFDictionaryGetValue(dict,
kSCPropNetServiceServiceIndex);
service_index = isA_CFNumber(service_index);
}
if (service_index != NULL) {
SInt32 index_val;
if (!CFNumberGetValue(service_index, kCFNumberSInt32Type,
&index_val)
|| index_val <= 0) {
my_log(LOG_NOTICE,
"%@%@ ServiceIndex %@ is invalid, ignoring",
which, serviceID, service_index);
service_index = NULL;
}
else if (ret_val != NULL) {
*ret_val = (uint32_t)index_val;
}
}
return (service_index);
}
static boolean_t
get_rank_changes(CFStringRef serviceID, CFDictionaryRef state_options,
CFDictionaryRef setup_options, CFDictionaryRef services_info)
{
boolean_t changed = FALSE;
CFStringRef interface;
boolean_t ip_is_coupled = FALSE;
CFMutableDictionaryRef new_dict = NULL;
Rank rank_assertion = kRankAssertionDefault;
Boolean rank_assertion_is_set = FALSE;
CFStringRef setup_rank = NULL;
CFStringRef state_rank = NULL;
CFNumberRef service_index = NULL;
boolean_t use_setup_rank = TRUE;
if (setup_options != NULL) {
CFBooleanRef coupled;
setup_rank
= CFDictionaryGetValue(setup_options, kSCPropNetServicePrimaryRank);
setup_rank = isA_CFString(setup_rank);
if (setup_rank != NULL && !use_setup_rank) {
my_log(LOG_DEBUG, "%@ ignoring Setup PrimaryRank = %@",
serviceID, setup_rank);
setup_rank = NULL;
}
coupled = CFDictionaryGetValue(setup_options, kIPIsCoupled);
if (isA_CFBoolean(coupled) != NULL && CFBooleanGetValue(coupled)) {
ip_is_coupled = TRUE;
}
service_index
= service_rank_entity_get_index(setup_options,
serviceID,
kSCDynamicStoreDomainSetup,
NULL);
}
if (state_options != NULL) {
CFBooleanRef coupled;
state_rank
= CFDictionaryGetValue(state_options, kSCPropNetServicePrimaryRank);
state_rank = isA_CFString(state_rank);
coupled = CFDictionaryGetValue(state_options, kIPIsCoupled);
if (isA_CFBoolean(coupled) != NULL && CFBooleanGetValue(coupled)) {
ip_is_coupled = TRUE;
}
if (service_index == NULL) {
service_index
= service_rank_entity_get_index(state_options,
serviceID,
kSCDynamicStoreDomainState,
NULL);
}
}
if (!ip_is_coupled) {
ip_is_coupled = service_is_expensive(serviceID, services_info);
}
if (setup_rank != NULL || state_rank != NULL) {
Rank setup_assertion;
Boolean setup_assertion_is_set = FALSE;
Rank state_assertion;
Boolean state_assertion_is_set = FALSE;
setup_assertion = PrimaryRankGetRankAssertion(setup_rank,
&setup_assertion_is_set);
state_assertion = PrimaryRankGetRankAssertion(state_rank,
&state_assertion_is_set);
if (setup_assertion_is_set && state_assertion_is_set) {
if (setup_assertion > state_assertion) {
rank_assertion = setup_assertion;
}
else {
rank_assertion = state_assertion;
}
rank_assertion_is_set = TRUE;
}
else if (setup_assertion_is_set) {
rank_assertion = setup_assertion;
rank_assertion_is_set = TRUE;
}
else if (state_assertion_is_set) {
rank_assertion = state_assertion;
rank_assertion_is_set = TRUE;
}
}
interface = services_info_get_interface(services_info, serviceID);
if (interface != NULL) {
if (!rank_assertion_is_set) {
CFNumberRef if_rank = NULL;
if (S_if_rank_dict != NULL) {
if_rank = CFDictionaryGetValue(S_if_rank_dict, interface);
}
rank_assertion
= InterfaceRankGetRankAssertion(if_rank,
&rank_assertion_is_set);
#define kNotSetString ((CFTypeRef)CFSTR("not set"))
my_log(LOG_INFO,
"serviceID %@ interface %@ rank = 0x%x (%@)%s",
serviceID,
interface,
rank_assertion,
(if_rank != NULL) ? (CFTypeRef)if_rank : kNotSetString,
ip_is_coupled ? " [coupled]" : "");
}
else {
my_log(LOG_INFO,
"serviceID %@ interface %@ rank = 0x%x%s",
serviceID, interface, rank_assertion,
ip_is_coupled ? " [coupled]" : "");
}
}
if (service_index != NULL || rank_assertion_is_set || ip_is_coupled) {
new_dict = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (rank_assertion_is_set) {
CFNumberRef new_rank;
new_rank = CFNumberCreate(NULL, kCFNumberSInt32Type,
(const void *)&rank_assertion);
CFDictionarySetValue(new_dict, kServiceOptionRankAssertion,
new_rank);
CFRelease(new_rank);
}
if (ip_is_coupled) {
CFDictionarySetValue(new_dict, kIPIsCoupled, kCFBooleanTrue);
}
if (service_index != NULL) {
CFDictionarySetValue(new_dict, kSCPropNetServiceServiceIndex,
service_index);
}
}
changed = service_dict_set(serviceID, kSCEntNetService, new_dict);
my_CFRelease(&new_dict);
return (changed);
}
static void
add_service_keys(CFStringRef serviceID,
CFMutableArrayRef keys,
CFMutableArrayRef patterns)
{
int i;
CFStringRef key;
if (CFEqual(serviceID, kSCCompAnyRegex)) {
keys = patterns;
}
for (i = 0; i < ENTITY_TYPES_COUNT; i++) {
CFStringRef name = *entityTypeNames[i];
key = setup_service_key(serviceID, name);
my_CFArrayAppendUniqueValue(keys, key);
CFRelease(key);
key = state_service_key(serviceID, name);
my_CFArrayAppendUniqueValue(keys, key);
CFRelease(key);
}
key = state_service_key(serviceID, kSCEntNetDHCP);
my_CFArrayAppendUniqueValue(keys, key);
CFRelease(key);
key = setup_service_key(serviceID, NULL);
my_CFArrayAppendUniqueValue(keys, key);
CFRelease(key);
key = state_service_key(serviceID, NULL);
my_CFArrayAppendUniqueValue(keys, key);
CFRelease(key);
return;
}
static void
add_transient_status_keys(CFStringRef serviceID,
CFMutableArrayRef keys,
CFMutableArrayRef patterns)
{
if (CFEqual(serviceID, kSCCompAnyRegex)) {
keys = patterns;
}
for (size_t i = 0; i < countof(transientServiceInfo); i++) {
CFStringRef key;
key = state_service_key(serviceID,
*transientServiceInfo[i].entityName);
my_CFArrayAppendUniqueValue(keys, key);
CFRelease(key);
}
return;
}
static const CFStringRef *reachabilitySetupKeys[] = {
&kSCEntNetPPP,
&kSCEntNetInterface,
&kSCEntNetIPv4,
&kSCEntNetIPv6,
};
static void
add_reachability_patterns(CFMutableArrayRef patterns)
{
for (size_t i = 0; i < countof(reachabilitySetupKeys); i++) {
CFStringRef pattern;
pattern = setup_service_key(kSCCompAnyRegex, *reachabilitySetupKeys[i]);
my_CFArrayAppendUniqueValue(patterns, pattern);
CFRelease(pattern);
}
}
static void
add_vpn_pattern(CFMutableArrayRef patterns)
{
CFStringRef pattern;
pattern = setup_service_key(kSCCompAnyRegex, kSCEntNetVPN);
my_CFArrayAppendUniqueValue(patterns, pattern);
CFRelease(pattern);
}
static void
add_interface_link_pattern(CFMutableArrayRef patterns)
{
CFStringRef pattern;
pattern = interface_entity_key_copy(kSCCompAnyRegex, kSCEntNetLink);
my_CFArrayAppendUniqueValue(patterns, pattern);
CFRelease(pattern);
}
static CFDictionaryRef
services_info_copy(SCDynamicStoreRef session, CFArrayRef service_list)
{
CFIndex count;
CFMutableArrayRef keys;
CFDictionaryRef info;
CFMutableArrayRef patterns;
keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
CFArrayAppendValue(keys, S_setup_global_ipv4);
CFArrayAppendValue(keys, S_multicast_resolvers);
CFArrayAppendValue(keys, S_private_resolvers);
count = CFArrayGetCount(service_list);
for (CFIndex s = 0; s < count; s++) {
CFStringRef serviceID = CFArrayGetValueAtIndex(service_list, s);
add_service_keys(serviceID, keys, patterns);
add_transient_status_keys(serviceID, keys, patterns);
}
add_reachability_patterns(patterns);
add_vpn_pattern(patterns);
add_interface_link_pattern(patterns);
info = SCDynamicStoreCopyMultiple(session, keys, patterns);
my_CFRelease(&keys);
my_CFRelease(&patterns);
return (info);
}
#if !TARGET_OS_SIMULATOR
static boolean_t
set_ipv6_default_interface(IFIndex ifindex)
{
struct in6_ndifreq ndifreq;
int sock;
boolean_t success = FALSE;
memset((char *)&ndifreq, 0, sizeof(ndifreq));
strlcpy(ndifreq.ifname, kLoopbackInterface, sizeof(ndifreq.ifname));
if (ifindex != 0) {
ndifreq.ifindex = ifindex;
}
else {
ndifreq.ifindex = lo0_ifindex();
}
sock = inet6_dgram_socket();
if (sock < 0) {
goto done;
}
if (ioctl(sock, SIOCSDEFIFACE_IN6, (caddr_t)&ndifreq) == -1) {
my_log(LOG_ERR,
"ioctl(SIOCSDEFIFACE_IN6) failed: %s",
strerror(errno));
}
else {
success = TRUE;
}
close(sock);
done:
return (success);
}
#endif
#if !TARGET_OS_IPHONE
static __inline__ void
empty_dns()
{
(void)unlink(VAR_RUN_RESOLV_CONF);
}
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;
SCPrint(TRUE, f, CFSTR("#\n"));
SCPrint(TRUE, f, CFSTR("# macOS Notice\n"));
SCPrint(TRUE, f, CFSTR("#\n"));
SCPrint(TRUE, f, CFSTR("# This file is not consulted for DNS hostname resolution, address\n"));
SCPrint(TRUE, f, CFSTR("# resolution, or the DNS query routing mechanism used by most\n"));
SCPrint(TRUE, f, CFSTR("# processes on this system.\n"));
SCPrint(TRUE, f, CFSTR("#\n"));
SCPrint(TRUE, f, CFSTR("# To view the DNS configuration used by this system, use:\n"));
SCPrint(TRUE, f, CFSTR("# scutil --dns\n"));
SCPrint(TRUE, f, CFSTR("#\n"));
SCPrint(TRUE, f, CFSTR("# SEE ALSO\n"));
SCPrint(TRUE, f, CFSTR("# dns-sd(1), scutil(8)\n"));
SCPrint(TRUE, f, CFSTR("#\n"));
SCPrint(TRUE, f, CFSTR("# This file is automatically generated.\n"));
SCPrint(TRUE, f, CFSTR("#\n"));
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"));
}
else if (isA_CFString(val_domain_name)) {
SCPrint(TRUE, f, CFSTR("domain %@\n"), val_domain_name);
}
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);
(void)rename(VAR_RUN_RESOLV_CONF "-", VAR_RUN_RESOLV_CONF);
}
return;
}
#endif
static boolean_t
service_get_ip_is_coupled(CFStringRef serviceID)
{
CFDictionaryRef dict;
boolean_t ip_is_coupled = FALSE;
dict = service_dict_get(serviceID, kSCEntNetService);
if (dict != NULL) {
if (CFDictionaryContainsKey(dict, kIPIsCoupled)) {
ip_is_coupled = TRUE;
}
}
return (ip_is_coupled);
}
static CFStringRef
my_CFStringCreateWithInAddr(struct in_addr ip)
{
CFStringRef str;
str = CFStringCreateWithFormat(NULL, NULL, CFSTR(IP_FORMAT), IP_LIST(&ip));
return (str);
}
static CFStringRef
my_CFStringCreateWithIn6Addr(const struct in6_addr * ip)
{
char ntopbuf[INET6_ADDRSTRLEN];
(void)inet_ntop(AF_INET6, ip, ntopbuf, sizeof(ntopbuf));
return (CFStringCreateWithFormat(NULL, NULL, CFSTR("%s"), ntopbuf));
}
static void
update_ipv4(CFStringRef primary,
IPv4RouteListRef new_routelist,
keyChangeListRef keys)
{
#if !TARGET_OS_SIMULATOR
int sockfd;
#endif
if (keys != NULL) {
if (new_routelist != NULL && primary != NULL) {
const char * ifn_p = NULL;
char ifname[IFNAMSIZ];
IPv4RouteRef r;
CFMutableDictionaryRef dict = NULL;
dict = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
r = new_routelist->list;
if (r->gateway.s_addr != 0) {
CFStringRef str;
str = my_CFStringCreateWithInAddr(r->gateway);
CFDictionarySetValue(dict, kSCPropNetIPv4Router, str);
CFRelease(str);
}
ifn_p = my_if_indextoname(r->ifindex, ifname);
if (ifn_p != NULL) {
CFStringRef ifname_cf;
ifname_cf = CFStringCreateWithCString(NULL,
ifn_p,
kCFStringEncodingASCII);
if (ifname_cf != NULL) {
CFDictionarySetValue(dict,
kSCDynamicStorePropNetPrimaryInterface,
ifname_cf);
CFRelease(ifname_cf);
}
}
CFDictionarySetValue(dict, kSCDynamicStorePropNetPrimaryService,
primary);
keyChangeListSetValue(keys, S_state_global_ipv4, dict);
CFRelease(dict);
}
else {
keyChangeListRemoveValue(keys, S_state_global_ipv4);
}
}
#if !TARGET_OS_SIMULATOR
sockfd = open_routing_socket();
if (sockfd != -1) {
if (new_routelist != NULL) {
IPv4RouteListFinalize(new_routelist);
}
else {
new_routelist = IPv4RouteListCopyMulticastLoopback();
}
if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
if (S_ipv4_routelist == NULL) {
my_log(LOG_DEBUG, "Old Routes = <none>");
}
else {
my_log(LOG_DEBUG, "Old Routes = ");
IPv4RouteListLog(LOG_DEBUG, S_ipv4_routelist);
}
if (new_routelist == NULL) {
my_log(LOG_DEBUG, "New Routes = <none>");
}
else {
my_log(LOG_DEBUG, "New Routes = ");
IPv4RouteListLog(LOG_DEBUG, new_routelist);
}
}
IPv4RouteListApply(S_ipv4_routelist, new_routelist, sockfd);
close(sockfd);
}
if (S_ipv4_routelist != NULL) {
free(S_ipv4_routelist);
}
S_ipv4_routelist = new_routelist;
#else
if (new_routelist != NULL) {
free(new_routelist);
}
#endif
return;
}
static void
update_ipv6(CFStringRef primary,
IPv6RouteListRef new_routelist,
keyChangeListRef keys)
{
#if !TARGET_OS_SIMULATOR
int sockfd;
#endif
if (keys != NULL) {
if (new_routelist != NULL && primary != NULL) {
const char * ifn_p = NULL;
char ifname[IFNAMSIZ];
IPv6RouteRef r;
CFMutableDictionaryRef dict = NULL;
dict = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
r = new_routelist->list;
if ((r->flags & kRouteFlagsHasGateway) != 0) {
CFStringRef router;
router = my_CFStringCreateWithIn6Addr(&r->gateway);
CFDictionarySetValue(dict, kSCPropNetIPv6Router, router);
CFRelease(router);
}
ifn_p = my_if_indextoname(r->ifindex, ifname);
if (ifn_p != NULL) {
CFStringRef ifname_cf;
ifname_cf = CFStringCreateWithCString(NULL,
ifn_p,
kCFStringEncodingASCII);
if (ifname_cf != NULL) {
CFDictionarySetValue(dict,
kSCDynamicStorePropNetPrimaryInterface,
ifname_cf);
CFRelease(ifname_cf);
}
}
CFDictionarySetValue(dict, kSCDynamicStorePropNetPrimaryService,
primary);
keyChangeListSetValue(keys, S_state_global_ipv6, dict);
CFRelease(dict);
#if !TARGET_OS_SIMULATOR
set_ipv6_default_interface(r->ifindex);
#endif
}
else {
#if !TARGET_OS_SIMULATOR
set_ipv6_default_interface(0);
#endif
keyChangeListRemoveValue(keys, S_state_global_ipv6);
}
}
#if !TARGET_OS_SIMULATOR
sockfd = open_routing_socket();
if (sockfd != -1) {
IPv6RouteListFinalize(new_routelist);
if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
if (S_ipv6_routelist == NULL) {
my_log(LOG_DEBUG, "Old Routes = <none>");
}
else {
my_log(LOG_DEBUG, "Old Routes = ");
IPv6RouteListLog(LOG_DEBUG, S_ipv6_routelist);
}
if (new_routelist == NULL) {
my_log(LOG_DEBUG, "New Routes = <none>");
}
else {
my_log(LOG_DEBUG, "New Routes = ");
IPv6RouteListLog(LOG_DEBUG, new_routelist);
}
}
IPv6RouteListApply(S_ipv6_routelist, new_routelist, sockfd);
close(sockfd);
}
if (S_ipv6_routelist != NULL) {
free(S_ipv6_routelist);
}
S_ipv6_routelist = new_routelist;
#else
if (new_routelist != NULL) {
free(new_routelist);
}
#endif
return;
}
static Boolean
update_dns(CFDictionaryRef services_info,
CFStringRef primary,
keyChangeListRef keys)
{
#pragma unused(services_info)
Boolean changed = FALSE;
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 (!_SC_CFEqual(S_dns_dict, dict)) {
if (dict == NULL) {
#if !TARGET_OS_IPHONE
empty_dns();
#endif
keyChangeListRemoveValue(keys, S_state_global_dns);
} else {
CFMutableDictionaryRef new_dict;
#if !TARGET_OS_IPHONE
set_dns(CFDictionaryGetValue(dict, kSCPropNetDNSSearchDomains),
CFDictionaryGetValue(dict, kSCPropNetDNSDomainName),
CFDictionaryGetValue(dict, kSCPropNetDNSServerAddresses),
CFDictionaryGetValue(dict, kSCPropNetDNSSortList));
#endif
new_dict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
CFDictionaryRemoveValue(new_dict, kSCPropInterfaceName);
CFDictionaryRemoveValue(new_dict, kSCPropNetDNSSupplementalMatchDomains);
CFDictionaryRemoveValue(new_dict, kSCPropNetDNSSupplementalMatchOrders);
CFDictionaryRemoveValue(new_dict, DNS_CONFIGURATION_SCOPED_QUERY_KEY);
keyChangeListSetValue(keys, S_state_global_dns, new_dict);
CFRelease(new_dict);
}
changed = TRUE;
}
if (dict != NULL) CFRetain(dict);
if (S_dns_dict != NULL) CFRelease(S_dns_dict);
S_dns_dict = dict;
return changed;
}
static Boolean
update_dnsinfo(CFDictionaryRef services_info,
CFStringRef primary,
keyChangeListRef keys,
CFArrayRef service_order)
{
Boolean changed;
CFDictionaryRef dict = NULL;
CFArrayRef multicastResolvers;
CFArrayRef privateResolvers;
multicastResolvers = CFDictionaryGetValue(services_info, S_multicast_resolvers);
privateResolvers = CFDictionaryGetValue(services_info, S_private_resolvers);
if (primary != NULL) {
CFDictionaryRef service_dict;
service_dict = CFDictionaryGetValue(S_service_state_dict, primary);
if (service_dict != NULL) {
dict = CFDictionaryGetValue(service_dict, kSCEntNetDNS);
}
}
changed = dns_configuration_set(dict,
S_service_state_dict,
service_order,
multicastResolvers,
privateResolvers);
if (changed) {
keyChangeListNotifyKey(keys, S_state_global_dns);
}
return changed;
}
static Boolean
update_nwi(nwi_state_t state)
{
unsigned char signature[CC_SHA256_DIGEST_LENGTH];
static unsigned char signature_last[CC_SHA256_DIGEST_LENGTH];
_nwi_state_compute_sha256_hash(state, signature);
if (bcmp(signature, signature_last, sizeof(signature)) == 0) {
my_log(LOG_DEBUG, "Not updating network information");
return FALSE;
}
memcpy(signature_last, signature, sizeof(signature));
my_log(LOG_INFO, "Updating network information");
_nwi_state_log(state, TRUE, NULL);
#if !TEST_IPV4_ROUTELIST && !TEST_IPV6_ROUTELIST
if (!_nwi_state_store(state)) {
my_log(LOG_ERR, "Notifying nwi_state_store failed");
}
#endif
return TRUE;
}
static Boolean
update_proxies(CFDictionaryRef services_info,
CFStringRef primary,
keyChangeListRef keys,
CFArrayRef service_order)
{
Boolean changed = FALSE;
CFDictionaryRef dict = NULL;
CFDictionaryRef new_dict;
if (primary != NULL) {
CFDictionaryRef service_dict;
service_dict = CFDictionaryGetValue(S_service_state_dict, primary);
if (service_dict != NULL) {
dict = CFDictionaryGetValue(service_dict, kSCEntNetProxies);
}
}
new_dict = proxy_configuration_update(dict,
S_service_state_dict,
service_order,
services_info);
if (!_SC_CFEqual(S_proxies_dict, new_dict)) {
if (new_dict == NULL) {
keyChangeListRemoveValue(keys, S_state_global_proxies);
} else {
keyChangeListSetValue(keys, S_state_global_proxies, new_dict);
}
changed = TRUE;
}
if (S_proxies_dict != NULL) CFRelease(S_proxies_dict);
S_proxies_dict = new_dict;
return changed;
}
#if !TARGET_OS_IPHONE
static Boolean
update_smb(CFDictionaryRef services_info,
CFStringRef primary,
keyChangeListRef keys)
{
#pragma unused(services_info)
Boolean changed = FALSE;
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, kSCEntNetSMB);
}
}
if (!_SC_CFEqual(S_smb_dict, dict)) {
if (dict == NULL) {
keyChangeListRemoveValue(keys, S_state_global_smb);
} else {
keyChangeListSetValue(keys, S_state_global_smb, dict);
}
changed = TRUE;
}
if (dict != NULL) CFRetain(dict);
if (S_smb_dict != NULL) CFRelease(S_smb_dict);
S_smb_dict = dict;
return changed;
}
#endif
static Rank
get_service_index(CFDictionaryRef rank_entity,
CFArrayRef order, CFIndex n_order, CFStringRef serviceID)
{
CFIndex i;
Rank rank = kRankIndexMask;
CFNumberRef service_index;
service_index
= service_rank_entity_get_index(rank_entity,
serviceID,
CFSTR(""),
&rank);
if (service_index != NULL) {
rank += n_order;
my_log(LOG_INFO,
"%@ specifies ServiceIndex %@, effective index is %d",
serviceID, service_index, rank);
}
else if (serviceID != NULL && order != NULL && n_order > 0) {
for (i = 0; i < n_order; i++) {
CFStringRef s = isA_CFString(CFArrayGetValueAtIndex(order, i));
if (s == NULL) {
continue;
}
if (CFEqual(serviceID, s)) {
rank = (Rank)i + 1;
break;
}
}
}
return (rank);
}
static Rank
rank_dict_get_service_rank(CFDictionaryRef rank_dict, CFStringRef serviceID)
{
CFNumberRef rank;
Rank rank_val = kRankAssertionDefault;
rank_val = RankMake(kRankIndexMask, kRankAssertionDefault);
rank = CFDictionaryGetValue(rank_dict, serviceID);
if (rank != NULL) {
if (!CFNumberGetValue(rank, kCFNumberSInt32Type, &rank_val)) {
rank_val = kRankAssertionDefault;
}
}
return (rank_val);
}
static void
rank_dict_set_service_rank(CFMutableDictionaryRef rank_dict,
CFStringRef serviceID, Rank rank_val)
{
CFNumberRef rank;
rank = CFNumberCreate(NULL, kCFNumberSInt32Type, (const void *)&rank_val);
if (rank != NULL) {
CFDictionarySetValue(rank_dict, serviceID, rank);
CFRelease(rank);
}
return;
}
static const CFStringRef *transientInterfaceEntityNames[] = {
&kSCEntNetPPP,
};
static void
CollectTransientServices(const void * key,
const void * value,
void * context)
{
#pragma unused(value)
CFStringRef service = key;
CFMutableArrayRef vif_setup_keys = context;
if (!CFStringHasPrefix(service, kSCDynamicStoreDomainSetup)) {
return;
}
for (size_t i = 0; i < countof(transientInterfaceEntityNames); i++) {
if (CFStringHasSuffix(service, *transientInterfaceEntityNames[i])) {
my_CFArrayAppendUniqueValue(vif_setup_keys, service);
break;
}
}
return;
}
static SCNetworkReachabilityFlags
GetReachabilityFlagsFromVPN(CFDictionaryRef services_info,
CFStringRef service_id,
CFStringRef entity,
CFStringRef vpn_setup_key)
{
CFStringRef key;
CFDictionaryRef dict;
SCNetworkReachabilityFlags flags = 0;
key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
service_id,
kSCEntNetInterface);
dict = CFDictionaryGetValue(services_info, key);
CFRelease(key);
if (isA_CFDictionary(dict)
&& CFDictionaryContainsKey(dict, kSCPropNetInterfaceDeviceName)) {
flags = (kSCNetworkReachabilityFlagsReachable
| kSCNetworkReachabilityFlagsTransientConnection
| kSCNetworkReachabilityFlagsConnectionRequired);
if (CFEqual(entity, kSCEntNetPPP)) {
CFNumberRef num;
CFDictionaryRef p_dict = CFDictionaryGetValue(services_info, vpn_setup_key);
if (!isA_CFDictionary(p_dict)) {
return (flags);
}
num = CFDictionaryGetValue(p_dict, kSCPropNetPPPDialOnDemand);
if (isA_CFNumber(num)) {
int32_t ppp_demand;
if (CFNumberGetValue(num, kCFNumberSInt32Type, &ppp_demand)) {
if (ppp_demand) {
flags |= kSCNetworkReachabilityFlagsConnectionOnTraffic;
}
}
}
}
}
return (flags);
}
static Boolean
S_dict_get_boolean(CFDictionaryRef dict, CFStringRef key, Boolean def_value)
{
Boolean ret = def_value;
if (dict != NULL) {
CFBooleanRef val;
val = CFDictionaryGetValue(dict, key);
if (isA_CFBoolean(val) != NULL) {
ret = CFBooleanGetValue(val);
}
}
return (ret);
}
static void
GetReachabilityFlagsFromTransientServices(CFDictionaryRef services_info,
SCNetworkReachabilityFlags *reach_flags_v4,
SCNetworkReachabilityFlags *reach_flags_v6)
{
CFIndex i;
CFIndex count;
CFMutableArrayRef vif_setup_keys;
vif_setup_keys = CFArrayCreateMutable(NULL,
0,
&kCFTypeArrayCallBacks);
CFDictionaryApplyFunction(services_info, CollectTransientServices,
vif_setup_keys);
count = CFArrayGetCount(vif_setup_keys);
for (i = 0; i < count; i++) {
CFArrayRef components = NULL;
CFStringRef entity;
CFStringRef service_id;
CFStringRef vif_setup_key;
vif_setup_key = CFArrayGetValueAtIndex(vif_setup_keys, i);
components = CFStringCreateArrayBySeparatingStrings(NULL, vif_setup_key, CFSTR("/"));
if (CFArrayGetCount(components) != 5) {
goto skip;
}
service_id = CFArrayGetValueAtIndex(components, 3);
entity = CFArrayGetValueAtIndex(components, 4);
if (CFEqual(entity, kSCEntNetPPP)) {
SCNetworkReachabilityFlags flags;
CFStringRef key;
flags = GetReachabilityFlagsFromVPN(services_info,
service_id,
entity,
vif_setup_key);
key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
service_id,
kSCEntNetIPv4);
if (CFDictionaryContainsKey(services_info, key)) {
*reach_flags_v4 |= flags;
my_log(LOG_DEBUG, "Service %@ setting ipv4 reach flags: %d", service_id, *reach_flags_v4);
}
CFRelease(key);
key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
service_id,
kSCEntNetIPv6);
if (CFDictionaryContainsKey(services_info, key)) {
*reach_flags_v6 |= flags;
my_log(LOG_DEBUG, "Service %@ setting ipv6 reach flags: %d", service_id, *reach_flags_v6);
}
CFRelease(key);
if (flags != 0) {
if (components != NULL) {
CFRelease(components);
}
goto done;
}
}
skip:
if (components != NULL) {
CFRelease(components);
}
}
done:
CFRelease(vif_setup_keys);
return;
}
static SCNetworkReachabilityFlags
GetReachFlagsFromStatus(CFStringRef entity, int status)
{
SCNetworkReachabilityFlags flags = 0;
if (CFEqual(entity, kSCEntNetPPP)) {
switch (status) {
case PPP_RUNNING :
break;
case PPP_ONHOLD :
break;
case PPP_IDLE :
flags |= kSCNetworkReachabilityFlagsConnectionRequired;
break;
case PPP_STATERESERVED :
flags |= kSCNetworkReachabilityFlagsConnectionRequired;
break;
default :
flags |= kSCNetworkReachabilityFlagsConnectionRequired;
break;
}
}
else if (CFEqual(entity, kSCEntNetIPSec)) {
switch (status) {
case IPSEC_RUNNING :
break;
case IPSEC_IDLE :
flags |= kSCNetworkReachabilityFlagsConnectionRequired;
break;
default :
flags |= kSCNetworkReachabilityFlagsConnectionRequired;
break;
}
}
else if (CFEqual(entity, kSCEntNetVPN)) {
switch (status) {
case VPN_RUNNING :
break;
case VPN_IDLE :
case VPN_LOADING :
case VPN_LOADED :
case VPN_UNLOADING :
flags |= kSCNetworkReachabilityFlagsConnectionRequired;
break;
default :
flags |= kSCNetworkReachabilityFlagsConnectionRequired;
break;
}
}
return (flags);
}
static void
VPNAttributesGet(CFStringRef service_id,
CFDictionaryRef services_info,
SCNetworkReachabilityFlags *flags,
CFStringRef *server_address,
int af)
{
CFDictionaryRef entity_dict;
CFNumberRef num;
CFDictionaryRef p_state = NULL;
int status = 0;
CFStringRef transient_entity = NULL;
if (af == AF_INET) {
entity_dict = service_dict_get(service_id, kSCEntNetIPv4);
} else {
entity_dict = service_dict_get(service_id, kSCEntNetIPv6);
}
entity_dict = ipdict_get_service(entity_dict);
if (entity_dict == NULL) {
return;
}
for (size_t i = 0; i < countof(transientServiceInfo); i++) {
CFStringRef entity = *transientServiceInfo[i].entityName;
p_state = service_dict_get(service_id, entity);
if (isA_CFDictionary(p_state)) {
transient_entity = entity;
break;
}
}
if (transient_entity == NULL) {
return;
}
*flags |= (kSCNetworkReachabilityFlagsReachable
| kSCNetworkReachabilityFlagsTransientConnection);
if (server_address != NULL) {
*server_address = CFDictionaryGetValue(entity_dict,
CFSTR("ServerAddress"));
*server_address = isA_CFString(*server_address);
if (*server_address != NULL) {
CFRetain(*server_address);
}
}
if (!CFDictionaryGetValueIfPresent(p_state,
kSCPropNetVPNStatus, (const void **)&num) ||
!isA_CFNumber(num) ||
!CFNumberGetValue(num, kCFNumberIntType, &status)) {
return;
}
*flags |= GetReachFlagsFromStatus(transient_entity, status);
if (CFEqual(transient_entity, kSCEntNetPPP)) {
CFStringRef key;
CFDictionaryRef p_setup;
int ppp_demand;
key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
service_id,
kSCEntNetPPP);
p_setup = CFDictionaryGetValue(services_info, key);
CFRelease(key);
if (isA_CFDictionary(p_setup) &&
CFDictionaryGetValueIfPresent(p_setup,
kSCPropNetPPPDialOnDemand,
(const void **)&num) &&
isA_CFNumber(num) &&
CFNumberGetValue(num, kCFNumberIntType, &ppp_demand) &&
(ppp_demand != 0)) {
*flags |= kSCNetworkReachabilityFlagsConnectionOnTraffic;
if (status == PPP_IDLE) {
*flags |= kSCNetworkReachabilityFlagsInterventionRequired;
}
}
}
return;
}
typedef struct ElectionInfo {
int af;
CFStringRef entity;
int n_services;
CFArrayRef order;
CFIndex n_order;
ElectionResultsRef results;
CFMutableDictionaryRef rank_dict;
} ElectionInfo, * ElectionInfoRef;
typedef CFDictionaryApplierFunction ElectionFuncRef;
static void
CandidateRelease(CandidateRef candidate)
{
my_CFRelease(&candidate->serviceID);
my_CFRelease(&candidate->if_name);
my_CFRelease(&candidate->signature);
return;
}
static void
CandidateCopy(CandidateRef dest, CandidateRef src)
{
*dest = *src;
if (dest->serviceID) {
CFRetain(dest->serviceID);
}
if (dest->if_name) {
CFRetain(dest->if_name);
}
if(dest->signature) {
CFRetain(dest->signature);
}
return;
}
static ElectionResultsRef
ElectionResultsAlloc(int af, int size)
{
ElectionResultsRef results;
results = (ElectionResultsRef)malloc(ElectionResultsComputeSize(size));
results->af = af;
results->count = 0;
results->size = size;
return (results);
}
static void
ElectionResultsRelease(ElectionResultsRef results)
{
int i;
CandidateRef scan;
for (i = 0, scan = results->candidates;
i < results->count;
i++, scan++) {
CandidateRelease(scan);
}
free(results);
return;
}
static void
ElectionResultsLog(int level, ElectionResultsRef results, const char * prefix)
{
int i;
CandidateRef scan;
if (results == NULL) {
my_log(level, "%s: no candidates", prefix);
return;
}
my_log(level, "%s: %d candidates", prefix, results->count);
for (i = 0, scan = results->candidates;
i < results->count;
i++, scan++) {
char ntopbuf[INET6_ADDRSTRLEN];
(void)inet_ntop(results->af, &scan->addr, ntopbuf, sizeof(ntopbuf));
my_log(level, "%d. %@ serviceID=%@ addr=%s rank=0x%x%s",
i, scan->if_name, scan->serviceID, ntopbuf, scan->rank,
scan->ineligible ? " [ineligible]" : "");
}
return;
}
static void
ElectionResultsAddCandidate(ElectionResultsRef results, CandidateRef candidate)
{
CFIndex i;
CFIndex where;
if (results->count == results->size) {
my_log(LOG_NOTICE, "can't fit another candidate");
return;
}
where = kCFNotFound;
for (i = 0; i < results->count; i++) {
CandidateRef this_candidate = results->candidates + i;
if (candidate->rank < this_candidate->rank) {
where = i;
break;
}
}
if (where == kCFNotFound) {
CandidateCopy(results->candidates + results->count, candidate);
results->count++;
return;
}
for (i = results->count; i > where; i--) {
results->candidates[i] = results->candidates[i - 1];
}
CandidateCopy(results->candidates + where, candidate);
results->count++;
return;
}
static void
elect_ip(const void * key, const void * value, void * context);
static ElectionResultsRef
ElectionResultsCopy(int af, CFArrayRef order)
{
int count;
ElectionInfo info;
count = (int)CFDictionaryGetCount(S_service_state_dict);
if (count == 0) {
return (NULL);
}
info.af = af;
if (af == AF_INET) {
info.entity = kSCEntNetIPv4;
info.rank_dict = S_ipv4_service_rank_dict;
}
else {
info.entity = kSCEntNetIPv6;
info.rank_dict = S_ipv6_service_rank_dict;
}
info.results = ElectionResultsAlloc(af, count);
info.n_services = count;
info.order = order;
if (order != NULL) {
info.n_order = CFArrayGetCount(order);
}
else {
info.order = 0;
}
CFDictionaryApplyFunction(S_service_state_dict, elect_ip, (void *)&info);
if (info.results->count == 0) {
ElectionResultsRelease(info.results);
info.results = NULL;
}
return (info.results);
}
static Boolean
ElectionResultsCandidateNeedsDemotion(CandidateRef other_candidate,
CandidateRef candidate)
{
Boolean ret = FALSE;
if (other_candidate == NULL) {
goto done;
}
if (other_candidate->ineligible) {
goto done;
}
if (RANK_ASSERTION_MASK(other_candidate->rank) == kRankAssertionNever) {
goto done;
}
if (!candidate->ip_is_coupled && !other_candidate->ip_is_coupled) {
goto done;
}
if (CFEqual(other_candidate->if_name, candidate->if_name)) {
goto done;
}
if (CFStringHasPrefix(other_candidate->if_name, CFSTR("stf"))) {
goto done;
}
if (candidate->rank < other_candidate->rank) {
goto done;
}
if (candidate->ip_is_coupled) {
if (other_candidate->ip_is_coupled
&& candidate->rank == other_candidate->rank) {
goto done;
}
}
else if (other_candidate->ip_is_coupled) {
if (candidate->rank == other_candidate->rank) {
goto done;
}
}
else {
goto done;
}
ret = TRUE;
done:
return (ret);
}
static void
get_signature_sha256(CFStringRef signature,
unsigned char * sha256)
{
CC_SHA256_CTX ctx;
CFDataRef signature_data;
signature_data = CFStringCreateExternalRepresentation(NULL,
signature,
kCFStringEncodingUTF8,
0);
CC_SHA256_Init(&ctx);
CC_SHA256_Update(&ctx,
CFDataGetBytePtr(signature_data),
(CC_LONG)CFDataGetLength(signature_data));
CC_SHA256_Final(sha256, &ctx);
CFRelease(signature_data);
return;
}
static void
add_candidate_to_nwi_state(nwi_state_t nwi_state, int af,
CandidateRef candidate, Boolean not_in_list,
Boolean not_in_iflist)
{
uint64_t flags = 0;
char ifname[IFNAMSIZ];
nwi_ifstate_t ifstate;
if (nwi_state == NULL) {
return;
}
if (not_in_list
|| RANK_ASSERTION_MASK(candidate->rank) == kRankAssertionNever) {
flags |= NWI_IFSTATE_FLAGS_NOT_IN_LIST;
}
if (not_in_iflist) {
flags |= NWI_IFSTATE_FLAGS_NOT_IN_IFLIST;
}
if (service_dict_get(candidate->serviceID, kSCEntNetDNS) != NULL) {
flags |= NWI_IFSTATE_FLAGS_HAS_DNS;
}
if ((af == AF_INET) && service_has_clat46_address(candidate->serviceID)) {
flags |= NWI_IFSTATE_FLAGS_HAS_CLAT46;
}
CFStringGetCString(candidate->if_name, ifname, sizeof(ifname),
kCFStringEncodingASCII);
if ((S_IPMonitor_debug & kDebugFlag2) != 0) {
char ntopbuf[INET6_ADDRSTRLEN];
(void)inet_ntop(af, &candidate->addr, ntopbuf, sizeof(ntopbuf));
my_log(LOG_DEBUG,
"Adding IPv%c [%s] %s "
"with flags 0x%llx rank 0x%x reach_flags 0x%x",
ipvx_char(af), ifname, ntopbuf,
flags, candidate->rank, candidate->reachability_flags);
}
ifstate = nwi_state_add_ifstate(nwi_state, ifname, af, flags,
candidate->rank,
(void *)&candidate->addr,
(void *)&candidate->vpn_server_addr,
candidate->reachability_flags);
if (ifstate != NULL && candidate->signature) {
uint8_t hash[CC_SHA256_DIGEST_LENGTH];
get_signature_sha256(candidate->signature, hash);
nwi_ifstate_set_signature(ifstate, hash);
}
return;
}
static void
add_reachability_flags_to_candidate(CandidateRef candidate, CFDictionaryRef services_info, int af)
{
SCNetworkReachabilityFlags flags = kSCNetworkReachabilityFlagsReachable;
CFStringRef vpn_server_address = NULL;
assert(candidate != NULL);
assert(services_info != NULL);
VPNAttributesGet(candidate->serviceID,
services_info,
&flags,
&vpn_server_address,
af);
candidate->reachability_flags = flags;
if (vpn_server_address == NULL) {
memset(&candidate->vpn_server_addr, 0, sizeof(candidate->vpn_server_addr));
} else {
char buf[128];
CFStringGetCString(vpn_server_address, buf, sizeof(buf),
kCFStringEncodingASCII);
_SC_string_to_sockaddr(buf,
AF_UNSPEC,
(void *)&candidate->vpn_server_addr,
sizeof(candidate->vpn_server_addr));
CFRelease(vpn_server_address);
}
return;
}
static CandidateRef
ElectionResultsGetPrimary(ElectionResultsRef results,
CandidateRef other_candidate,
nwi_state_t nwi_state, int af,
RouteListRef * ret_routes,
CFDictionaryRef services_info,
CFSetRef ip_service_changes)
{
CandidateRef primary = NULL;
Boolean primary_is_null = FALSE;
RouteListRef routes = NULL;
assert(services_info != NULL);
if (results != NULL) {
CandidateRef deferred[results->count];
int deferred_count;
CFStringRef entity_name;
int i;
int initial_size;
RouteListInfoRef info;
CandidateRef scan;
switch (af) {
case AF_INET:
entity_name = kSCEntNetIPv4;
info = &IPv4RouteListInfo;
initial_size = results->count * IPV4_ROUTES_N_STATIC;
break;
default:
case AF_INET6:
entity_name = kSCEntNetIPv6;
info = &IPv6RouteListInfo;
initial_size = results->count * IPV6_ROUTES_N_STATIC;
break;
}
deferred_count = 0;
for (i = 0, scan = results->candidates;
i < results->count;
i++, scan++) {
Boolean is_primary = FALSE;
CFDictionaryRef service_dict;
RouteListRef service_routes;
Boolean skip = FALSE;
if (!scan->ineligible
&& primary == NULL
&& RANK_ASSERTION_MASK(scan->rank) != kRankAssertionNever) {
if (ElectionResultsCandidateNeedsDemotion(other_candidate,
scan)) {
my_log(LOG_NOTICE,
"IPv%c over %@ (rank 0x%x) demoted: "
"primary IPv%c %@ (rank 0x%x)",
ipvx_char(af), scan->if_name, scan->rank,
ipvx_other_char(af), other_candidate->if_name,
other_candidate->rank);
deferred[deferred_count++] = scan;
skip = TRUE;
}
else {
primary = scan;
is_primary = TRUE;
}
}
service_dict = service_dict_get(scan->serviceID, entity_name);
service_routes = ipdict_get_routelist(service_dict);
if (service_routes != NULL) {
boolean_t force;
Rank rank = scan->rank;
if (skip) {
rank = RankMake(rank, kRankAssertionNever);
}
force = my_CFSetContainsValue(ip_service_changes,
scan->serviceID);
routes = RouteListAddRouteList(info, routes, initial_size,
service_routes, rank, force);
if ((service_routes->flags & kRouteListFlagsExcludeNWI) != 0) {
skip = TRUE;
}
}
else {
skip = TRUE;
}
if (skip) {
if (is_primary) {
primary_is_null = TRUE;
}
}
else if (!scan->ineligible) {
Boolean not_in_iflist;
add_reachability_flags_to_candidate(scan, services_info, af);
not_in_iflist
= (service_routes->flags & kRouteListFlagsScopedOnly) != 0;
add_candidate_to_nwi_state(nwi_state, af, scan,
primary_is_null,
not_in_iflist);
}
}
for (i = 0; i < deferred_count; i++) {
CandidateRef candidate = deferred[i];
add_reachability_flags_to_candidate(candidate, services_info, af);
add_candidate_to_nwi_state(nwi_state, af, candidate, TRUE, FALSE);
}
}
if (ret_routes != NULL) {
*ret_routes = routes;
}
else if (routes != NULL) {
free(routes);
}
if (primary_is_null) {
primary = NULL;
}
return (primary);
}
static inline
CFStringRef
service_dict_get_signature(CFDictionaryRef service_dict)
{
CFStringRef ifname;
ifname = CFDictionaryGetValue(service_dict, kSCPropInterfaceName);
if (isA_CFString(ifname) == NULL
|| !confirm_interface_name(service_dict, ifname)) {
return (NULL);
}
return (CFDictionaryGetValue(service_dict, kStoreKeyNetworkSignature));
}
static void
elect_ip(const void * key, const void * value, void * context)
{
CFDictionaryRef all_entities_dict = (CFDictionaryRef)value;
Candidate candidate;
Rank default_rank;
ElectionInfoRef elect_info;
CFStringRef if_name;
CFDictionaryRef ipdict;
Rank primary_rank;
CFDictionaryRef rank_entity;
RouteListUnion routelist;
CFDictionaryRef service_dict;
elect_info = (ElectionInfoRef)context;
ipdict = CFDictionaryGetValue(all_entities_dict, elect_info->entity);
if (ipdict != NULL) {
routelist.ptr = ipdict_get_routelist(ipdict);
service_dict = ipdict_get_service(ipdict);
}
else {
routelist.ptr = NULL;
}
if (routelist.ptr == NULL || service_dict == NULL) {
return;
}
if_name = CFDictionaryGetValue(service_dict, kSCPropInterfaceName);
if (if_name == NULL) {
return;
}
if (CFEqual(if_name, CFSTR(kLoopbackInterface))) {
return;
}
memset(&candidate, 0, sizeof(candidate));
candidate.serviceID = (CFStringRef)key;
if ((routelist.common->flags & kRouteListFlagsHasDefault) == 0) {
candidate.ineligible = TRUE;
}
rank_entity = CFDictionaryGetValue(all_entities_dict, kSCEntNetService);
candidate.rank = get_service_index(rank_entity,
elect_info->order, elect_info->n_order,
candidate.serviceID);
if (elect_info->af == AF_INET) {
default_rank = routelist.v4->list->rank;
candidate.addr.v4 = routelist.v4->list->ifa;
}
else {
default_rank = routelist.v6->list->rank;
candidate.addr.v6 = routelist.v6->list->ifa;
}
primary_rank = RANK_ASSERTION_MASK(default_rank);
if (S_ppp_override_primary) {
char ifn[IFNAMSIZ];
if (CFStringGetCString(if_name, ifn, sizeof(ifn),
kCFStringEncodingASCII)
&& (strncmp(PPP_PREFIX, ifn, sizeof(PPP_PREFIX) - 1) == 0)) {
primary_rank = kRankAssertionFirst;
}
}
candidate.rank = RankMake(candidate.rank, primary_rank);
candidate.ip_is_coupled = service_get_ip_is_coupled(candidate.serviceID);
candidate.if_name = if_name;
rank_dict_set_service_rank(elect_info->rank_dict,
candidate.serviceID, candidate.rank);
candidate.signature = service_dict_get_signature(service_dict);
ElectionResultsAddCandidate(elect_info->results, &candidate);
return;
}
static uint32_t
service_changed(CFDictionaryRef services_info, CFStringRef serviceID)
{
uint32_t changed = 0;
int i;
if (get_rank_changes(serviceID,
get_service_state_entity(services_info, serviceID,
NULL),
get_service_setup_entity(services_info, serviceID,
NULL),
services_info)) {
changed |= (1 << kEntityTypeServiceOptions);
}
for (i = 0; i < ENTITY_TYPES_COUNT; i++) {
GetEntityChangesFuncRef func;
CFStringRef name;
func = entityChangeFunc[i];
name = *entityTypeNames[i];
if ((*func)(serviceID,
get_service_state_entity(services_info, serviceID, name),
get_service_setup_entity(services_info, serviceID, name),
services_info)) {
changed |= (1 << i);
}
}
if (get_transient_status_changes(serviceID, services_info)) {
changed |= (1 << kEntityTypeTransientStatus);
}
return (changed);
}
static CFStringRef
serviceID_get_ifname(CFStringRef serviceID)
{
CFDictionaryRef entity_dict;
CFStringRef ifname = NULL;
entity_dict = service_dict_get(serviceID, kSCEntNetIPv4);
if (entity_dict == NULL) {
entity_dict = service_dict_get(serviceID, kSCEntNetIPv6);
}
if (entity_dict != NULL) {
ifname = ipdict_get_ifname(entity_dict);
}
return (ifname);
}
__private_extern__ boolean_t
check_if_service_expensive(CFStringRef serviceID)
{
CFStringRef ifname;
ifname = serviceID_get_ifname(serviceID);
return interface_is_expensive(ifname);
}
static CFArrayRef
service_order_get(CFDictionaryRef services_info)
{
CFArrayRef order = NULL;
CFDictionaryRef ipv4_dict;
ipv4_dict = my_CFDictionaryGetDictionary(services_info,
S_setup_global_ipv4);
if (ipv4_dict != NULL) {
CFNumberRef ppp_override;
int ppp_val = 0;
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 = FALSE;
}
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)) {
my_log(LOG_INFO, "%@ is still primary %s", new_primary, entity);
}
else {
my_CFRelease(primary_p);
*primary_p = CFRetain(new_primary);
my_log(LOG_INFO, "%@ is the new primary %s", new_primary, entity);
changed = TRUE;
}
}
else if (primary != NULL) {
my_log(LOG_INFO, "%@ is no longer primary %s", primary, entity);
my_CFRelease(primary_p);
changed = TRUE;
}
return (changed);
}
static Rank
rank_service_entity(CFDictionaryRef rank_dict, CFStringRef serviceID,
CFStringRef entity)
{
if (service_dict_get(serviceID, entity) == NULL) {
return (RankMake(kRankIndexMask, kRankAssertionDefault));
}
return (rank_dict_get_service_rank(rank_dict, serviceID));
}
static void
append_serviceIDs_for_interface(CFMutableArrayRef services_changed,
CFStringRef ifname)
{
CFIndex count;
CFIndex i;
void * * keys;
#define N_KEYS_VALUES_STATIC 10
void * keys_values_buf[N_KEYS_VALUES_STATIC * 2];
void * * values;
count = CFDictionaryGetCount(S_service_state_dict);
if (count <= N_KEYS_VALUES_STATIC) {
keys = keys_values_buf;
} else {
keys = (void * *)malloc(sizeof(*keys) * count * 2);
}
values = keys + count;
CFDictionaryGetKeysAndValues(S_service_state_dict,
(const void * *)keys,
(const void * *)values);
for (i = 0; i < count; i++) {
CFDictionaryRef ipdict = NULL;
CFStringRef interface = NULL;
CFStringRef serviceID;
CFDictionaryRef service_dict;
serviceID = (CFStringRef)keys[i];
service_dict = (CFDictionaryRef)values[i];
ipdict = CFDictionaryGetValue(service_dict, kSCEntNetIPv4);
if (ipdict == NULL) {
ipdict = CFDictionaryGetValue(service_dict, kSCEntNetIPv6);
if (ipdict == NULL) {
continue;
}
}
interface = ipdict_get_ifname(ipdict);
if (interface != NULL && CFEqual(interface, ifname)) {
my_log(LOG_DEBUG,
"Found IP service %@ on interface %@",
serviceID, ifname);
my_CFArrayAppendUniqueValue(services_changed, serviceID);
}
}
if (keys != keys_values_buf) {
free(keys);
}
return;
}
static __inline__ const char *
get_changed_str(CFStringRef serviceID, CFStringRef entity,
CFDictionaryRef old_dict)
{
CFDictionaryRef new_dict = NULL;
if (serviceID != NULL) {
new_dict = service_dict_get(serviceID, entity);
}
if (old_dict == NULL) {
if (new_dict != NULL) {
return "+";
}
} else {
if (new_dict == NULL) {
return "-";
} else if (!CFEqual(old_dict, new_dict)) {
return "!";
}
}
return "";
}
#if !TARGET_OS_SIMULATOR
#ifdef SIOCSIFORDER
#define MANAGE_IF_ORDER
#define MANAGE_IF_IOCTL
#endif
#ifdef SIOCSIFNETSIGNATURE
#define MANAGE_IF_SIGNATURE
#define MANAGE_IF_IOCTL
#endif
#ifdef MANAGE_IF_IOCTL
static int
inet_dgram_socket(void)
{
int sockfd;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
my_log(LOG_ERR, "socket() failed: %s", strerror(errno));
}
return sockfd;
}
#endif
#ifdef MANAGE_IF_ORDER
static Boolean
interface_order_changed(nwi_state_t old_state, nwi_state_t new_state)
{
if (old_state == NULL && new_state == NULL) {
return FALSE;
}
if (old_state == NULL || new_state == NULL) {
return TRUE;
}
if (old_state->if_list_count != new_state->if_list_count) {
return TRUE;
}
if (new_state->if_list_count == 0) {
return FALSE;
}
int i;
nwi_ifindex_t *old_scan;
nwi_ifindex_t *new_scan;
for (i = 0, old_scan = nwi_state_if_list(old_state), new_scan = nwi_state_if_list(new_state);
i < new_state->if_list_count; i++, old_scan++, new_scan++) {
if (strcmp(old_state->ifstate_list[*old_scan].ifname, new_state->ifstate_list[*new_scan].ifname) != 0) {
return TRUE;
}
}
return FALSE;
}
static Boolean
update_interface_order(nwi_state_t state, int sockfd)
{
Boolean success = FALSE;
struct if_order interface_order;
interface_order.ifo_count = (uint32_t)state->if_list_count;
interface_order.ifo_ordered_indices = (mach_vm_address_t)calloc((size_t)interface_order.ifo_count, sizeof(uint32_t));
if (((uint32_t *)interface_order.ifo_ordered_indices) != NULL) {
int i;
nwi_ifindex_t *scan;
for (i = 0, scan = nwi_state_if_list(state);
i < state->if_list_count; i++, scan++) {
const char *ifname = state->ifstate_list[*scan].ifname;
((uint32_t *)interface_order.ifo_ordered_indices)[i] = my_if_nametoindex(ifname);
}
}
if (ioctl(sockfd, SIOCSIFORDER, &interface_order) != 0) {
my_log(LOG_ERR, "SIOCSIFORDER for %u(%p) failed on %d: %s", interface_order.ifo_count, (void *)interface_order.ifo_ordered_indices, sockfd, strerror(errno));
} else {
my_log(LOG_INFO, "Set kernel interface order for %u interfaces", interface_order.ifo_count);
success = TRUE;
}
if (((uint32_t *)interface_order.ifo_ordered_indices) != NULL) {
free((void *)interface_order.ifo_ordered_indices);
interface_order.ifo_ordered_indices = (mach_vm_address_t)NULL;
}
return success;
}
#endif
#ifdef MANAGE_IF_SIGNATURE
static int
siocsifnetsignature(int s, const char * ifname, int af,
const uint8_t * signature, size_t signature_length)
{
struct if_nsreq nsreq;
memset(&nsreq, 0, sizeof(nsreq));
strlcpy(nsreq.ifnsr_name, ifname, sizeof(nsreq.ifnsr_name));
nsreq.ifnsr_family = af;
if (signature_length > 0) {
if (signature_length > sizeof(nsreq.ifnsr_data)) {
signature_length = sizeof(nsreq.ifnsr_data);
}
nsreq.ifnsr_len = signature_length;
memcpy(nsreq.ifnsr_data, signature, signature_length);
}
return (ioctl(s, SIOCSIFNETSIGNATURE, &nsreq));
}
static void
process_ifstate_difference(nwi_ifstate_t ifstate, int af, int sockfd)
{
nwi_ifstate_difference_t diff;
boolean_t set_signature = FALSE;
int signature_length = 0;
diff = nwi_ifstate_get_difference(ifstate);
switch (diff) {
case knwi_ifstate_difference_changed:
set_signature = TRUE;
if ((ifstate->flags & NWI_IFSTATE_FLAGS_HAS_SIGNATURE) != 0) {
signature_length = sizeof(ifstate->signature);
}
break;
case knwi_ifstate_difference_removed:
set_signature = TRUE;
break;
default:
break;
}
if (set_signature) {
if (siocsifnetsignature(sockfd, ifstate->ifname, af,
ifstate->signature,
signature_length) < 0) {
my_log(LOG_ERR,
"siocsifnetsignature(%s, IPv%c, %d) failed: %s",
ifstate->ifname, ipvx_char(af),
signature_length,
strerror(errno));
}
else {
my_log(LOG_DEBUG, "IPv%c Network Signature %s %s",
ipvx_char(af),
(signature_length > 0) ? "Set" : "Cleared",
ifstate->ifname);
if (signature_length > 0
&& (S_IPMonitor_debug & kDebugFlag1) != 0) {
int i;
char sig_buf[signature_length * 3 + 1];
sig_buf[0] = '\0';
for (i = 0; i < signature_length; i++) {
char byte_buf[4];
snprintf(byte_buf, sizeof(byte_buf),
"%02x ", ifstate->signature[i]);
strlcat(sig_buf, byte_buf, sizeof(sig_buf));
}
my_log(LOG_DEBUG, "Signature Bytes: %s", sig_buf);
}
}
}
return;
}
static void
process_state_differences(nwi_state_t state, int af, int sockfd)
{
int count;
int i;
nwi_ifstate_t scan;
if (af == AF_INET) {
count = state->ipv4_count;
}
else {
count = state->ipv6_count;
}
for (i = 0, scan = nwi_state_ifstate_list(state, af);
i < count; i++, scan++) {
process_ifstate_difference(scan, af, sockfd);
}
return;
}
#endif
#endif
static void
process_nwi_changes(CFMutableStringRef log_output,
nwi_state_t changes_state,
nwi_state_t new_state,
nwi_state_t old_state,
boolean_t dns_changed,
boolean_t dnsinfo_changed,
CFDictionaryRef old_primary_dns,
boolean_t proxy_changed,
CFDictionaryRef old_primary_proxy,
boolean_t smb_changed,
CFDictionaryRef old_primary_smb)
{
#ifndef MANAGE_IF_ORDER
#pragma unused(new_state)
#pragma unused(old_state)
#endif // !MANAGE_IF_ORDER
#if TARGET_OS_IPHONE
#pragma unused(smb_changed)
#pragma unused(old_primary_smb)
#endif // TARGET_OS_IPHONE
if (changes_state != NULL) {
const sa_family_t af_list[] = {AF_INET, AF_INET6};
nwi_ifstate_t scan;
#ifdef MANAGE_IF_IOCTL
int sockfd = inet_dgram_socket();
#endif
#ifdef MANAGE_IF_ORDER
if (interface_order_changed(new_state, old_state)) {
update_interface_order(new_state, sockfd);
}
#endif
for (size_t idx = 0; idx < countof(af_list); idx++) {
int af = af_list[idx];
CFMutableStringRef changes = NULL;
CFMutableStringRef primary_str = NULL;
#ifdef MANAGE_IF_SIGNATURE
process_state_differences(changes_state, af, sockfd);
#endif
scan = nwi_state_get_first_ifstate(changes_state, af);
while (scan != NULL) {
const char * changed_str;
changed_str = nwi_ifstate_get_diff_str(scan);
if (changed_str != NULL) {
void * address;
const char * addr_str;
char ntopbuf[INET6_ADDRSTRLEN];
address = (void *)nwi_ifstate_get_address(scan);
addr_str = inet_ntop(scan->af, address, ntopbuf,
sizeof(ntopbuf));
if (primary_str == NULL) {
primary_str = CFStringCreateMutable(NULL, 0);
CFStringAppendFormat(primary_str, NULL,
CFSTR("%s%s:%s"),
nwi_ifstate_get_ifname(scan),
changed_str, addr_str);
} else {
if (changes == NULL) {
changes = CFStringCreateMutable(NULL, 0);
}
CFStringAppendFormat(changes, NULL, CFSTR(", %s"),
nwi_ifstate_get_ifname(scan));
if (strcmp(changed_str, "") != 0) {
CFStringAppendFormat(changes, NULL, CFSTR("%s:%s"),
changed_str, addr_str);
}
}
}
scan = nwi_ifstate_get_next(scan, scan->af);
}
if (primary_str != NULL) {
CFStringAppendFormat(log_output, NULL, CFSTR(" %s(%@"),
af == AF_INET ? "v4" : "v6",
primary_str);
if (changes != NULL && CFStringGetLength(changes) != 0) {
CFStringAppendFormat(log_output, NULL, CFSTR("%@"),
changes);
}
CFStringAppend(log_output, CFSTR(")"));
my_CFRelease(&primary_str);
my_CFRelease(&changes);
}
}
#ifdef MANAGE_IF_IOCTL
if (sockfd >= 0) {
close(sockfd);
}
#endif
}
if (dns_changed || dnsinfo_changed) {
const char *str;
str = get_changed_str(S_primary_dns, kSCEntNetDNS, old_primary_dns);
if ((strcmp(str, "") == 0) && dnsinfo_changed) {
str = "*"; }
CFStringAppendFormat(log_output, NULL, CFSTR(" DNS%s"), str);
} else if (S_primary_dns != NULL) {
CFStringAppend(log_output, CFSTR(" DNS"));
}
if (proxy_changed) {
const char *str;
str = get_changed_str(S_primary_proxies, kSCEntNetProxies, old_primary_proxy);
CFStringAppendFormat(log_output, NULL, CFSTR(" Proxy%s"), str);
} else if (S_primary_proxies != NULL) {
CFStringAppend(log_output, CFSTR(" Proxy"));
}
#if !TARGET_OS_IPHONE
if (smb_changed) {
const char *str;
str = get_changed_str(S_primary_smb, kSCEntNetSMB, old_primary_smb);
CFStringAppendFormat(log_output, NULL, CFSTR(" SMB%s"), str);
} else if (S_primary_smb != NULL) {
CFStringAppend(log_output, CFSTR(" SMB"));
}
#endif // !TARGET_OS_IPHONE
return;
}
#pragma mark -
#pragma mark Network changed notification
static dispatch_queue_t
__network_change_queue()
{
static dispatch_once_t once;
static dispatch_queue_t q;
dispatch_once(&once, ^{
q = dispatch_queue_create("network change queue", NULL);
});
return q;
}
static void
post_network_change_when_ready()
{
int status;
dispatch_assert_queue(__network_change_queue());
if (S_network_change_needed == 0) {
return;
}
if (!S_network_change_timeout &&
(!S_dnsinfo_synced || !S_nwi_synced)) {
my_log(LOG_DEBUG,
"Defer \"" _SC_NOTIFY_NETWORK_CHANGE "\" (%s, %s)",
S_dnsinfo_synced ? "DNS" : "!DNS",
S_nwi_synced ? "nwi" : "!nwi");
return;
}
if (S_network_change_timer != NULL) {
dispatch_source_cancel(S_network_change_timer);
dispatch_release(S_network_change_timer);
S_network_change_timer = NULL;
S_network_change_timeout = FALSE;
}
{
struct timeval elapsed;
struct timeval end;
(void) gettimeofday(&end, NULL);
timersub(&end, &S_network_change_start, &elapsed);
#define QUERY_TIME__FMT "%ld.%6.6d"
#define QUERY_TIME__DIV 1
my_log(LOG_INFO,
"Post \"" _SC_NOTIFY_NETWORK_CHANGE "\" (%s: " QUERY_TIME__FMT ": 0x%x)",
S_network_change_timeout ? "timeout" : "delayed",
elapsed.tv_sec,
elapsed.tv_usec / QUERY_TIME__DIV,
S_network_change_needed);
}
#if !TARGET_OS_SIMULATOR && !TEST_IPV4_ROUTELIST && !TEST_IPV6_ROUTELIST
if ((S_network_change_needed & NETWORK_CHANGE_DNS) != 0) {
process_AgentMonitor_DNS();
}
#endif //!TARGET_OS_SIMULATOR
if ((S_network_change_needed & NETWORK_CHANGE_NET) != 0) {
status = notify_post(_SC_NOTIFY_NETWORK_CHANGE_NWI);
if (status != NOTIFY_STATUS_OK) {
my_log(LOG_ERR,
"notify_post(" _SC_NOTIFY_NETWORK_CHANGE_NWI ") failed: error=%d", status);
}
}
if ((S_network_change_needed & NETWORK_CHANGE_DNS) != 0) {
status = notify_post(_SC_NOTIFY_NETWORK_CHANGE_DNS);
if (status != NOTIFY_STATUS_OK) {
my_log(LOG_ERR,
"notify_post(" _SC_NOTIFY_NETWORK_CHANGE_DNS ") failed: error=%d", status);
}
}
if ((S_network_change_needed & NETWORK_CHANGE_PROXY) != 0) {
#if !TARGET_OS_SIMULATOR && !TEST_IPV4_ROUTELIST && !TEST_IPV6_ROUTELIST
process_AgentMonitor_Proxy();
#endif //!TARGET_OS_SIMULATOR
status = notify_post(_SC_NOTIFY_NETWORK_CHANGE_PROXY);
if (status != NOTIFY_STATUS_OK) {
my_log(LOG_ERR,
"notify_post(" _SC_NOTIFY_NETWORK_CHANGE_PROXY ") failed: error=%d", status);
}
}
if ((S_network_change_needed & NETWORK_CHANGE_NAT64) != 0) {
#if !TARGET_OS_SIMULATOR && !TEST_IPV4_ROUTELIST && !TEST_IPV6_ROUTELIST
if (S_nat64_prefix_requests != NULL || S_nat64_prefix_updates != NULL
|| S_nat64_cancel_prefix_requests != NULL) {
nat64_configuration_update(S_nat64_prefix_requests,
S_nat64_prefix_updates,
S_nat64_cancel_prefix_requests);
my_CFRelease(&S_nat64_prefix_requests);
my_CFRelease(&S_nat64_prefix_updates);
my_CFRelease(&S_nat64_cancel_prefix_requests);
}
#endif
S_network_change_needed &= ~(NETWORK_CHANGE_NAT64);
}
if (S_network_change_needed != 0) {
status = notify_post(_SC_NOTIFY_NETWORK_CHANGE);
if (status != NOTIFY_STATUS_OK) {
my_log(LOG_ERR,
"notify_post(" _SC_NOTIFY_NETWORK_CHANGE ") failed: error=%d", status);
}
}
S_network_change_needed = 0;
return;
}
#define TRAILING_EDGE_TIMEOUT_NSEC 5 * NSEC_PER_SEC // 5s
static void
post_network_change(uint32_t change)
{
dispatch_assert_queue(__network_change_queue());
if (S_network_change_needed == 0) {
(void) gettimeofday(&S_network_change_start, NULL);
}
S_network_change_needed |= change;
if (S_network_change_timer != NULL) {
dispatch_source_cancel(S_network_change_timer);
dispatch_release(S_network_change_timer);
S_network_change_timer = NULL;
S_network_change_timeout = FALSE;
}
if (!S_dnsinfo_synced || !S_nwi_synced) {
S_network_change_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
0,
0,
__network_change_queue());
dispatch_source_set_event_handler(S_network_change_timer, ^{
S_network_change_timeout = TRUE;
post_network_change_when_ready();
});
dispatch_source_set_timer(S_network_change_timer,
dispatch_time(DISPATCH_TIME_NOW,
TRAILING_EDGE_TIMEOUT_NSEC), DISPATCH_TIME_FOREVER, 10 * NSEC_PER_MSEC); dispatch_resume(S_network_change_timer);
}
post_network_change_when_ready();
return;
}
#pragma mark -
#pragma mark Process network (SCDynamicStore) changes
static void
IPMonitorProcessChanges(SCDynamicStoreRef session, CFArrayRef changed_keys,
CFArrayRef if_rank_changes)
{
CFIndex count = 0;
uint32_t changes = 0;
nwi_state_t changes_state = NULL;
boolean_t dns_changed = FALSE;
boolean_t dnsinfo_changed = FALSE;
boolean_t global_ipv4_changed = FALSE;
boolean_t global_ipv6_changed = FALSE;
CFMutableSetRef ipv4_service_changes = NULL;
CFMutableSetRef ipv6_service_changes = NULL;
keyChangeList keys;
CFIndex n;
boolean_t nat64_changed = FALSE;
CFMutableStringRef network_change_msg = NULL;
int n_services;
nwi_state_t old_nwi_state = NULL;
CFDictionaryRef old_primary_dns = NULL;
CFDictionaryRef old_primary_proxy = NULL;
#if !TARGET_OS_IPHONE
CFDictionaryRef old_primary_smb = NULL;
#endif // !TARGET_OS_IPHONE
boolean_t proxies_changed = FALSE;
boolean_t reachability_changed = FALSE;
CFArrayRef service_order;
CFMutableArrayRef service_changes = NULL;
CFDictionaryRef services_info = NULL;
#if !TARGET_OS_IPHONE
boolean_t smb_changed = FALSE;
#endif // !TARGET_OS_IPHONE
my_if_nameindex();
if (changed_keys != NULL) {
count = CFArrayGetCount(changed_keys);
if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
my_log(LOG_DEBUG,
"changed keys %@ (%ld)", changed_keys, count);
}
}
if (if_rank_changes == NULL && count == 0) {
return;
}
if (S_primary_dns != NULL) {
old_primary_dns = service_dict_get(S_primary_dns, kSCEntNetDNS);
if (old_primary_dns != NULL) {
old_primary_dns = CFDictionaryCreateCopy(NULL, old_primary_dns);
}
}
if (S_primary_proxies != NULL) {
old_primary_proxy
= service_dict_get(S_primary_proxies, kSCEntNetProxies);
if (old_primary_proxy != NULL) {
old_primary_proxy = CFDictionaryCreateCopy(NULL, old_primary_proxy);
}
}
#if !TARGET_OS_IPHONE
if (S_primary_smb != NULL) {
old_primary_smb = service_dict_get(S_primary_smb, kSCEntNetSMB);
if (old_primary_smb != NULL) {
old_primary_smb = CFDictionaryCreateCopy(NULL, old_primary_smb);
}
}
#endif // !TARGET_OS_IPHONE
keyChangeListInit(&keys);
service_changes = CFArrayCreateMutable(NULL, 0,
&kCFTypeArrayCallBacks);
for (CFIndex i = 0; i < count; i++) {
CFStringRef change;
#if !TARGET_OS_SIMULATOR && !TEST_IPV4_ROUTELIST && !TEST_IPV6_ROUTELIST
CFStringRef interface = NULL;
#endif
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_multicast_resolvers)) {
dnsinfo_changed = TRUE;
}
else if (CFEqual(change, S_private_resolvers)) {
dnsinfo_changed = TRUE;
}
else if (CFEqual(change, CFSTR(_PATH_RESOLVER_DIR))) {
dnsinfo_changed = TRUE;
}
else if (CFStringHasPrefix(change, S_interface_delegation_prefix) &&
CFStringHasSuffix(change, kSCEntNetInterfaceDelegation)) {
reachability_changed = TRUE;
}
else if (CFStringHasPrefix(change, S_state_service_prefix)) {
CFStringRef protocol = NULL;
CFStringRef serviceID;
serviceID = parseNetworkServiceString(change, &protocol);
if (serviceID != NULL) {
my_CFArrayAppendUniqueValue(service_changes, serviceID);
if (protocol != NULL) {
if (CFEqual(protocol, kSCEntNetIPv4)) {
my_CFSetAddValue(&ipv4_service_changes, serviceID);
}
else if (CFEqual(protocol, kSCEntNetIPv6)) {
my_CFSetAddValue(&ipv6_service_changes, serviceID);
}
}
CFRelease(serviceID);
}
my_CFRelease(&protocol);
}
else if (CFStringHasPrefix(change, S_setup_service_prefix)) {
CFStringRef serviceID;
serviceID = parseNetworkServiceString(change, NULL);
if (serviceID != NULL) {
my_CFArrayAppendUniqueValue(service_changes, serviceID);
CFRelease(serviceID);
}
for (size_t j = 0; j < countof(transientInterfaceEntityNames); j++) {
if (CFStringHasSuffix(change,
*transientInterfaceEntityNames[j])) {
reachability_changed = TRUE;
break;
}
}
if (CFStringHasSuffix(change, kSCEntNetInterface)) {
reachability_changed = TRUE;
}
}
#if !TARGET_OS_SIMULATOR && !TEST_IPV4_ROUTELIST && !TEST_IPV6_ROUTELIST
else if (is_nat64_prefix_request(change, &interface)) {
set_plat_discovery(kPLATDiscoveryOptionStart, interface);
nat64_changed = TRUE;
}
#endif
}
if (if_rank_changes != NULL) {
n = CFArrayGetCount(if_rank_changes);
for (CFIndex i = 0; i < n; i++) {
CFStringRef ifname = CFArrayGetValueAtIndex(if_rank_changes, i);
if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
my_log(LOG_DEBUG, "Interface rank changed %@", ifname);
}
append_serviceIDs_for_interface(service_changes, ifname);
}
}
services_info = services_info_copy(session, service_changes);
assert(services_info != NULL);
service_order = service_order_get(services_info);
if (service_order != NULL) {
if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
my_log(LOG_DEBUG, "service_order %@ ", service_order);
}
}
n = CFArrayGetCount(service_changes);
for (CFIndex i = 0; i < n; i++) {
uint32_t changes;
CFStringRef serviceID;
serviceID = CFArrayGetValueAtIndex(service_changes, i);
changes = service_changed(services_info, serviceID);
if (my_CFSetContainsValue(ipv4_service_changes, serviceID)) {
changes |= (1 << kEntityTypeIPv4);
}
if (my_CFSetContainsValue(ipv6_service_changes, serviceID)) {
changes |= (1 << kEntityTypeIPv6);
}
if ((changes & (1 << kEntityTypeServiceOptions)) != 0) {
global_ipv4_changed = TRUE;
global_ipv6_changed = TRUE;
}
else {
if ((changes & (1 << kEntityTypeIPv4)) != 0) {
global_ipv4_changed = TRUE;
dnsinfo_changed = TRUE;
proxies_changed = TRUE;
}
if ((changes & (1 << kEntityTypeIPv6)) != 0) {
global_ipv6_changed = TRUE;
dnsinfo_changed = TRUE;
proxies_changed = TRUE;
nat64_changed = TRUE;
}
}
if ((changes & (1 << kEntityTypeDNS)) != 0) {
if (S_primary_dns != NULL && CFEqual(S_primary_dns, serviceID)) {
dns_changed = TRUE;
}
dnsinfo_changed = TRUE;
nat64_changed = TRUE;
}
if ((changes & (1 << kEntityTypeProxies)) != 0) {
proxies_changed = TRUE;
}
#if !TARGET_OS_IPHONE
if ((changes & (1 << kEntityTypeSMB)) != 0) {
if (S_primary_smb != NULL && CFEqual(S_primary_smb, serviceID)) {
smb_changed = TRUE;
}
}
#endif
if ((changes & (1 << kEntityTypeTransientStatus)) != 0
&& (service_dict_get(serviceID, kSCEntNetIPv4) != NULL
|| service_dict_get(serviceID, kSCEntNetIPv6) != NULL)) {
dnsinfo_changed = TRUE;
}
}
n_services = (int)CFDictionaryGetCount(S_service_state_dict);
old_nwi_state = nwi_state_make_copy(S_nwi_state);
S_nwi_state = nwi_state_new(S_nwi_state, n_services);
if (global_ipv4_changed) {
if (S_ipv4_results != NULL) {
ElectionResultsRelease(S_ipv4_results);
}
S_ipv4_results
= ElectionResultsCopy(AF_INET, service_order);
ElectionResultsLog(LOG_INFO, S_ipv4_results, "IPv4");
}
if (global_ipv6_changed) {
if (S_ipv6_results != NULL) {
ElectionResultsRelease(S_ipv6_results);
}
S_ipv6_results
= ElectionResultsCopy(AF_INET6, service_order);
ElectionResultsLog(LOG_INFO, S_ipv6_results, "IPv6");
}
if (global_ipv4_changed || global_ipv6_changed || dnsinfo_changed) {
CFStringRef new_primary;
CFStringRef new_primary_dns = NULL;
CFStringRef new_primary_proxies = NULL;
#if !TARGET_OS_IPHONE
CFStringRef new_primary_smb = NULL;
#endif
RouteListUnion new_routelist;
CandidateRef other_candidate;
CandidateRef primary_candidate;
if (S_nwi_state != NULL) {
nwi_state_clear(S_nwi_state, AF_INET);
nwi_state_clear(S_nwi_state, AF_INET6);
}
my_log(LOG_DEBUG, "electing IPv4 primary");
new_routelist.ptr = NULL;
other_candidate = (S_ipv6_results != NULL)
? S_ipv6_results->candidates : NULL;
primary_candidate = ElectionResultsGetPrimary(S_ipv4_results,
other_candidate,
S_nwi_state, AF_INET,
&new_routelist.common,
services_info,
ipv4_service_changes);
new_primary = (primary_candidate != NULL)
? primary_candidate->serviceID : NULL;
(void)set_new_primary(&S_primary_ipv4, new_primary, "IPv4");
update_ipv4(S_primary_ipv4, new_routelist.v4, &keys);
my_log(LOG_DEBUG, "electing IPv6 primary");
new_routelist.ptr = NULL;
other_candidate = primary_candidate;
primary_candidate = ElectionResultsGetPrimary(S_ipv6_results,
other_candidate,
S_nwi_state, AF_INET6,
&new_routelist.common,
services_info,
ipv6_service_changes);
new_primary = (primary_candidate != NULL)
? primary_candidate->serviceID : NULL;
(void)set_new_primary(&S_primary_ipv6, new_primary, "IPv6");
update_ipv6(S_primary_ipv6, new_routelist.v6, &keys);
nwi_state_finalize(S_nwi_state);
if (S_primary_ipv4 != NULL && S_primary_ipv6 != NULL) {
if (rank_service_entity(S_ipv4_service_rank_dict,
S_primary_ipv4, kSCEntNetDNS)
<= rank_service_entity(S_ipv6_service_rank_dict,
S_primary_ipv6, kSCEntNetDNS)) {
new_primary_dns = S_primary_ipv4;
}
else {
new_primary_dns = S_primary_ipv6;
}
if (rank_service_entity(S_ipv4_service_rank_dict,
S_primary_ipv4, kSCEntNetProxies)
<= rank_service_entity(S_ipv6_service_rank_dict,
S_primary_ipv6, kSCEntNetProxies)) {
new_primary_proxies = S_primary_ipv4;
}
else {
new_primary_proxies = S_primary_ipv6;
}
#if !TARGET_OS_IPHONE
if (rank_service_entity(S_ipv4_service_rank_dict,
S_primary_ipv4, kSCEntNetSMB)
<= rank_service_entity(S_ipv6_service_rank_dict,
S_primary_ipv6, kSCEntNetSMB)) {
new_primary_smb = S_primary_ipv4;
}
else {
new_primary_smb = S_primary_ipv6;
}
#endif
}
else if (S_primary_ipv6 != NULL) {
new_primary_dns = S_primary_ipv6;
new_primary_proxies = S_primary_ipv6;
#if !TARGET_OS_IPHONE
new_primary_smb = S_primary_ipv6;
#endif
}
else if (S_primary_ipv4 != NULL) {
new_primary_dns = S_primary_ipv4;
new_primary_proxies = S_primary_ipv4;
#if !TARGET_OS_IPHONE
new_primary_smb = S_primary_ipv4;
#endif
}
if (set_new_primary(&S_primary_dns, new_primary_dns, "DNS")) {
dns_changed = TRUE;
dnsinfo_changed = TRUE;
}
if (set_new_primary(&S_primary_proxies, new_primary_proxies,
"Proxies")) {
proxies_changed = TRUE;
}
#if !TARGET_OS_IPHONE
if (set_new_primary(&S_primary_smb, new_primary_smb, "SMB")) {
smb_changed = TRUE;
}
#endif
}
if (!proxies_changed && dnsinfo_changed
&& ((G_supplemental_proxies_follow_dns != NULL)
&& CFBooleanGetValue(G_supplemental_proxies_follow_dns))) {
proxies_changed = TRUE;
}
changes_state = nwi_state_diff(old_nwi_state, S_nwi_state);
if (global_ipv4_changed || global_ipv6_changed
|| dnsinfo_changed || reachability_changed) {
if (S_nwi_state != NULL) {
S_nwi_state->generation_count = mach_absolute_time();
if (global_ipv4_changed || global_ipv6_changed
|| reachability_changed) {
SCNetworkReachabilityFlags reach_flags_v4 = 0;
SCNetworkReachabilityFlags reach_flags_v6 = 0;
GetReachabilityFlagsFromTransientServices(services_info,
&reach_flags_v4,
&reach_flags_v6);
_nwi_state_set_reachability_flags(S_nwi_state, reach_flags_v4,
reach_flags_v6);
}
_nwi_state_update_interface_generations(old_nwi_state, S_nwi_state,
changes_state);
}
if (update_nwi(S_nwi_state)) {
changes |= NETWORK_CHANGE_NET;
dnsinfo_changed = TRUE;
}
}
if (dns_changed) {
if (update_dns(services_info, S_primary_dns, &keys)) {
changes |= NETWORK_CHANGE_DNS;
dnsinfo_changed = TRUE;
} else {
dns_changed = FALSE;
}
}
if (dnsinfo_changed) {
if (update_dnsinfo(services_info, S_primary_dns,
&keys, service_order)) {
changes |= NETWORK_CHANGE_DNS;
} else {
dnsinfo_changed = FALSE;
}
}
if (proxies_changed) {
if (update_proxies(services_info, S_primary_proxies,
&keys, service_order)) {
changes |= NETWORK_CHANGE_PROXY;
} else {
proxies_changed = FALSE;
}
}
#if !TARGET_OS_IPHONE
if (smb_changed) {
if (update_smb(services_info, S_primary_smb, &keys)) {
changes |= NETWORK_CHANGE_SMB;
} else {
smb_changed = FALSE;
}
}
#endif
if (nat64_changed) {
changes |= NETWORK_CHANGE_NAT64;
}
my_CFRelease(&service_changes);
my_CFRelease(&services_info);
my_CFRelease(&ipv4_service_changes);
my_CFRelease(&ipv6_service_changes);
if (changes != 0) {
network_change_msg = CFStringCreateMutable(NULL, 0);
process_nwi_changes(network_change_msg,
changes_state,
S_nwi_state,
old_nwi_state,
dns_changed,
dnsinfo_changed,
old_primary_dns,
proxies_changed,
old_primary_proxy,
#if !TARGET_OS_IPHONE
smb_changed,
old_primary_smb
#else // !TARGET_OS_IPHONE
FALSE, NULL #endif // !TARGET_OS_IPHONE
);
}
keyChangeListApplyToStore(&keys, session);
my_CFRelease(&old_primary_dns);
my_CFRelease(&old_primary_proxy);
#if !TARGET_OS_IPHONE
my_CFRelease(&old_primary_smb);
#endif // !TARGET_OS_IPHONE
if (changes != 0) {
dispatch_async(__network_change_queue(), ^{
post_network_change(changes);
});
}
if ((network_change_msg != NULL)
&& (CFStringGetLength(network_change_msg) != 0)) {
my_log(LOG_NOTICE, "network changed:%@", network_change_msg);
} else if (keyChangeListActive(&keys)) {
my_log(LOG_NOTICE, "network changed");
} else if (nat64_changed) {
my_log(LOG_NOTICE, "nat64 update");
} else {
my_log(LOG_INFO, "network event w/no changes");
}
my_CFRelease(&network_change_msg);
if (changes_state != NULL) {
nwi_state_free(changes_state);
}
if (old_nwi_state != NULL) {
nwi_state_free(old_nwi_state);
}
keyChangeListFree(&keys);
my_if_freenameindex();
return;
}
static void
IPMonitorNotify(SCDynamicStoreRef session, CFArrayRef changed_keys,
void * info)
{
#pragma unused(info)
IPMonitorProcessChanges(session, changed_keys, NULL);
return;
}
#if !TARGET_OS_IPHONE
#define PROXY_GLOBAL_OBSERVER_TYPE scprefs_observer_type_mcx
#else
#define PROXY_GLOBAL_OBSERVER_TYPE scprefs_observer_type_global
#endif
static void
watch_proxies()
{
static dispatch_queue_t proxy_cb_queue;
proxy_cb_queue = dispatch_queue_create("com.apple.SystemConfiguration.IPMonitor.proxy", NULL);
_scprefs_observer_watch(PROXY_GLOBAL_OBSERVER_TYPE,
"com.apple.SystemConfiguration.plist",
proxy_cb_queue,
^{
SCDynamicStoreNotifyValue(NULL, S_state_global_proxies);
#if !TARGET_OS_SIMULATOR && !TEST_IPV4_ROUTELIST && !TEST_IPV6_ROUTELIST
process_AgentMonitor_Proxy();
#endif //!TARGET_OS_SIMULATOR
(void)notify_post(_SC_NOTIFY_NETWORK_CHANGE_PROXY);
my_log(LOG_INFO, "Notifying:\n%@",
S_state_global_proxies);
});
return;
}
#if TEST_IPV4_ROUTELIST || TEST_IPV6_ROUTELIST
static void
prefs_changed_callback_init(void)
{
}
#else
#include "IPMonitorControlPrefs.h"
static void
prefs_changed(SCPreferencesRef prefs)
{
#pragma unused(prefs)
if (S_bundle_logging_verbose || IPMonitorControlPrefsIsVerbose()) {
S_IPMonitor_debug = kDebugFlagDefault;
S_IPMonitor_verbose = TRUE;
my_log(LOG_DEBUG, "Setting logging verbose mode on");
} else {
my_log(LOG_DEBUG, "Setting logging verbose mode off");
S_IPMonitor_debug = 0;
S_IPMonitor_verbose = FALSE;
}
return;
}
static void
prefs_changed_callback_init(void)
{
IPMonitorControlPrefsInit(CFRunLoopGetCurrent(), prefs_changed);
prefs_changed(NULL);
return;
}
#endif
#if !TARGET_OS_SIMULATOR
static int
flush_routes(int s)
{
char * buf = NULL;
int i;
char * lim;
#define N_MIB 6
int mib[N_MIB];
size_t needed;
char * next;
struct rt_msghdr * rtm;
struct sockaddr_in *sin;
mib[0] = CTL_NET;
mib[1] = PF_ROUTE;
mib[2] = 0;
mib[3] = AF_INET;
mib[4] = NET_RT_FLAGS;
mib[5] = RTF_STATIC | RTF_DYNAMIC;
for (i = 0; i < 3; i++) {
if (sysctl(mib, N_MIB, NULL, &needed, NULL, 0) < 0) {
break;
}
if ((buf = malloc(needed)) == NULL) {
break;
}
if (sysctl(mib, N_MIB, buf, &needed, NULL, 0) >= 0) {
break;
}
free(buf);
buf = NULL;
}
if (buf == NULL) {
return (-1);
}
lim = buf + needed;
for (next = buf; next < lim; next += rtm->rtm_msglen) {
uint32_t addr;
rtm = (struct rt_msghdr *)(void *)next;
sin = (struct sockaddr_in *)(rtm + 1);
addr = ntohl(sin->sin_addr.s_addr);
if (IN_LOOPBACK(addr)) {
my_log(LOG_DEBUG,
"flush_routes: ignoring loopback route");
continue;
}
if (IN_LOCAL_GROUP(addr)) {
my_log(LOG_DEBUG,
"flush_routes: ignoring multicast route");
continue;
}
rtm->rtm_type = RTM_DELETE;
rtm->rtm_seq = ++rtm_seq;
if (write(s, rtm, rtm->rtm_msglen) < 0) {
my_log(LOG_NOTICE,
"flush_routes: removing route for "
IP_FORMAT " failed: %s",
IP_LIST(&sin->sin_addr),
strerror(errno));
}
else {
my_log(LOG_DEBUG,
"flush_routes: removed route for " IP_FORMAT,
IP_LIST(&sin->sin_addr));
}
}
free(buf);
return (0);
}
static void
flush_inet_routes(void)
{
int s;
s = open_routing_socket();
if (s != -1) {
flush_routes(s);
close(s);
}
}
#else
static void
flush_inet_routes(void)
{
}
#endif
static void
ip_plugin_init()
{
CFMutableArrayRef keys = NULL;
CFStringRef pattern;
CFMutableArrayRef patterns = NULL;
CFRunLoopSourceRef rls = NULL;
if (S_is_network_boot() != 0) {
S_netboot = TRUE;
}
else {
flush_inet_routes();
}
S_session = SCDynamicStoreCreate(NULL, CFSTR("IPMonitor"),
IPMonitorNotify, NULL);
if (S_session == NULL) {
my_log(LOG_ERR,
"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_proxies
= SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
kSCDynamicStoreDomainState,
kSCEntNetProxies);
#if !TARGET_OS_IPHONE
S_state_global_smb
= SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
kSCDynamicStoreDomainState,
kSCEntNetSMB);
#endif
S_setup_global_ipv4
= SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCEntNetIPv4);
S_state_service_prefix
= SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainState,
CFSTR(""),
NULL);
S_setup_service_prefix
= SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
CFSTR(""),
NULL);
S_interface_delegation_prefix
= SCDynamicStoreKeyCreateNetworkInterface(NULL,
kSCDynamicStoreDomainState);
S_service_state_dict
= CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
S_ipv4_service_rank_dict
= CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
S_ipv6_service_rank_dict
= CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
add_service_keys(kSCCompAnyRegex, keys, patterns);
pattern = setup_service_key(kSCCompAnyRegex, kSCEntNetPPP);
CFArrayAppendValue(patterns, pattern);
CFRelease(pattern);
pattern = setup_service_key(kSCCompAnyRegex, kSCEntNetVPN);
CFArrayAppendValue(patterns, pattern);
CFRelease(pattern);
pattern = setup_service_key(kSCCompAnyRegex, kSCEntNetInterface);
CFArrayAppendValue(patterns, pattern);
CFRelease(pattern);
add_transient_status_keys(kSCCompAnyRegex, NULL, patterns);
CFArrayAppendValue(keys, S_setup_global_ipv4);
S_multicast_resolvers = SCDynamicStoreKeyCreate(NULL, CFSTR("%@/%@/%@"),
kSCDynamicStoreDomainState,
kSCCompNetwork,
CFSTR(kDNSServiceCompMulticastDNS));
CFArrayAppendValue(keys, S_multicast_resolvers);
S_private_resolvers = SCDynamicStoreKeyCreate(NULL, CFSTR("%@/%@/%@"),
kSCDynamicStoreDomainState,
kSCCompNetwork,
CFSTR(kDNSServiceCompPrivateDNS));
CFArrayAppendValue(keys, S_private_resolvers);
#if !TARGET_OS_SIMULATOR && !TEST_IPV4_ROUTELIST && !TEST_IPV6_ROUTELIST
nat64_prefix_request_add_pattern(patterns);
#endif
pattern = interface_entity_key_copy(kSCCompAnyRegex, kSCEntNetInterfaceDelegation);
CFArrayAppendValue(patterns, pattern);
CFRelease(pattern);
if (!SCDynamicStoreSetNotificationKeys(S_session, keys, patterns)) {
my_log(LOG_ERR,
"SCDynamicStoreSetNotificationKeys() failed: %s",
SCErrorString(SCError()));
goto done;
}
rls = SCDynamicStoreCreateRunLoopSource(NULL, S_session, 0);
if (rls == NULL) {
my_log(LOG_ERR,
"SCDynamicStoreCreateRunLoopSource() failed: %s",
SCErrorString(SCError()));
goto done;
}
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
CFRelease(rls);
(void)dns_configuration_set(NULL, NULL, NULL, NULL, NULL);
#if !TARGET_OS_IPHONE
empty_dns();
#endif
(void)SCDynamicStoreRemoveValue(S_session, S_state_global_dns);
#if !TARGET_OS_IPHONE
(void)SCDynamicStoreRemoveValue(S_session, S_state_global_smb);
#endif
watch_proxies();
done:
my_CFRelease(&keys);
my_CFRelease(&patterns);
return;
}
__private_extern__
void
prime_IPMonitor()
{
update_ipv4(NULL, NULL, NULL);
#if !TARGET_OS_SIMULATOR && !TEST_IPV4_ROUTELIST && !TEST_IPV6_ROUTELIST
process_AgentMonitor();
#endif // !TARGET_OS_SIMULATOR
return;
}
static boolean_t
S_get_plist_boolean(CFDictionaryRef plist, CFStringRef key,
boolean_t def)
{
CFBooleanRef b;
boolean_t ret = def;
b = isA_CFBoolean(CFDictionaryGetValue(plist, key));
if (b != NULL) {
ret = CFBooleanGetValue(b);
}
return (ret);
}
#if !TARGET_OS_SIMULATOR && !TEST_IPV4_ROUTELIST && !TEST_IPV6_ROUTELIST
#include "IPMonitorControlServer.h"
static void
InterfaceRankChanged(void * info)
{
#pragma unused(info)
CFDictionaryRef assertions = NULL;
CFArrayRef changes;
changes = IPMonitorControlServerCopyInterfaceRankInformation(&assertions);
if (S_if_rank_dict != NULL) {
CFRelease(S_if_rank_dict);
}
S_if_rank_dict = assertions;
if (changes != NULL) {
IPMonitorProcessChanges(S_session, NULL, changes);
CFRelease(changes);
}
return;
}
static void
StartIPMonitorControlServer(void)
{
CFRunLoopSourceContext context;
CFRunLoopSourceRef rls;
memset(&context, 0, sizeof(context));
context.perform = InterfaceRankChanged;
rls = CFRunLoopSourceCreate(NULL, 0, &context);
if (!IPMonitorControlServerStart(CFRunLoopGetCurrent(),
rls,
&S_bundle_logging_verbose)) {
my_log(LOG_ERR, "IPMonitorControlServerStart failed");
}
else {
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls,
kCFRunLoopDefaultMode);
}
CFRelease(rls);
return;
}
#endif
__private_extern__
void
load_IPMonitor(CFBundleRef bundle, Boolean bundleVerbose)
{
CFDictionaryRef info_dict;
info_dict = CFBundleGetInfoDictionary(bundle);
if (info_dict != NULL) {
S_append_state
= S_get_plist_boolean(info_dict,
CFSTR("AppendStateArrayToSetupArray"),
FALSE);
}
if (bundleVerbose) {
S_IPMonitor_debug = kDebugFlagDefault;
S_bundle_logging_verbose = TRUE;
S_IPMonitor_verbose = TRUE;
}
prefs_changed_callback_init();
#if !TEST_IPV4_ROUTELIST && !TEST_IPV6_ROUTELIST
load_DNSConfiguration(bundle, ^(Boolean inSync) { dispatch_async(__network_change_queue(), ^{
S_dnsinfo_synced = inSync;
if (inSync &&
((S_network_change_needed & NETWORK_CHANGE_DNS) == 0)) {
post_network_change(NETWORK_CHANGE_DNS);
} else {
post_network_change_when_ready();
}
});
});
load_NetworkInformation(bundle, ^(Boolean inSync) { dispatch_async(__network_change_queue(), ^{
S_nwi_synced = inSync;
post_network_change_when_ready();
});
});
#endif
#if !TARGET_OS_SIMULATOR && !TEST_IPV4_ROUTELIST && !TEST_IPV6_ROUTELIST
StartIPMonitorControlServer();
#endif
dns_configuration_init(bundle);
proxy_configuration_init(bundle);
ip_plugin_init();
if (S_session != NULL) {
dns_configuration_monitor(S_session, IPMonitorNotify);
}
#if !TARGET_OS_SIMULATOR && !TEST_IPV4_ROUTELIST && !TEST_IPV6_ROUTELIST
load_hostname(TRUE);
#endif
#if !TARGET_OS_IPHONE
load_smb_configuration(TRUE);
#endif
return;
}
#pragma mark -
#pragma mark Standalone test code
#ifdef TEST_IPMONITOR
int
main(int argc, char **argv)
{
_sc_log = FALSE;
S_IPMonitor_debug = kDebugFlag1;
if (argc > 1) {
S_IPMonitor_debug = strtoul(argv[1], NULL, 0);
}
load_IPMonitor(CFBundleGetMainBundle(), FALSE);
prime_IPMonitor();
S_IPMonitor_debug = kDebugFlag1;
CFRunLoopRun();
exit(0);
return 0;
}
#endif
#ifdef TEST_ROUTELIST
struct route {
const char * dest;
int prefix_length;
const char * gateway;
const char * ifname;
};
#endif
#if TEST_IPV4_ROUTELIST
typedef struct {
const char * addr;
int prefix_length;
const char * dest;
const char * router;
const char * ifname;
Rank rank;
const CFStringRef * primary_rank;
struct route * additional_routes;
int additional_routes_count;
struct route * excluded_routes;
int excluded_routes_count;
} IPv4ServiceContents;
typedef const IPv4ServiceContents * IPv4ServiceContentsRef;
struct route loop_routelist[] = {
{ "1.1.1.1", 32, "1.1.1.2", NULL },
{ "1.1.1.2", 32, "1.1.1.3", NULL },
{ "1.1.1.3", 32, "1.1.1.4", NULL },
{ "1.1.1.4", 32, "1.1.1.5", NULL },
{ "1.1.1.5", 32, "1.1.1.6", NULL },
{ "1.1.1.6", 32, "1.1.1.7", NULL },
{ "1.1.1.7", 32, "1.1.1.8", NULL },
{ "1.1.1.8", 32, "1.1.1.9", NULL },
{ "1.1.1.9", 32, "1.1.1.10", NULL },
{ "1.1.1.10", 32, "1.1.1.11", NULL },
{ "1.1.1.11", 32, "1.1.1.1", NULL },
};
struct route vpn_routelist[] = {
{ "10.1.3.0", 24, "17.153.46.24", NULL },
{ "10.1.4.0", 24, "17.153.46.24", NULL },
{ "10.1.5.0", 24, "17.153.46.24", NULL },
{ "10.1.6.0", 24, "17.153.46.24", NULL },
{ "10.1.7.0", 24, "17.153.46.24", NULL },
{ "10.16.0.0", 12, "17.153.46.24", NULL },
{ "10.45.0.0", 16, "17.153.46.24", NULL },
{ "10.53.0.0", 16, "17.153.46.24", NULL },
{ "10.70.0.0", 15, "17.153.46.24", NULL },
{ "10.74.0.0", 15, "17.153.46.24", NULL },
{ "10.90.0.0", 15, "17.153.46.24", NULL },
{ "10.91.0.0", 16, "17.153.46.24", NULL },
{ "10.100.0.0", 16, "17.153.46.24", NULL },
{ "10.113.0.0", 16, "17.153.46.24", NULL },
{ "10.128.0.0", 9, "17.153.46.24", NULL },
{ "17.0.0.0", 9, "17.153.46.24", NULL },
{ "17.34.0.0", 16, "17.153.46.24", NULL },
{ "17.112.156.53", 32, "17.153.46.24", NULL },
{ "17.128.0.0", 10, "17.153.46.24", NULL },
{ "17.149.0.121", 32, "17.153.46.24", NULL },
{ "17.149.7.200", 32, "17.153.46.24", NULL },
{ "17.153.46.24", 32, "17.153.46.24", NULL },
{ "17.192.0.0", 12, "17.153.46.24", NULL },
{ "17.208.0.0", 15, "17.153.46.24", NULL },
{ "17.211.0.0", 16, "17.153.46.24", NULL },
{ "17.212.0.0", 14, "17.153.46.24", NULL },
{ "17.216.0.0", 13, "17.153.46.24", NULL },
{ "17.224.0.0", 12, "17.153.46.24", NULL },
{ "17.240.0.0", 16, "17.153.46.24", NULL },
{ "17.241.0.0", 16, "17.153.46.24", NULL },
{ "17.248.0.0", 14, "17.153.46.24", NULL },
{ "17.251.104.200", 32, "17.153.46.24", NULL },
{ "17.252.0.0", 16, "17.153.46.24", NULL },
{ "17.253.0.0", 16, "17.153.46.24", NULL },
{ "17.254.0.0", 16, "17.153.46.24", NULL },
{ "17.255.0.0", 16, "17.153.46.24", NULL },
{ "151.193.141.0", 27, "17.153.46.24", NULL },
{ "172.16.2.0", 24, "17.153.46.24", NULL },
{ "192.35.50.0", 24, "17.153.46.24", NULL },
{ "204.179.20.0", 24, "17.153.46.24", NULL },
{ "206.112.116.0", 24, "17.153.46.24", NULL },
};
struct route vpn_routelist_ext[] = {
{ "17.151.63.82", 32, "10.0.0.1", "en0" },
{ "17.151.63.81", 32, "17.151.63.81", "en0" },
{ "17.151.63.80", 32, NULL, NULL },
{ "17.1.0.0", 16, NULL, NULL },
{ "17.2.0.0", 24, NULL, NULL },
{ "10.0.0.0", 24, NULL, NULL },
};
const IPv4ServiceContents en0_10 = {
"10.0.0.10", 24, NULL, "10.0.0.1", "en0", 10, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents en0_15 = {
"10.0.0.19", 24, NULL, "10.0.0.1", "en0", 15, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents en0_30 = {
"10.0.0.11", 24, NULL, "10.0.0.1", "en0", 30, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents en0_40 = {
"10.0.0.12", 24, NULL, "10.0.0.1", "en0", 40, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents en0_50 = {
"10.0.0.13", 24, NULL, "10.0.0.1", "en0", 50, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents en0_110 = {
"192.168.2.10", 24, NULL, "192.168.2.1", "en0", 110, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents en0_1 = {
"17.202.40.191", 22, NULL, "17.202.20.1", "en0", 1, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents en1_20 = {
"10.0.0.20", 24, NULL, "10.0.0.1", "en1", 20, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents en1_2 = {
"17.202.42.24", 22, NULL, "17.202.20.1", "en1", 2, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents en1_125 = {
"192.168.2.20", 24, NULL, "192.168.2.1", "en1", 125, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents fw0_25 = {
"192.168.2.30", 24, NULL, "192.168.2.1", "fw0", 25, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents fw0_21 = {
"192.168.3.30", 24, NULL, "192.168.3.1", "fw0", 21, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents ppp0_0_1 = {
"17.219.156.22", -1, "17.219.156.1", "17.219.156.1", "ppp0", 0, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents utun0 = {
"17.153.46.24", -1, "17.153.46.24", "17.153.46.24", "utun0", 20, NULL, vpn_routelist, countof(vpn_routelist), vpn_routelist_ext, countof(vpn_routelist_ext)
};
const IPv4ServiceContents en0_test6 = {
"17.202.42.113", 22, NULL, "17.202.40.1", "en0", 2, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents en1_test6 = {
"17.202.42.111", 22, NULL, "17.202.40.1", "en1", 3, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents en2_test6 = {
"17.255.98.164", 20, NULL, "17.255.96.1", "en2", 1, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents en0_test7 = {
"17.202.42.113", 22, NULL, "17.202.40.1", "en0", 3, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents en1_test7 = {
"17.202.42.111", 22, NULL, "17.202.40.1", "en1", 2, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents en2_test7 = {
"17.255.98.164", 20, NULL, "17.255.96.1", "en2", 1, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents fw0_test6_and_7 = {
"169.254.11.33", 16, NULL, NULL, "fw0", 0x0ffffff, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents en0_10_last = {
"10.0.0.10", 24, NULL, "10.0.0.1", "en0", 10, &kSCValNetServicePrimaryRankLast, NULL, 0, NULL, 0
};
const IPv4ServiceContents en0_10_never = {
"10.0.0.10", 24, NULL, "10.0.0.1", "en0", 10, &kSCValNetServicePrimaryRankNever, NULL, 0, NULL, 0
};
const IPv4ServiceContents en1_20_first = {
"10.0.0.20", 24, NULL, "10.0.0.1", "en1", 20, &kSCValNetServicePrimaryRankFirst, NULL, 0, NULL, 0
};
const IPv4ServiceContents en1_20_never = {
"10.0.0.20", 24, NULL, "10.0.0.1", "en1", 20, &kSCValNetServicePrimaryRankNever, NULL, 0, NULL, 0
};
const IPv4ServiceContents en1_20_other_never = {
"192.168.2.50", 24, NULL, "192.168.2.1", "en1", 20, &kSCValNetServicePrimaryRankNever, NULL, 0, NULL, 0
};
const IPv4ServiceContents en0_linklocal = {
"169.254.22.44", 16, NULL, NULL, "en0", 0xfffff, NULL, NULL, 0, NULL, 0
};
const IPv4ServiceContents en0_route_loop = {
"192.168.130.16", 24, NULL, "192.168.130.1", "en0", 2, NULL, loop_routelist, countof(loop_routelist), NULL, 0
};
typedef struct {
const char * name;
IPv4ServiceContentsRef test[];
} IPv4RouteTest, * IPv4RouteTestRef;
static IPv4RouteTest test1 = {
"test1",
{
&en0_40,
&en0_15,
&fw0_25,
&en0_30,
&en1_20,
&en0_50,
&en0_10,
NULL
}
};
static IPv4RouteTest test2 = {
"test2",
{
&en0_40,
&fw0_25,
&en0_30,
&en1_20,
&en0_50,
&en0_10,
NULL
}
};
static IPv4RouteTest test3 = {
"test3",
{
&en0_40,
&en1_20,
&en0_50,
&en0_10,
&en0_110,
&en1_125,
&fw0_25,
&fw0_21,
&en0_40,
&en0_30,
NULL
}
};
static IPv4RouteTest test4 = {
"test4",
{
&en0_1,
&en0_40,
&en0_30,
&en1_20,
&en1_2,
NULL
}
};
static IPv4RouteTest test5 = {
"test5",
{
&ppp0_0_1,
&en0_1,
&en0_40,
&en0_30,
&en1_20,
&en1_2,
NULL
}
};
static IPv4RouteTest test6 = {
"test6",
{
&en0_test6,
&en1_test6,
&en2_test6,
&fw0_test6_and_7,
NULL
}
};
static IPv4RouteTest test7 = {
"test7",
{
&en0_test7,
&en1_test7,
&en2_test7,
&fw0_test6_and_7,
NULL
}
};
static IPv4RouteTest test8 = {
"test8",
{
&en0_10,
&en1_20,
NULL
}
};
static IPv4RouteTest test9 = {
"test9",
{
&en0_10,
&en1_20_first,
&fw0_25,
NULL
}
};
static IPv4RouteTest test10 = {
"test10",
{
&en0_10_last,
&en1_20,
&fw0_25,
NULL
}
};
static IPv4RouteTest test11 = {
"test11",
{
&en0_10_never,
&en1_20,
&fw0_25,
NULL
}
};
static IPv4RouteTest test12 = {
"test12",
{
&en0_10,
&en1_20,
NULL
}
};
static IPv4RouteTest test13 = {
"test13",
{
&en0_10,
&en1_20_never,
NULL
}
};
static IPv4RouteTest test14 = {
"test14",
{
&en1_20_never,
NULL
}
};
static IPv4RouteTest test15 = {
"test15",
{
&en0_linklocal,
NULL
}
};
static IPv4RouteTest test16 = {
"test16",
{
&en0_10,
&utun0,
NULL
}
};
static IPv4RouteTest test17 = {
"test17",
{
&en0_10,
&en1_20_other_never,
NULL
}
};
static IPv4RouteTest test18 = {
"test18",
{
&en0_route_loop,
NULL
}
};
static IPv4RouteTestRef ipv4_tests[] = {
&test1,
&test2,
&test3,
&test4,
&test5,
&test6,
&test7,
&test8,
&test9,
&test10,
&test11,
&test12,
&test13,
&test14,
&test15,
&test16,
&test17,
&test18,
NULL
};
static boolean_t
ipv4_prefix_length_is_valid(int prefix_length)
{
if (prefix_length < 0 || prefix_length > IPV4_ROUTE_ALL_BITS_SET) {
return (FALSE);
}
return (TRUE);
}
static void
dict_add_string(CFMutableDictionaryRef dict, CFStringRef prop_name,
const char * str)
{
CFStringRef prop_val;
if (str == NULL) {
return;
}
prop_val = CFStringCreateWithCString(NULL,
str,
kCFStringEncodingASCII);
CFDictionarySetValue(dict, prop_name, prop_val);
CFRelease(prop_val);
return;
}
static void
dict_add_string_as_array(CFMutableDictionaryRef dict, CFStringRef prop_name,
const char * str)
{
CFArrayRef array;
CFStringRef prop_val;
if (str == NULL) {
return;
}
prop_val = CFStringCreateWithCString(NULL,
str,
kCFStringEncodingASCII);
array = CFArrayCreate(NULL,
(const void **)&prop_val, 1,
&kCFTypeArrayCallBacks);
CFRelease(prop_val);
CFDictionarySetValue(dict, prop_name, array);
CFRelease(array);
return;
}
static void
dict_add_ip(CFMutableDictionaryRef dict, CFStringRef prop_name,
struct in_addr ip)
{
CFStringRef str;
str = my_CFStringCreateWithInAddr(ip);
CFDictionarySetValue(dict, prop_name, str);
CFRelease(str);
return;
}
static void
dict_add_ip_as_array(CFMutableDictionaryRef dict, CFStringRef prop_name,
struct in_addr ip)
{
CFArrayRef array;
CFStringRef str;
str = my_CFStringCreateWithInAddr(ip);
array = CFArrayCreate(NULL,
(const void **)&str, 1,
&kCFTypeArrayCallBacks);
CFRelease(str);
CFDictionarySetValue(dict, prop_name, array);
CFRelease(array);
return;
}
static void
dict_insert_routes(CFMutableDictionaryRef dict, CFStringRef prop_name,
struct route * routes, int routes_count)
{
int i;
CFMutableArrayRef route_list;
struct route * scan;
if (routes == NULL || routes_count == 0) {
return;
}
route_list = CFArrayCreateMutable(NULL, routes_count,
&kCFTypeArrayCallBacks);
for (i = 0, scan = routes; i < routes_count; i++, scan++) {
struct in_addr mask;
CFMutableDictionaryRef route_dict;
route_dict
= CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
dict_add_string(route_dict, kSCPropNetIPv4RouteDestinationAddress,
scan->dest);
if (ipv4_prefix_length_is_valid(scan->prefix_length)) {
mask.s_addr = htonl(prefix_to_mask32(scan->prefix_length));
dict_add_ip(route_dict, kSCPropNetIPv4RouteSubnetMask, mask);
}
dict_add_string(route_dict, kSCPropNetIPv4RouteGatewayAddress,
scan->gateway);
dict_add_string(route_dict, kSCPropNetIPv4RouteInterfaceName,
scan->ifname);
CFArrayAppendValue(route_list, route_dict);
CFRelease(route_dict);
}
CFDictionarySetValue(dict, prop_name, route_list);
CFRelease(route_list);
return;
}
static CFDictionaryRef
make_IPv4_dict(IPv4ServiceContentsRef t)
{
CFMutableDictionaryRef dict;
dict = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
dict_add_string_as_array(dict, kSCPropNetIPv4Addresses, t->addr);
if (ipv4_prefix_length_is_valid(t->prefix_length)) {
struct in_addr mask;
mask.s_addr = htonl(prefix_to_mask32(t->prefix_length));
dict_add_ip_as_array(dict, kSCPropNetIPv4SubnetMasks, mask);
}
dict_add_string_as_array(dict, kSCPropNetIPv4DestAddresses, t->dest);
dict_add_string(dict, kSCPropNetIPv4Router, t->router);
dict_add_string(dict, kSCPropInterfaceName, t->ifname);
dict_add_string(dict, kSCPropConfirmedInterfaceName, t->ifname);
dict_insert_routes(dict, kSCPropNetIPv4AdditionalRoutes,
t->additional_routes, t->additional_routes_count);
dict_insert_routes(dict, kSCPropNetIPv4ExcludedRoutes,
t->excluded_routes, t->excluded_routes_count);
return (dict);
}
typedef enum {
kDirectionForwards = 0,
kDirectionBackwards = 1
} Direction;
typedef enum {
kLogRouteDisabled = 0,
kLogRouteEnabled = 1
} LogRoute;
static IPv4RouteListRef
make_IPv4RouteList_for_test(IPv4RouteListRef list,
IPv4ServiceContentsRef test,
LogRoute log_it)
{
CFDictionaryRef dict;
IPv4RouteListRef r;
Rank rank;
Rank rank_assertion = kRankAssertionDefault;
CFNumberRef rank_assertion_cf = NULL;
Boolean rank_assertion_is_set = FALSE;
IPv4RouteListRef ret = NULL;
IPV4_ROUTES_BUF_DECL(routes);
dict = make_IPv4_dict(test);
if (dict == NULL) {
fprintf(stderr, "make_IPv4_dict failed\n");
exit(1);
}
if (test->primary_rank != NULL) {
rank_assertion
= PrimaryRankGetRankAssertion(*test->primary_rank,
&rank_assertion_is_set);
if (rank_assertion_is_set) {
rank_assertion_cf
= CFNumberCreate(NULL, kCFNumberSInt32Type, &rank_assertion);
}
}
r = IPv4RouteListCreateWithDictionary(routes, dict, rank_assertion_cf);
my_CFRelease(&rank_assertion_cf);
if (r == NULL) {
fprintf(stderr, "IPv4RouteListCreateWithDictionary failed\n");
exit(1);
}
if (rank_assertion == kRankAssertionScoped) {
rank_assertion = kRankAssertionNever;
}
rank = RankMake(test->rank, rank_assertion);
if (log_it == kLogRouteEnabled
&& (S_IPMonitor_debug & kDebugFlag4) != 0) {
CFStringRef descr;
descr = IPv4RouteListCopyDescription(r);
SCPrint(TRUE, stdout, CFSTR("Adding %@\n"), descr);
CFRelease(descr);
}
ret = IPv4RouteListAddRouteList(list, 1, r, rank);
if (r != routes) {
free(r);
}
CFRelease(dict);
return (ret);
}
static IPv4RouteListRef
make_IPv4RouteList(IPv4ServiceContentsRef * test, Direction direction,
LogRoute log_it)
{
IPv4RouteListRef ret = NULL;
IPv4ServiceContentsRef * scan;
switch (direction) {
case kDirectionBackwards:
for (scan = test; *scan != NULL; scan++) {
}
for (scan--; scan >= test; scan--) {
ret = make_IPv4RouteList_for_test(ret, *scan, log_it);
}
break;
default:
case kDirectionForwards:
for (scan = test; *scan != NULL; scan++) {
ret = make_IPv4RouteList_for_test(ret, *scan, log_it);
}
break;
}
IPv4RouteListFinalize(ret);
return (ret);
}
#define EMPHASIS_CHARS "================="
static boolean_t
routelist_build_test(IPv4RouteTestRef test)
{
CFStringRef descr;
boolean_t ret = FALSE;
IPv4RouteListRef routes1;
IPv4RouteListRef routes2;
printf("\n" EMPHASIS_CHARS "> RouteList Build '%s' <"
EMPHASIS_CHARS "\n",
test->name);
routes1 = make_IPv4RouteList(test->test, kDirectionForwards,
kLogRouteEnabled);
if ((S_IPMonitor_debug & kDebugFlag4) != 0) {
if (routes1 != NULL) {
descr = IPv4RouteListCopyDescription(routes1);
SCPrint(TRUE, stdout, CFSTR("Routes are %@\n"), descr);
CFRelease(descr);
}
}
routes2 = make_IPv4RouteList(test->test, kDirectionBackwards,
kLogRouteEnabled);
if ((S_IPMonitor_debug & kDebugFlag4) != 0) {
if (routes2 != NULL) {
descr = IPv4RouteListCopyDescription(routes2);
SCPrint(TRUE, stdout, CFSTR("Routes are %@\n"), descr);
CFRelease(descr);
}
}
if ((routes1 != NULL && routes2 == NULL)
|| (routes1 == NULL && routes2 != NULL)) {
fprintf(stderr, "routes1 is %sNULL but routes2 is %sNULL\n",
(routes1 != NULL) ? "not " : "",
(routes2 != NULL) ? "not " : "");
}
else if (routes1 != NULL && routes2 != NULL) {
if (routes1->count != routes2->count) {
fprintf(stderr, "routes1 count %d != routes 2 count %d\n",
routes1->count, routes2->count);
}
else if (bcmp(routes1, routes2,
IPv4RouteListComputeSize(routes1->count)) != 0) {
fprintf(stderr, "routes1 and routes2 are different\n");
}
else {
printf("routes1 and routes2 are the same\n");
ret = TRUE;
}
}
if (routes1 != NULL) {
free(routes1);
}
if (routes2 != NULL) {
free(routes2);
}
printf(EMPHASIS_CHARS "> RouteList Build '%s': %s <"
EMPHASIS_CHARS "\n",
test->name, ret ? "PASSED" : "FAILED");
return (ret);
}
static void
apply_test(IPv4RouteTestRef old_test, IPv4RouteTestRef new_test)
{
IPv4RouteListRef new_routes;
IPv4RouteListRef old_routes;
printf("\n" EMPHASIS_CHARS "> Apply '%s', '%s' Begin <"
EMPHASIS_CHARS "\n",
old_test->name, new_test->name);
old_routes = make_IPv4RouteList(old_test->test, kDirectionForwards,
kLogRouteDisabled);
new_routes = make_IPv4RouteList(new_test->test, kDirectionForwards,
kLogRouteDisabled);
if (old_routes == NULL) {
printf("No Old Routes\n");
}
else {
printf("Old routes ('%s') = ", old_test->name);
IPv4RouteListPrint(old_routes);
}
IPv4RouteListApply(NULL, old_routes, -1);
if (new_routes == NULL) {
printf("No New Routes\n");
}
else {
printf("New Routes ('%s') = ", new_test->name);
IPv4RouteListPrint(new_routes);
}
IPv4RouteListApply(old_routes, new_routes, -1);
if (old_routes != NULL) {
free(old_routes);
}
if (new_routes != NULL) {
free(new_routes);
}
printf(EMPHASIS_CHARS "> Apply '%s', '%s' End <"
EMPHASIS_CHARS "\n",
old_test->name, new_test->name);
return;
}
int
main(int argc, char **argv)
{
IPv4RouteTestRef * test;
_sc_log = FALSE;
_sc_verbose = (argc > 1) ? TRUE : FALSE;
S_IPMonitor_debug = kDebugFlag1 | kDebugFlag2 | kDebugFlag4;
if (argc > 1) {
S_IPMonitor_debug = strtoul(argv[1], NULL, 0);
}
for (test = ipv4_tests; *test != NULL; test++) {
if (!routelist_build_test(*test)) {
fprintf(stderr, "%s failed\n", (*test)->name);
exit(1);
}
}
for (test = ipv4_tests; *test != NULL; test++) {
IPv4RouteTestRef * test2;
for (test2 = test + 1; *test2 != NULL; test2++) {
apply_test(*test, *test2);
apply_test(*test2, *test);
}
}
{
char cmd[128];
printf("\nChecking for leaks\n");
sprintf(cmd, "leaks %d 2>&1", getpid());
fflush(stdout);
(void)system(cmd);
}
exit(0);
return (0);
}
#endif
#if TEST_IPV6_ROUTELIST
typedef struct {
const char * addr;
int prefix_length;
const char * dest;
} IPv6Address;
typedef const IPv6Address * IPv6AddressRef;
typedef struct {
IPv6AddressRef addr;
int addr_count;
const char * router;
const char * ifname;
Rank rank;
const CFStringRef * primary_rank;
struct route * additional_routes;
int additional_routes_count;
struct route * excluded_routes;
int excluded_routes_count;
} IPv6ServiceContents;
typedef const IPv6ServiceContents * IPv6ServiceContentsRef;
struct route loop_routelist[] = {
{ "2620:149:4:f01:225:ff:fecc:89a1", 128,
"2620:149:4:f01:225:ff:fecc:89a2", NULL },
{ "2620:149:4:f01:225:ff:fecc:89a2", 128,
"2620:149:4:f01:225:ff:fecc:89a3", NULL },
{ "2620:149:4:f01:225:ff:fecc:89a3", 128,
"2620:149:4:f01:225:ff:fecc:89a4", NULL },
{ "2620:149:4:f01:225:ff:fecc:89a4", 128,
"2620:149:4:f01:225:ff:fecc:89a5", NULL },
{ "2620:149:4:f01:225:ff:fecc:89a5", 128,
"2620:149:4:f01:225:ff:fecc:89a6", NULL },
{ "2620:149:4:f01:225:ff:fecc:89a6", 128,
"2620:149:4:f01:225:ff:fecc:89a7", NULL },
{ "2620:149:4:f01:225:ff:fecc:89a7", 128,
"2620:149:4:f01:225:ff:fecc:89a8", NULL },
{ "2620:149:4:f01:225:ff:fecc:89a8", 128,
"2620:149:4:f01:225:ff:fecc:89a9", NULL },
{ "2620:149:4:f01:225:ff:fecc:89a9", 128,
"2620:149:4:f01:225:ff:fecc:89aa", NULL },
{ "2620:149:4:f01:225:ff:fecc:89aa", 128,
"2620:149:4:f01:225:ff:fecc:89ab", NULL },
{ "2620:149:4:f01:225:ff:fecc:89ab", 128,
"2620:149:4:f01:225:ff:fecc:89a1", NULL },
};
struct route vpn_routelist[] = {
{ "2010:470:1f05:3cb::", 64,
"fe80::2d0:bcff:fe3d:8c00", NULL },
{ "2010:222:3fa5:acb::", 48,
"fe80::2d0:bcff:fe3d:8c00", NULL },
{ "2010:222:3fa5:1234::", 40,
"fe80::2d0:bcff:fe3d:8c00", NULL },
{ "2010:222:3fa5:5678::", 40,
NULL, NULL },
};
struct route vpn_routelist_ext[] = {
{ "2020:299:a:e02:825:1ed:fecc:abab", 128, NULL, NULL },
};
struct route en1_routelist_ext[] = {
{ "2020:299:abcd:ef12::", 64, NULL, NULL },
};
static const IPv6Address en0_addr1[] = {
{ "2001:470:1f05:3cb:cabc:c8ff:fe96:9601", 64, NULL },
{ "2001:470:1f05:3cb:5c95:58b1:b956:6101", 64, NULL }
};
static const IPv6Address en0_addr2[] = {
{ "2001:470:1f05:3cb:cabc:c8ff:fe96:9602", 64, NULL },
{ "2001:470:1f05:3cb:5c95:58b1:b956:6102", 64, NULL }
};
static const IPv6Address en0_addr3[] = {
{ "2001:470:1f05:3cb:cabc:c8ff:fe96:9603", 64, NULL },
{ "2001:470:1f05:3cb:5c95:58b1:b956:6103", 64, NULL }
};
static const IPv6Address en0_addr4[] = {
{ "2001:470:1f05:3cb:cabc:c8ff:fe96:9604", 64, NULL },
{ "2001:470:1f05:3cb:5c95:58b1:b956:6104", 64, NULL }
};
static const IPv6Address en0_addr5[] = {
{ "2001:470:1f05:3cb:cabc:c8ff:fe96:9605", 64, NULL },
{ "2001:470:1f05:3cb:5c95:58b1:b956:6105", 64, NULL }
};
static const IPv6Address en0_addr6[] = {
{ "2020:299:abcd:ef12:1:2:3:4", 64, NULL },
};
static const IPv6Address en0_lladdr[] = {
{ "fe80::cabc:c8ff:fe96:96af", 64, NULL }
};
static const IPv6Address en1_addr[] = {
{ "2001:470:1f05:3cb:cabc:c8ff:fed9:125a", 64, NULL },
{ "2001:470:1f05:3cb:2d5e:4ec3:304:5b9c", 64, NULL }
};
static const IPv6Address utun0_addr[] = {
{ "2620:149:4:f01:225:ff:fecc:89aa", 64, NULL },
};
static const IPv6Address fw0_addr1[] = {
{ "2011:470:1f05:3cb:cabc:c8ff:fe96:ab01", 64, NULL },
{ "2011:470:1f05:3cb:5c95:58b1:b956:ab01", 64, NULL }
};
static const IPv6ServiceContents en0_10 = {
en0_addr1, countof(en0_addr1),
"fe80::21f:f3ff:fe43:1abf", "en0", 10, NULL, NULL, 0, NULL, 0
};
static const IPv6ServiceContents en0_15 = {
en0_addr2, countof(en0_addr2),
"fe80::21f:f3ff:fe43:1abf", "en0", 15, NULL, NULL, 0, NULL, 0
};
static const IPv6ServiceContents en0_30 = {
en0_addr3, countof(en0_addr3),
"fe80::21f:f3ff:fe43:1abf", "en0", 30, NULL, NULL, 0, NULL, 0
};
static const IPv6ServiceContents en0_40 = {
en0_addr4, countof(en0_addr4),
"fe80::21f:f3ff:fe43:1abf", "en0", 40, NULL, NULL, 0, NULL, 0
};
static const IPv6ServiceContents en0_50 = {
en0_addr5, countof(en0_addr5),
"fe80::21f:f3ff:fe43:1abf", "en0", 50, NULL, NULL, 0, NULL, 0
};
static const IPv6ServiceContents en0_10_a = {
en0_addr6, countof(en0_addr6),
"fe80::21f:f3ff:fe43:1abf", "en0", 10, NULL, NULL, 0, NULL, 0
};
static const IPv6ServiceContents fw0_25 = {
fw0_addr1, countof(fw0_addr1),
"fe80::21f:f3ff:fe43:1abf", "fw0", 25, NULL, NULL, 0, NULL, 0
};
static const IPv6ServiceContents en1_20 = {
en1_addr, countof(en1_addr),
"fe80::21f:f3ff:fe43:1abf", "en1", 20, NULL, NULL, 0, NULL, 0
};
static const IPv6ServiceContents en1_10_ext = {
en1_addr, countof(en1_addr),
"fe80::21f:f3ff:fe43:1abf", "en1", 10, NULL, NULL, 0,
en1_routelist_ext, countof(en1_routelist_ext)
};
static const IPv6ServiceContents en0_0_lladdr = {
en0_lladdr, countof(en0_lladdr),
"fe80::21f:f3ff:fe43:1abf", "en0", 20, NULL, NULL, 0, NULL, 0
};
static const IPv6ServiceContents en0_loop = {
en0_addr1, countof(en0_addr1),
"fe80::21f:f3ff:fe43:1abf", "en0", 10, NULL,
loop_routelist, countof(loop_routelist), NULL, 0
};
static const IPv6ServiceContents utun0 = {
utun0_addr, countof(utun0_addr),
"fe80::2d0:bcff:fe3d:8c00", "utun0", 40, NULL,
vpn_routelist, countof(vpn_routelist),
vpn_routelist_ext, countof(vpn_routelist_ext),
};
typedef struct {
const char * name;
IPv6ServiceContentsRef test[];
} IPv6RouteTest, * IPv6RouteTestRef;
static IPv6RouteTest test1 = {
"test1",
{
&en0_40,
&en0_15,
&fw0_25,
&en0_30,
&en1_20,
&en0_50,
&en0_10,
NULL
}
};
static IPv6RouteTest test2 = {
"test2",
{
&en0_40,
&fw0_25,
&en0_30,
&en1_20,
&en0_50,
&en0_10,
NULL
}
};
static IPv6RouteTest test3 = {
"test3",
{
&en0_10_a,
&en1_10_ext,
NULL
}
};
static IPv6RouteTest test4 = {
"test4",
{
&en0_loop,
&en1_20,
NULL
}
};
static IPv6RouteTest test5 = {
"test5",
{
&en0_10,
&utun0,
&en0_0_lladdr,
&en1_20,
NULL
}
};
static IPv6RouteTestRef ipv6_tests[] = {
&test1,
&test2,
&test3,
&test4,
&test5,
NULL
};
static void
dict_add_string(CFMutableDictionaryRef dict, CFStringRef prop_name,
const char * str)
{
CFStringRef prop_val;
if (str == NULL) {
return;
}
prop_val = CFStringCreateWithCString(NULL,
str,
kCFStringEncodingASCII);
CFDictionarySetValue(dict, prop_name, prop_val);
CFRelease(prop_val);
return;
}
static void
dict_add_int(CFMutableDictionaryRef dict, CFStringRef prop_name,
int int_val)
{
CFNumberRef num;
num = CFNumberCreate(NULL, kCFNumberIntType, &int_val);
CFDictionarySetValue(dict, prop_name, num);
CFRelease(num);
return;
}
static void
dict_insert_v6_routes(CFMutableDictionaryRef dict, CFStringRef prop_name,
struct route * routes, int routes_count)
{
int i;
CFMutableArrayRef route_list;
struct route * scan;
if (routes == NULL || routes_count == 0) {
return;
}
route_list = CFArrayCreateMutable(NULL, routes_count,
&kCFTypeArrayCallBacks);
for (i = 0, scan = routes; i < routes_count; i++, scan++) {
CFMutableDictionaryRef route_dict;
route_dict = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
dict_add_string(route_dict, kSCPropNetIPv6RouteDestinationAddress,
scan->dest);
dict_add_int(route_dict, kSCPropNetIPv6PrefixLength,
scan->prefix_length);
dict_add_string(route_dict, kSCPropNetIPv6RouteGatewayAddress,
scan->gateway);
dict_add_string(route_dict, kSCPropNetIPv6RouteInterfaceName,
scan->ifname);
CFArrayAppendValue(route_list, route_dict);
CFRelease(route_dict);
}
CFDictionarySetValue(dict, prop_name, route_list);
CFRelease(route_list);
return;
}
static void
array_add_string(CFMutableArrayRef array, const char * c_str)
{
CFStringRef str;
str = CFStringCreateWithCString(NULL,
c_str,
kCFStringEncodingUTF8);
CFArrayAppendValue(array, str);
CFRelease(str);
return;
}
static void
array_add_int(CFMutableArrayRef array, int int_val)
{
CFNumberRef num;
num = CFNumberCreate(NULL, kCFNumberIntType, &int_val);
CFArrayAppendValue(array, num);
CFRelease(num);
return;
}
static void
dict_add_ipv6_addressing(CFMutableDictionaryRef dict,
IPv6AddressRef list, int list_count)
{
CFMutableArrayRef addr = NULL;
CFMutableArrayRef dest = NULL;
int i;
CFMutableArrayRef prefix = NULL;
IPv6AddressRef scan;
if (list == NULL || list_count == 0) {
return;
}
for (i = 0, scan = list; i < list_count; i++, scan++) {
if (scan->addr != NULL) {
if (addr == NULL) {
addr = CFArrayCreateMutable(NULL, list_count,
&kCFTypeArrayCallBacks);
}
array_add_string(addr, scan->addr);
}
if (scan->prefix_length >= 0) {
if (prefix == NULL) {
prefix = CFArrayCreateMutable(NULL, list_count,
&kCFTypeArrayCallBacks);
}
array_add_int(prefix, scan->prefix_length);
}
if (scan->dest != NULL) {
if (dest == NULL) {
dest = CFArrayCreateMutable(NULL, list_count,
&kCFTypeArrayCallBacks);
}
array_add_string(dest, scan->dest);
}
}
if (addr != NULL) {
CFDictionarySetValue(dict, kSCPropNetIPv6Addresses, addr);
CFRelease(addr);
}
if (dest != NULL) {
CFDictionarySetValue(dict, kSCPropNetIPv6DestAddresses, dest);
CFRelease(dest);
}
if (prefix != NULL) {
CFDictionarySetValue(dict, kSCPropNetIPv6PrefixLength, prefix);
CFRelease(prefix);
}
return;
}
static CFDictionaryRef
make_IPv6_dict(IPv6ServiceContentsRef t)
{
CFMutableDictionaryRef dict;
dict = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
dict_add_ipv6_addressing(dict, t->addr, t->addr_count);
dict_add_string(dict, kSCPropNetIPv6Router, t->router);
dict_add_string(dict, kSCPropInterfaceName, t->ifname);
dict_insert_v6_routes(dict, kSCPropNetIPv6AdditionalRoutes,
t->additional_routes, t->additional_routes_count);
dict_insert_v6_routes(dict, kSCPropNetIPv6ExcludedRoutes,
t->excluded_routes, t->excluded_routes_count);
return (dict);
}
typedef enum {
kDirectionForwards = 0,
kDirectionBackwards = 1
} Direction;
typedef enum {
kLogRouteDisabled = 0,
kLogRouteEnabled = 1
} LogRoute;
static IPv6RouteListRef
make_IPv6RouteList_for_test(IPv6RouteListRef list,
IPv6ServiceContentsRef test,
LogRoute log_it)
{
CFDictionaryRef dict;
IPv6RouteListRef r;
Rank rank;
Rank rank_assertion = kRankAssertionDefault;
CFNumberRef rank_assertion_cf = NULL;
Boolean rank_assertion_is_set = FALSE;
IPv6RouteListRef ret = NULL;
IPV6_ROUTES_BUF_DECL(routes);
dict = make_IPv6_dict(test);
if (dict == NULL) {
fprintf(stderr, "make_IPv6_dict failed\n");
exit(1);
}
if (test->primary_rank != NULL) {
rank_assertion
= PrimaryRankGetRankAssertion(*test->primary_rank,
&rank_assertion_is_set);
if (rank_assertion_is_set) {
rank_assertion_cf
= CFNumberCreate(NULL, kCFNumberSInt32Type, &rank_assertion);
}
}
r = IPv6RouteListCreateWithDictionary(routes, dict, rank_assertion_cf);
my_CFRelease(&rank_assertion_cf);
if (r == NULL) {
fprintf(stderr, "IPv6RouteListCreateWithDictionary failed\n");
exit(1);
}
if (rank_assertion == kRankAssertionScoped) {
rank_assertion = kRankAssertionNever;
}
rank = RankMake(test->rank, rank_assertion);
if (log_it == kLogRouteEnabled
&& (S_IPMonitor_debug & kDebugFlag4) != 0) {
CFStringRef descr;
descr = IPv6RouteListCopyDescription(r);
SCPrint(TRUE, stdout, CFSTR("Adding %@\n"), descr);
CFRelease(descr);
}
ret = IPv6RouteListAddRouteList(list, 1, r, rank);
if (r != routes) {
free(r);
}
CFRelease(dict);
return (ret);
}
static IPv6RouteListRef
make_IPv6RouteList(IPv6ServiceContentsRef * test, Direction direction,
LogRoute log_it)
{
IPv6RouteListRef ret = NULL;
IPv6ServiceContentsRef * scan;
switch (direction) {
case kDirectionBackwards:
for (scan = test; *scan != NULL; scan++) {
}
for (scan--; scan >= test; scan--) {
ret = make_IPv6RouteList_for_test(ret, *scan, log_it);
}
break;
default:
case kDirectionForwards:
for (scan = test; *scan != NULL; scan++) {
ret = make_IPv6RouteList_for_test(ret, *scan, log_it);
}
break;
}
IPv6RouteListFinalize(ret);
return (ret);
}
#define EMPHASIS_CHARS "================="
static boolean_t
routelist_build_test(IPv6RouteTestRef test)
{
CFStringRef descr;
boolean_t ret = FALSE;
IPv6RouteListRef routes1;
IPv6RouteListRef routes2;
printf("\n" EMPHASIS_CHARS "> RouteList Build '%s' <"
EMPHASIS_CHARS "\n",
test->name);
routes1 = make_IPv6RouteList(test->test, kDirectionForwards,
kLogRouteEnabled);
if ((S_IPMonitor_debug & kDebugFlag4) != 0) {
if (routes1 != NULL) {
descr = IPv6RouteListCopyDescription(routes1);
SCPrint(TRUE, stdout, CFSTR("Routes are %@\n"), descr);
CFRelease(descr);
}
}
routes2 = make_IPv6RouteList(test->test, kDirectionBackwards,
kLogRouteEnabled);
if ((S_IPMonitor_debug & kDebugFlag4) != 0) {
if (routes2 != NULL) {
descr = IPv6RouteListCopyDescription(routes2);
SCPrint(TRUE, stdout, CFSTR("Routes are %@\n"), descr);
CFRelease(descr);
}
}
if ((routes1 != NULL && routes2 == NULL)
|| (routes1 == NULL && routes2 != NULL)) {
fprintf(stderr, "routes1 is %sNULL but routes2 is %sNULL\n",
(routes1 != NULL) ? "not " : "",
(routes2 != NULL) ? "not " : "");
}
else if (routes1 != NULL && routes2 != NULL) {
if (routes1->count != routes2->count) {
fprintf(stderr, "routes1 count %d != routes 2 count %d\n",
routes1->count, routes2->count);
}
else if (bcmp(routes1, routes2,
IPv6RouteListComputeSize(routes1->count)) != 0) {
fprintf(stderr, "routes1 and routes2 are different\n");
}
else {
printf("routes1 and routes2 are the same\n");
ret = TRUE;
}
}
if (routes1 != NULL) {
free(routes1);
}
if (routes2 != NULL) {
free(routes2);
}
printf(EMPHASIS_CHARS "> RouteList Build '%s': %s <"
EMPHASIS_CHARS "\n",
test->name, ret ? "PASSED" : "FAILED");
return (ret);
}
static void
apply_test(IPv6RouteTestRef old_test, IPv6RouteTestRef new_test)
{
IPv6RouteListRef new_routes;
IPv6RouteListRef old_routes;
printf("\n" EMPHASIS_CHARS "> Apply '%s', '%s' Begin <"
EMPHASIS_CHARS "\n",
old_test->name, new_test->name);
old_routes = make_IPv6RouteList(old_test->test, kDirectionForwards,
kLogRouteDisabled);
new_routes = make_IPv6RouteList(new_test->test, kDirectionForwards,
kLogRouteDisabled);
if (old_routes == NULL) {
printf("No Old Routes\n");
}
else {
printf("Old routes ('%s') = ", old_test->name);
IPv6RouteListPrint(old_routes);
}
IPv6RouteListApply(NULL, old_routes, -1);
if (new_routes == NULL) {
printf("No New Routes\n");
}
else {
printf("New Routes ('%s') = ", new_test->name);
IPv6RouteListPrint(new_routes);
}
IPv6RouteListApply(old_routes, new_routes, -1);
if (old_routes != NULL) {
free(old_routes);
}
if (new_routes != NULL) {
free(new_routes);
}
printf(EMPHASIS_CHARS "> Apply '%s', '%s' End <"
EMPHASIS_CHARS "\n",
old_test->name, new_test->name);
return;
}
int
main(int argc, char **argv)
{
IPv6RouteTestRef * test;
_sc_log = FALSE;
_sc_verbose = (argc > 1) ? TRUE : FALSE;
S_IPMonitor_debug = kDebugFlag1 | kDebugFlag2 | kDebugFlag4;
if (argc > 1) {
S_IPMonitor_debug = strtoul(argv[1], NULL, 0);
}
for (test = ipv6_tests; *test != NULL; test++) {
if (!routelist_build_test(*test)) {
fprintf(stderr, "%s failed\n", (*test)->name);
exit(1);
}
}
for (test = ipv6_tests; *test != NULL; test++) {
IPv6RouteTestRef * test2;
for (test2 = test + 1; *test2 != NULL; test2++) {
apply_test(*test, *test2);
apply_test(*test2, *test);
}
}
{
char cmd[128];
printf("\nChecking for leaks\n");
sprintf(cmd, "leaks %d 2>&1", getpid());
fflush(stdout);
(void)system(cmd);
}
exit(0);
return (0);
}
#endif
#ifdef TEST_DNS_ORDER
#define kProtocolFlagsIPv4v6 (kProtocolFlagsIPv4 | kProtocolFlagsIPv6)
#define V4_ADDR_LOOP CFSTR("127.0.0.1")
#define V4_ADDR_1 CFSTR("192.168.1.1")
#define V4_ADDR_2 CFSTR("192.168.1.2")
#define V4_ADDR_3 CFSTR("8.8.8.8")
#define V4_ADDR_4 CFSTR("8.8.4.4")
#define V6_ADDR_LOOP CFSTR("::1")
#define V6_ADDR_1 CFSTR("fe80::0123:4567:89ab:cdef%en0")
#define V6_ADDR_2 CFSTR("fd00::2acf:e9ff:fe14:8c59")
#define V6_ADDR_3 CFSTR("2001:4860:4860::8888")
typedef struct {
const char * name;
const ProtocolFlags flags;
const CFStringRef server_addrs[];
} DNSOrderTest, * DNSOrderTestRef;
static DNSOrderTest test0a = {
"test0a",
kProtocolFlagsIPv4,
{
V4_ADDR_1, V4_ADDR_2, V4_ADDR_3, V4_ADDR_4, NULL
}
};
static DNSOrderTest test0b = {
"test0b",
kProtocolFlagsIPv6,
{
V6_ADDR_1, V6_ADDR_2, V6_ADDR_3, NULL
}
};
static DNSOrderTest test1a = {
"test1a",
kProtocolFlagsIPv4v6,
{
V4_ADDR_1, V6_ADDR_1, NULL
}
};
static DNSOrderTest test2a = {
"test2a",
kProtocolFlagsIPv4v6,
{
V4_ADDR_1, V6_ADDR_2, NULL
}
};
static DNSOrderTest test3a = {
"test3a",
kProtocolFlagsIPv4v6,
{
V4_ADDR_1, V6_ADDR_3, NULL
}
};
static DNSOrderTest test1b = {
"test1b",
kProtocolFlagsIPv4v6,
{
V4_ADDR_3, V6_ADDR_1, NULL
}
};
static DNSOrderTest test2b = {
"test2b",
kProtocolFlagsIPv4v6,
{
V4_ADDR_3, V6_ADDR_2, NULL
}
};
static DNSOrderTest test3b = {
"test3b",
kProtocolFlagsIPv4v6,
{
V4_ADDR_3, V6_ADDR_3, NULL
}
};
static DNSOrderTest test1c = {
"test1c",
kProtocolFlagsIPv4v6,
{
V6_ADDR_1, V4_ADDR_1, NULL
}
};
static DNSOrderTest test2c = {
"test2c",
kProtocolFlagsIPv4v6,
{
V6_ADDR_2, V4_ADDR_1, NULL
}
};
static DNSOrderTest test3c = {
"test3c",
kProtocolFlagsIPv4v6,
{
V6_ADDR_3, V4_ADDR_1, NULL
}
};
static DNSOrderTest test1d = {
"test1d",
kProtocolFlagsIPv4v6,
{
V6_ADDR_1, V4_ADDR_3, NULL
}
};
static DNSOrderTest test2d = {
"test2d",
kProtocolFlagsIPv4v6,
{
V6_ADDR_2, V4_ADDR_3, NULL
}
};
static DNSOrderTest test3d = {
"test3d",
kProtocolFlagsIPv4v6,
{
V6_ADDR_3, V4_ADDR_3, NULL
}
};
static DNSOrderTest test4 = {
"test4",
kProtocolFlagsIPv4v6,
{
V4_ADDR_LOOP, V4_ADDR_3, V6_ADDR_2, NULL
}
};
static DNSOrderTest test5 = {
"test5",
kProtocolFlagsIPv4v6,
{
V4_ADDR_3, V6_ADDR_LOOP, V6_ADDR_2, NULL
}
};
static DNSOrderTest test6 = {
"test6",
kProtocolFlagsIPv4v6,
{
V4_ADDR_1, V4_ADDR_2, V4_ADDR_3, V4_ADDR_4, V6_ADDR_1, V6_ADDR_2, V6_ADDR_3, NULL
}
};
static DNSOrderTest test7 = {
"test7",
kProtocolFlagsIPv4v6,
{
V4_ADDR_1, V6_ADDR_1, V4_ADDR_3, V6_ADDR_2, NULL
}
};
static DNSOrderTestRef dns_order_tests[] = {
&test0a, &test0b,
&test1a, &test2a, &test3a,
&test1b, &test2b, &test3b,
&test1c, &test2c, &test3c,
&test1d, &test2d, &test3d,
&test4,
&test5,
&test6,
&test7,
NULL
};
#define EMPHASIS_CHARS "================="
static void
apply_order(CFArrayRef servers, ProtocolFlags flags)
{
CFArrayRef ordered_servers;
ordered_servers = order_dns_servers(servers, flags);
if (ordered_servers != NULL) {
SCPrint(TRUE, stdout, CFSTR("After :\n%@\n"), ordered_servers);
CFRelease(ordered_servers);
} else {
printf("FAIL: No ordered servers\n");
}
return;
}
static void
apply_test(DNSOrderTestRef test)
{
CFMutableArrayRef servers;
printf("\n" EMPHASIS_CHARS "> '%s' Begin <" EMPHASIS_CHARS "\n\n", test->name);
servers = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
for (int i = 0; test->server_addrs[i] != NULL; i++) {
CFStringRef server_addr = test->server_addrs[i];
CFArrayAppendValue(servers, server_addr);
}
SCPrint(TRUE, stdout, CFSTR("Before :\n%@\n"), servers);
apply_order(servers, test->flags);
CFRelease(servers);
return;
}
int
main(int argc, char **argv)
{
_sc_log = FALSE;
_sc_verbose = (argc > 1) ? TRUE : FALSE;
S_IPMonitor_debug = kDebugFlag1 | kDebugFlag2 | kDebugFlag4;
if (argc > 1) {
S_IPMonitor_debug = (uint32)strtoul(argv[1], NULL, 0);
}
for (DNSOrderTestRef * test = dns_order_tests; *test != NULL; test++) {
apply_test(*test);
}
{
char cmd[128];
printf("\nChecking for leaks\n");
sprintf(cmd, "leaks %d 2>&1", getpid());
fflush(stdout);
(void)system(cmd);
}
exit(0);
return (0);
}
#endif