SCNetworkReachability.c [plain text]
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCValidation.h>
#include <SystemConfiguration/SCPrivate.h>
#include <CoreFoundation/CFRuntime.h>
#include <pthread.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <netdb.h>
#include <netdb_async.h>
#include <resolv.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/if_dl.h>
#define KERNEL_PRIVATE
#include <net/route.h>
#undef KERNEL_PRIVATE
#ifndef s6_addr16
#define s6_addr16 __u6_addr.__u6_addr16
#endif
#include "ppp.h"
#define kSCNetworkFlagsFirstResolvePending (1<<31)
#define N_QUICK 32
typedef enum {
reachabilityTypeAddress,
reachabilityTypeAddressPair,
reachabilityTypeName
} addressType;
static CFStringRef __SCNetworkReachabilityCopyDescription (CFTypeRef cf);
static void __SCNetworkReachabilityDeallocate (CFTypeRef cf);
typedef struct {
CFRuntimeBase cfBase;
pthread_mutex_t lock;
addressType type;
const char *name;
CFArrayRef resolvedAddress;
int resolvedAddressError;
struct sockaddr *localAddress;
struct sockaddr *remoteAddress;
SCNetworkConnectionFlags flags;
uint16_t if_index;
CFRunLoopSourceRef rls;
SCNetworkReachabilityCallBack rlsFunction;
SCNetworkReachabilityContext rlsContext;
CFMutableArrayRef rlList;
CFMachPortRef dnsPort;
CFRunLoopSourceRef dnsRLS;
} SCNetworkReachabilityPrivate, *SCNetworkReachabilityPrivateRef;
static CFTypeID __kSCNetworkReachabilityTypeID = _kCFRuntimeNotATypeID;
static const CFRuntimeClass __SCNetworkReachabilityClass = {
0, "SCNetworkReachability", NULL, NULL, __SCNetworkReachabilityDeallocate, NULL, NULL, NULL, __SCNetworkReachabilityCopyDescription };
static pthread_once_t initialized = PTHREAD_ONCE_INIT;
static Boolean needDNS = TRUE;
static pthread_mutex_t hn_lock = PTHREAD_MUTEX_INITIALIZER;
static SCDynamicStoreRef hn_store = NULL;
static CFRunLoopSourceRef hn_storeRLS = NULL;
static CFMutableArrayRef hn_rlList = NULL;
static CFMutableSetRef hn_targets = NULL;
static __inline__ CFTypeRef
isA_SCNetworkReachability(CFTypeRef obj)
{
return (isA_CFType(obj, SCNetworkReachabilityGetTypeID()));
}
static void
sockaddr_to_string(const struct sockaddr *address, char *buf, size_t bufLen)
{
bzero(buf, bufLen);
switch (address->sa_family) {
case AF_INET :
(void)inet_ntop(((struct sockaddr_in *)address)->sin_family,
&((struct sockaddr_in *)address)->sin_addr,
buf,
bufLen);
break;
case AF_INET6 : {
(void)inet_ntop(((struct sockaddr_in6 *)address)->sin6_family,
&((struct sockaddr_in6 *)address)->sin6_addr,
buf,
bufLen);
if (((struct sockaddr_in6 *)address)->sin6_scope_id != 0) {
int n;
n = strlen(buf);
if ((n+IF_NAMESIZE+1) <= (int)bufLen) {
buf[n++] = '%';
if_indextoname(((struct sockaddr_in6 *)address)->sin6_scope_id, &buf[n]);
}
}
break;
}
case AF_LINK :
if (((struct sockaddr_dl *)address)->sdl_len < bufLen) {
bufLen = ((struct sockaddr_dl *)address)->sdl_len;
} else {
bufLen = bufLen - 1;
}
bcopy(((struct sockaddr_dl *)address)->sdl_data, buf, bufLen);
break;
default :
snprintf(buf, bufLen, "unexpected address family %d", address->sa_family);
break;
}
return;
}
#ifndef CHECK_IPV6_REACHABILITY
static char *
__netdb_error(int error)
{
char *msg;
switch(error) {
case NETDB_INTERNAL :
msg = strerror(errno);
break;
case HOST_NOT_FOUND :
msg = "Host not found.";
break;
case TRY_AGAIN :
msg = "Try again.";
break;
case NO_RECOVERY :
msg = "No recovery.";
break;
case NO_DATA :
msg = "No data available.";
break;
default :
msg = "Unknown";
break;
}
return msg;
}
#endif
static void
__signalRunLoop(CFTypeRef obj, CFRunLoopSourceRef rls, CFArrayRef rlList)
{
CFRunLoopRef rl = NULL;
CFRunLoopRef rl1 = NULL;
CFIndex i;
CFIndex n = CFArrayGetCount(rlList);
if (n == 0) {
return;
}
for (i = 0; i < n; i += 3) {
if (!CFEqual(obj, CFArrayGetValueAtIndex(rlList, i))) {
continue;
}
rl1 = (CFRunLoopRef)CFArrayGetValueAtIndex(rlList, i+1);
break;
}
if (!rl1) {
return;
}
rl = rl1;
for (i = i+3; i < n; i += 3) {
CFRunLoopRef rl2;
if (!CFEqual(obj, CFArrayGetValueAtIndex(rlList, i))) {
continue;
}
rl2 = (CFRunLoopRef)CFArrayGetValueAtIndex(rlList, i+1);
if (!CFEqual(rl1, rl2)) {
rl = NULL;
break;
}
}
if (rl) {
CFRunLoopWakeUp(rl);
return;
}
for (i = 0; i < n; i+=3) {
CFStringRef rlMode;
if (!CFEqual(obj, CFArrayGetValueAtIndex(rlList, i))) {
continue;
}
rl = (CFRunLoopRef)CFArrayGetValueAtIndex(rlList, i+1);
rlMode = CFRunLoopCopyCurrentMode(rl);
if (rlMode && CFRunLoopIsWaiting(rl) && CFRunLoopContainsSource(rl, rls, rlMode)) {
CFRelease(rlMode);
CFRunLoopWakeUp(rl);
return;
}
if (rlMode) CFRelease(rlMode);
}
CFRunLoopWakeUp(rl1);
return;
}
static int
updatePPPStatus(SCDynamicStoreRef *storeP,
const struct sockaddr *sa,
const char *if_name,
SCNetworkConnectionFlags *flags)
{
CFDictionaryRef dict = NULL;
CFStringRef entity;
CFIndex i;
const void * keys_q[N_QUICK];
const void ** keys = keys_q;
CFIndex n;
CFStringRef ppp_if;
int sc_status = kSCStatusReachabilityUnknown;
SCDynamicStoreRef store = (storeP) ? *storeP : NULL;
const void * values_q[N_QUICK];
const void ** values = values_q;
switch (sa->sa_family) {
case AF_INET :
entity = kSCEntNetIPv4;
break;
case AF_INET6 :
entity = kSCEntNetIPv6;
break;
default :
goto done;
}
if (!store) {
store = SCDynamicStoreCreate(NULL, CFSTR("SCNetworkReachability"), NULL, NULL);
if (!store) {
SCLog(_sc_verbose, LOG_INFO, CFSTR("SCDynamicStoreCreate() failed"));
goto done;
}
}
{
CFStringRef pattern;
CFMutableArrayRef patterns;
patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainState,
kSCCompAnyRegex,
entity);
CFArrayAppendValue(patterns, pattern);
CFRelease(pattern);
pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCCompAnyRegex,
kSCEntNetPPP);
CFArrayAppendValue(patterns, pattern);
CFRelease(pattern);
pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainState,
kSCCompAnyRegex,
kSCEntNetPPP);
CFArrayAppendValue(patterns, pattern);
CFRelease(pattern);
dict = SCDynamicStoreCopyMultiple(store, NULL, patterns);
CFRelease(patterns);
}
if (!dict) {
goto done;
}
sc_status = kSCStatusOK;
n = CFDictionaryGetCount(dict);
if (n <= 0) {
goto done;
}
ppp_if = CFStringCreateWithCStringNoCopy(NULL,
if_name,
kCFStringEncodingASCII,
kCFAllocatorNull);
if (n > (CFIndex)(sizeof(keys_q) / sizeof(CFTypeRef))) {
keys = CFAllocatorAllocate(NULL, n * sizeof(CFTypeRef), 0);
values = CFAllocatorAllocate(NULL, n * sizeof(CFTypeRef), 0);
}
CFDictionaryGetKeysAndValues(dict, keys, values);
for (i=0; i < n; i++) {
CFArrayRef components;
CFStringRef key;
CFNumberRef num;
CFDictionaryRef p_setup;
CFDictionaryRef p_state;
int32_t ppp_status;
CFStringRef service = NULL;
CFStringRef s_key = (CFStringRef) keys[i];
CFDictionaryRef s_dict = (CFDictionaryRef)values[i];
CFStringRef s_if;
if (!isA_CFString(s_key) || !isA_CFDictionary(s_dict)) {
continue;
}
if (!CFStringHasSuffix(s_key, entity)) {
continue; }
s_if = CFDictionaryGetValue(s_dict, kSCPropInterfaceName);
if (!isA_CFString(s_if)) {
continue; }
if (!CFEqual(ppp_if, s_if)) {
continue; }
components = CFStringCreateArrayBySeparatingStrings(NULL, s_key, CFSTR("/"));
if (CFArrayGetCount(components) != 5) {
continue;
}
service = CFArrayGetValueAtIndex(components, 3);
key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainState,
service,
kSCEntNetPPP);
p_state = CFDictionaryGetValue(dict, key);
CFRelease(key);
key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
service,
kSCEntNetPPP);
p_setup = CFDictionaryGetValue(dict, key);
CFRelease(key);
CFRelease(components);
if (!isA_CFDictionary(p_state)) {
continue;
}
num = CFDictionaryGetValue(p_state, kSCPropNetPPPStatus);
if (!isA_CFNumber(num)) {
continue;
}
if (!CFNumberGetValue(num, kCFNumberSInt32Type, &ppp_status)) {
continue;
}
switch (ppp_status) {
case PPP_RUNNING :
break;
case PPP_ONHOLD :
break;
case PPP_IDLE :
case PPP_STATERESERVED :
SCLog(_sc_debug, LOG_INFO, CFSTR(" PPP link idle, dial-on-traffic to connect"));
*flags |= kSCNetworkFlagsConnectionRequired;
break;
default :
SCLog(_sc_debug, LOG_INFO, CFSTR(" PPP link, connection in progress"));
*flags |= kSCNetworkFlagsConnectionRequired;
break;
}
if (isA_CFDictionary(p_setup)) {
num = CFDictionaryGetValue(p_setup, kSCPropNetPPPDialOnDemand);
if (isA_CFNumber(num)) {
int32_t ppp_demand;
if (CFNumberGetValue(num, kCFNumberSInt32Type, &ppp_demand)) {
if (ppp_demand) {
*flags |= kSCNetworkFlagsConnectionAutomatic;
if (ppp_status == PPP_IDLE) {
*flags |= kSCNetworkFlagsInterventionRequired;
}
}
}
}
}
break;
}
CFRelease(ppp_if);
if (keys != keys_q) {
CFAllocatorDeallocate(NULL, keys);
CFAllocatorDeallocate(NULL, values);
}
done :
if (dict) CFRelease(dict);
if (storeP) *storeP = store;
return sc_status;
}
static int
updatePPPAvailable(SCDynamicStoreRef *storeP,
const struct sockaddr *sa,
SCNetworkConnectionFlags *flags)
{
CFDictionaryRef dict = NULL;
CFStringRef entity;
CFIndex i;
const void * keys_q[N_QUICK];
const void ** keys = keys_q;
CFIndex n;
int sc_status = kSCStatusReachabilityUnknown;
SCDynamicStoreRef store = (storeP) ? *storeP : NULL;
const void * values_q[N_QUICK];
const void ** values = values_q;
switch (sa->sa_family) {
case AF_INET :
entity = kSCEntNetIPv4;
break;
case AF_INET6 :
entity = kSCEntNetIPv6;
break;
default :
goto done;
}
if (!store) {
store = SCDynamicStoreCreate(NULL, CFSTR("SCNetworkReachability"), NULL, NULL);
if (!store) {
SCLog(_sc_verbose, LOG_INFO, CFSTR("SCDynamicStoreCreate() failed"));
goto done;
}
}
{
CFStringRef pattern;
CFMutableArrayRef patterns;
patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCCompAnyRegex,
entity);
CFArrayAppendValue(patterns, pattern);
CFRelease(pattern);
pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCCompAnyRegex,
kSCEntNetPPP);
CFArrayAppendValue(patterns, pattern);
CFRelease(pattern);
dict = SCDynamicStoreCopyMultiple(store, NULL, patterns);
CFRelease(patterns);
}
if (!dict) {
goto done;
}
sc_status = kSCStatusOK;
n = CFDictionaryGetCount(dict);
if (n <= 0) {
goto done;
}
if (n > (CFIndex)(sizeof(keys_q) / sizeof(CFTypeRef))) {
keys = CFAllocatorAllocate(NULL, n * sizeof(CFTypeRef), 0);
values = CFAllocatorAllocate(NULL, n * sizeof(CFTypeRef), 0);
}
CFDictionaryGetKeysAndValues(dict, keys, values);
for (i = 0; i < n; i++) {
CFArrayRef components;
Boolean found = FALSE;
CFStringRef p_key;
CFDictionaryRef p_dict;
CFStringRef service;
CFStringRef s_key = (CFStringRef) keys[i];
CFDictionaryRef s_dict = (CFDictionaryRef)values[i];
if (!isA_CFString(s_key) || !isA_CFDictionary(s_dict)) {
continue;
}
if (!CFStringHasSuffix(s_key, entity)) {
continue; }
components = CFStringCreateArrayBySeparatingStrings(NULL, s_key, CFSTR("/"));
if (CFArrayGetCount(components) != 5) {
continue;
}
service = CFArrayGetValueAtIndex(components, 3);
p_key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
service,
kSCEntNetPPP);
p_dict = CFDictionaryGetValue(dict, p_key);
CFRelease(p_key);
if (isA_CFDictionary(p_dict)) {
CFNumberRef num;
found = TRUE;
*flags |= kSCNetworkFlagsReachable;
*flags |= kSCNetworkFlagsTransientConnection;
*flags |= kSCNetworkFlagsConnectionRequired;
num = CFDictionaryGetValue(p_dict, kSCPropNetPPPDialOnDemand);
if (isA_CFNumber(num)) {
int32_t ppp_demand;
if (CFNumberGetValue(num, kCFNumberSInt32Type, &ppp_demand)) {
if (ppp_demand) {
*flags |= kSCNetworkFlagsConnectionAutomatic;
}
}
}
if (_sc_debug) {
SCLog(TRUE, LOG_INFO, CFSTR(" status = isReachable (after connect)"));
SCLog(TRUE, LOG_INFO, CFSTR(" service = %@"), service);
}
}
CFRelease(components);
if (found) {
break;
}
}
if (keys != keys_q) {
CFAllocatorDeallocate(NULL, keys);
CFAllocatorDeallocate(NULL, values);
}
done :
if (dict) CFRelease(dict);
if (storeP) *storeP = store;
return sc_status;
}
#define ROUNDUP(a, size) \
(((a) & ((size)-1)) ? (1 + ((a) | ((size)-1))) : (a))
#define NEXT_SA(ap) (ap) = (struct sockaddr *) \
((caddr_t)(ap) + ((ap)->sa_len ? ROUNDUP((ap)->sa_len,\
sizeof(u_long)) :\
sizeof(u_long)))
static void
get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info)
{
int i;
for (i = 0; i < RTAX_MAX; i++) {
if (addrs & (1 << i)) {
rti_info[i] = sa;
NEXT_SA(sa);
} else
rti_info[i] = NULL;
}
}
#define BUFLEN (sizeof(struct rt_msghdr) + 512)
static Boolean
checkAddress(SCDynamicStoreRef *storeP,
const struct sockaddr *address,
SCNetworkConnectionFlags *flags,
uint16_t *if_index)
{
char buf[BUFLEN];
struct ifreq ifr;
char if_name[IFNAMSIZ+1];
int isock;
int n;
pid_t pid = getpid();
int rsock;
struct sockaddr *rti_info[RTAX_MAX];
struct rt_msghdr *rtm;
struct sockaddr *sa;
int sc_status = kSCStatusReachabilityUnknown;
struct sockaddr_dl *sdl;
int seq = (int)pthread_self();
SCDynamicStoreRef store = (storeP) ? *storeP : NULL;
char *statusMessage = NULL;
#ifndef RTM_GET_SILENT
#warning Note: Using RTM_GET (and not RTM_GET_SILENT)
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int sosize = 48 * 1024;
#endif
if (!address || !flags) {
sc_status = kSCStatusInvalidArgument;
goto done;
}
switch (address->sa_family) {
case AF_INET :
case AF_INET6 :
if (_sc_debug) {
sockaddr_to_string(address, buf, sizeof(buf));
SCLog(TRUE, LOG_INFO, CFSTR("checkAddress(%s)"), buf);
}
break;
default :
SCLog(_sc_verbose, LOG_ERR,
CFSTR("checkAddress(): unexpected address family %d"),
address->sa_family);
sc_status = kSCStatusInvalidArgument;
goto done;
}
*flags = 0;
if (if_index) {
*if_index = 0;
}
if ((address->sa_family == AF_INET) && (((struct sockaddr_in *)address)->sin_addr.s_addr == 0)) {
goto checkAvailable;
}
bzero(&buf, sizeof(buf));
rtm = (struct rt_msghdr *)&buf;
rtm->rtm_msglen = sizeof(struct rt_msghdr);
rtm->rtm_version = RTM_VERSION;
#ifdef RTM_GET_SILENT
rtm->rtm_type = RTM_GET_SILENT;
#else
rtm->rtm_type = RTM_GET;
#endif
rtm->rtm_flags = RTF_STATIC|RTF_UP|RTF_HOST|RTF_GATEWAY;
rtm->rtm_addrs = RTA_DST|RTA_IFP;
rtm->rtm_pid = pid;
rtm->rtm_seq = seq;
switch (address->sa_family) {
case AF_INET6: {
struct sockaddr_in6 *sin6;
sin6 = (struct sockaddr_in6 *)address;
if ((IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) ||
IN6_IS_ADDR_MC_LINKLOCAL(&sin6->sin6_addr)) &&
(sin6->sin6_scope_id != 0)) {
sin6->sin6_addr.s6_addr16[1] = htons(sin6->sin6_scope_id);
sin6->sin6_scope_id = 0;
}
break;
}
}
sa = (struct sockaddr *) (rtm + 1);
bcopy(address, sa, address->sa_len);
n = ROUNDUP(sa->sa_len, sizeof(u_long));
rtm->rtm_msglen += n;
sdl = (struct sockaddr_dl *) ((void *)sa + n);
sdl->sdl_family = AF_LINK;
sdl->sdl_len = sizeof (struct sockaddr_dl);
n = ROUNDUP(sdl->sdl_len, sizeof(u_long));
rtm->rtm_msglen += n;
#ifndef RTM_GET_SILENT
pthread_mutex_lock(&lock);
#endif
rsock = socket(PF_ROUTE, SOCK_RAW, 0);
if (rsock == -1) {
#ifndef RTM_GET_SILENT
pthread_mutex_unlock(&lock);
#endif
SCLog(TRUE, LOG_ERR, CFSTR("socket(PF_ROUTE) failed: %s"), strerror(errno));
sc_status = kSCStatusFailed;
goto done;
}
#ifndef RTM_GET_SILENT
if (setsockopt(rsock, SOL_SOCKET, SO_RCVBUF, &sosize, sizeof(sosize)) == -1) {
(void)close(rsock);
pthread_mutex_unlock(&lock);
SCLog(TRUE, LOG_ERR, CFSTR("setsockopt(SO_RCVBUF) failed: %s"), strerror(errno));
sc_status = kSCStatusFailed;
goto done;
}
#endif
if (write(rsock, &buf, rtm->rtm_msglen) == -1) {
int err = errno;
(void)close(rsock);
#ifndef RTM_GET_SILENT
pthread_mutex_unlock(&lock);
#endif
if (err != ESRCH) {
SCLog(TRUE, LOG_ERR, CFSTR("write() failed: %s"), strerror(err));
goto done;
}
goto checkAvailable;
}
do {
int n;
n = read(rsock, (void *)&buf, sizeof(buf));
if (n == -1) {
int err = errno;
if (err != EINTR) {
(void)close(rsock);
SCLog(TRUE, LOG_ERR, CFSTR("read() failed: %s"), strerror(err));
#ifndef RTM_GET_SILENT
pthread_mutex_unlock(&lock);
#endif
goto done;
}
}
} while (rtm->rtm_type != RTM_GET ||
rtm->rtm_seq != seq ||
rtm->rtm_pid != pid);
(void)close(rsock);
#ifndef RTM_GET_SILENT
pthread_mutex_unlock(&lock);
#endif
get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
#ifdef DEBUG
{
int i;
char buf[200];
SCLog(_sc_debug, LOG_DEBUG, CFSTR("rtm_flags = 0x%8.8x"), rtm->rtm_flags);
for (i = 0; i < RTAX_MAX; i++) {
if (rti_info[i] != NULL) {
sockaddr_to_string(rti_info[i], buf, sizeof(buf));
SCLog(_sc_debug, LOG_DEBUG, CFSTR("%d: %s"), i, buf);
}
}
}
#endif
if ((rti_info[RTAX_IFP] == NULL) ||
(rti_info[RTAX_IFP]->sa_family != AF_LINK)) {
goto done; }
sdl = (struct sockaddr_dl *) rti_info[RTAX_IFP];
if ((sdl->sdl_nlen == 0) || (sdl->sdl_nlen > IFNAMSIZ)) {
goto done; }
bzero(&ifr, sizeof(ifr));
bcopy(sdl->sdl_data, ifr.ifr_name, sdl->sdl_nlen);
isock = socket(AF_INET, SOCK_DGRAM, 0);
if (isock < 0) {
SCLog(TRUE, LOG_NOTICE, CFSTR("socket() failed: %s"), strerror(errno));
goto done;
}
if (ioctl(isock, SIOCGIFFLAGS, (char *)&ifr) < 0) {
SCLog(TRUE, LOG_NOTICE, CFSTR("ioctl() failed: %s"), strerror(errno));
(void)close(isock);
goto done;
}
(void)close(isock);
if (!(ifr.ifr_flags & IFF_UP)) {
goto checkAvailable;
}
statusMessage = "isReachable";
*flags |= kSCNetworkFlagsReachable;
if (rtm->rtm_flags & RTF_LOCAL) {
statusMessage = "isReachable (is a local address)";
*flags |= kSCNetworkFlagsIsLocalAddress;
} else if (ifr.ifr_flags & IFF_LOOPBACK) {
statusMessage = "isReachable (is loopback network)";
*flags |= kSCNetworkFlagsIsLocalAddress;
} else if (rti_info[RTAX_IFA]) {
void *addr1 = (void *)address;
void *addr2 = (void *)rti_info[RTAX_IFA];
size_t len = address->sa_len;
if ((address->sa_family != rti_info[RTAX_IFA]->sa_family) &&
(address->sa_len != rti_info[RTAX_IFA]->sa_len)) {
SCLog(TRUE, LOG_NOTICE,
CFSTR("address family/length mismatch: %d/%d != %d/%d"),
address->sa_family,
address->sa_len,
rti_info[RTAX_IFA]->sa_family,
rti_info[RTAX_IFA]->sa_len);
goto done;
}
switch (address->sa_family) {
case AF_INET :
addr1 = &((struct sockaddr_in *)address)->sin_addr;
addr2 = &((struct sockaddr_in *)rti_info[RTAX_IFA])->sin_addr;
len = sizeof(struct in_addr);
break;
case AF_INET6 :
addr1 = &((struct sockaddr_in6 *)address)->sin6_addr;
addr2 = &((struct sockaddr_in6 *)rti_info[RTAX_IFA])->sin6_addr;
len = sizeof(struct in6_addr);
break;
default :
break;
}
if (memcmp(addr1, addr2, len) == 0) {
statusMessage = "isReachable (is interface address)";
*flags |= kSCNetworkFlagsIsLocalAddress;
}
}
if (rti_info[RTAX_GATEWAY] && (rti_info[RTAX_GATEWAY]->sa_family == AF_LINK)) {
*flags |= kSCNetworkFlagsIsDirect;
}
bzero(&if_name, sizeof(if_name));
bcopy(sdl->sdl_data,
if_name,
(sdl->sdl_nlen <= IFNAMSIZ) ? sdl->sdl_nlen : IFNAMSIZ);
if (if_index) {
*if_index = sdl->sdl_index;
}
if (_sc_debug) {
SCLog(TRUE, LOG_INFO, CFSTR(" status = %s"), statusMessage);
SCLog(TRUE, LOG_INFO, CFSTR(" device = %s (%hu)"), if_name, sdl->sdl_index);
SCLog(TRUE, LOG_INFO, CFSTR(" ifr_flags = 0x%04hx"), ifr.ifr_flags);
SCLog(TRUE, LOG_INFO, CFSTR(" rtm_flags = 0x%08x"), rtm->rtm_flags);
}
sc_status = kSCStatusOK;
if (ifr.ifr_flags & IFF_POINTOPOINT) {
*flags |= kSCNetworkFlagsTransientConnection;
sc_status = updatePPPStatus(&store, address, if_name, flags);
}
goto done;
checkAvailable :
sc_status = updatePPPAvailable(&store, address, flags);
done :
if (*flags == 0) {
SCLog(_sc_debug, LOG_INFO, CFSTR(" cannot be reached"));
}
if (storeP) *storeP = store;
if (sc_status != kSCStatusOK) {
_SCErrorSet(sc_status);
return FALSE;
}
return TRUE;
}
static Boolean
checkAddressZero(SCDynamicStoreRef *storeP,
SCNetworkConnectionFlags *flags,
uint16_t *if_index)
{
Boolean ok;
struct sockaddr_in sin;
bzero(&sin, sizeof(sin));
sin.sin_len = sizeof(sin);
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
ok = checkAddress(storeP, (struct sockaddr *)&sin, flags, if_index);
return ok;
}
static Boolean
isAddressZero(struct sockaddr *sa, SCNetworkConnectionFlags *flags)
{
if (sa->sa_family == AF_INET) {
struct sockaddr_in *sin = (struct sockaddr_in *)sa;
if (sin->sin_addr.s_addr == 0) {
SCLog(_sc_debug, LOG_INFO, CFSTR("isAddressZero(0.0.0.0)"));
SCLog(_sc_debug, LOG_INFO, CFSTR(" status = isReachable (this host)"));
*flags |= kSCNetworkFlagsReachable;
*flags |= kSCNetworkFlagsIsLocalAddress;
return TRUE;
}
}
return FALSE;
}
static CFStringRef
__SCNetworkReachabilityCopyDescription(CFTypeRef cf)
{
CFAllocatorRef allocator = CFGetAllocator(cf);
CFMutableStringRef result;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)cf;
result = CFStringCreateMutable(allocator, 0);
CFStringAppendFormat(result, NULL, CFSTR("<SCNetworkReachability %p [%p]> { "), cf, allocator);
switch (targetPrivate->type) {
case reachabilityTypeAddress :
case reachabilityTypeAddressPair : {
char buf[64];
if (targetPrivate->localAddress) {
sockaddr_to_string(targetPrivate->localAddress, buf, sizeof(buf));
CFStringAppendFormat(result, NULL, CFSTR("local address=%s"),
buf);
}
if (targetPrivate->remoteAddress) {
sockaddr_to_string(targetPrivate->remoteAddress, buf, sizeof(buf));
CFStringAppendFormat(result, NULL, CFSTR("%s%saddress=%s"),
targetPrivate->localAddress ? ", " : "",
(targetPrivate->type == reachabilityTypeAddressPair) ? "remote " : "",
buf);
}
break;
}
case reachabilityTypeName : {
CFStringAppendFormat(result, NULL, CFSTR("name=%s"), targetPrivate->name);
if (targetPrivate->resolvedAddress || (targetPrivate->resolvedAddressError != NETDB_SUCCESS)) {
if (targetPrivate->resolvedAddress) {
if (isA_CFArray(targetPrivate->resolvedAddress)) {
CFIndex i;
CFIndex n = CFArrayGetCount(targetPrivate->resolvedAddress);
CFStringAppendFormat(result, NULL, CFSTR(" ("));
for (i = 0; i < n; i++) {
CFDataRef address;
char buf[64];
struct sockaddr *sa;
address = CFArrayGetValueAtIndex(targetPrivate->resolvedAddress, i);
sa = (struct sockaddr *)CFDataGetBytePtr(address);
sockaddr_to_string(sa, buf, sizeof(buf));
CFStringAppendFormat(result, NULL, CFSTR("%s%s"),
i > 0 ? ", " : "",
buf);
}
CFStringAppendFormat(result, NULL, CFSTR(")"));
} else {
CFStringAppendFormat(result, NULL, CFSTR(" (no addresses)"));
}
} else {
#ifdef CHECK_IPV6_REACHABILITY
CFStringAppendFormat(result, NULL, CFSTR(" (%s)"),
gai_strerror(targetPrivate->resolvedAddressError));
#else
CFStringAppendFormat(result, NULL, CFSTR(" (%s)"),
__netdb_error(targetPrivate->resolvedAddressError));
#endif
}
} else if (targetPrivate->dnsPort) {
CFStringAppendFormat(result, NULL, CFSTR(" (DNS query active)"));
}
break;
}
}
if (targetPrivate->rls) {
CFStringAppendFormat(result,
NULL,
CFSTR(", flags=%8.8x, if_index=%hu"),
targetPrivate->flags,
targetPrivate->if_index);
}
CFStringAppendFormat(result, NULL, CFSTR(" }"));
return result;
}
static void
__SCNetworkReachabilityDeallocate(CFTypeRef cf)
{
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)cf;
SCLog(_sc_verbose, LOG_DEBUG, CFSTR("__SCNetworkReachabilityDeallocate:"));
pthread_mutex_destroy(&targetPrivate->lock);
if (targetPrivate->name)
CFAllocatorDeallocate(NULL, (void *)targetPrivate->name);
if (targetPrivate->resolvedAddress)
CFRelease(targetPrivate->resolvedAddress);
if (targetPrivate->localAddress)
CFAllocatorDeallocate(NULL, (void *)targetPrivate->localAddress);
if (targetPrivate->remoteAddress)
CFAllocatorDeallocate(NULL, (void *)targetPrivate->remoteAddress);
if (targetPrivate->rlsContext.release) {
targetPrivate->rlsContext.release(targetPrivate->rlsContext.info);
}
return;
}
static void
__SCNetworkReachabilityInitialize(void)
{
__kSCNetworkReachabilityTypeID = _CFRuntimeRegisterClass(&__SCNetworkReachabilityClass);
return;
}
static SCNetworkReachabilityPrivateRef
__SCNetworkReachabilityCreatePrivate(CFAllocatorRef allocator)
{
SCNetworkReachabilityPrivateRef targetPrivate;
uint32_t size;
pthread_once(&initialized, __SCNetworkReachabilityInitialize);
SCLog(_sc_verbose, LOG_DEBUG, CFSTR("__SCNetworkReachabilityCreatePrivate:"));
size = sizeof(SCNetworkReachabilityPrivate) - sizeof(CFRuntimeBase);
targetPrivate = (SCNetworkReachabilityPrivateRef)_CFRuntimeCreateInstance(allocator,
__kSCNetworkReachabilityTypeID,
size,
NULL);
if (!targetPrivate) {
return NULL;
}
pthread_mutex_init(&targetPrivate->lock, NULL);
targetPrivate->name = NULL;
targetPrivate->resolvedAddress = NULL;
targetPrivate->resolvedAddressError = NETDB_SUCCESS;
targetPrivate->localAddress = NULL;
targetPrivate->remoteAddress = NULL;
targetPrivate->flags = 0;
targetPrivate->if_index = 0;
targetPrivate->rls = NULL;
targetPrivate->rlsFunction = NULL;
targetPrivate->rlsContext.info = NULL;
targetPrivate->rlsContext.retain = NULL;
targetPrivate->rlsContext.release = NULL;
targetPrivate->rlsContext.copyDescription = NULL;
targetPrivate->rlList = NULL;
targetPrivate->dnsPort = NULL;
targetPrivate->dnsRLS = NULL;
return targetPrivate;
}
SCNetworkReachabilityRef
SCNetworkReachabilityCreateWithAddress(CFAllocatorRef allocator,
const struct sockaddr *address)
{
SCNetworkReachabilityPrivateRef targetPrivate;
if (!address ||
(address->sa_len == 0) ||
(address->sa_len > sizeof(struct sockaddr_storage))) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
targetPrivate = __SCNetworkReachabilityCreatePrivate(allocator);
if (!targetPrivate) {
return NULL;
}
targetPrivate->type = reachabilityTypeAddress;
targetPrivate->remoteAddress = CFAllocatorAllocate(NULL, address->sa_len, 0);
bcopy(address, targetPrivate->remoteAddress, address->sa_len);
return (SCNetworkReachabilityRef)targetPrivate;
}
SCNetworkReachabilityRef
SCNetworkReachabilityCreateWithAddressPair(CFAllocatorRef allocator,
const struct sockaddr *localAddress,
const struct sockaddr *remoteAddress)
{
SCNetworkReachabilityPrivateRef targetPrivate;
if ((localAddress == NULL) && (remoteAddress == NULL)) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
if (localAddress) {
if ((localAddress->sa_len == 0) ||
(localAddress->sa_len > sizeof(struct sockaddr_storage))) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
}
if (remoteAddress) {
if ((remoteAddress->sa_len == 0) ||
(remoteAddress->sa_len > sizeof(struct sockaddr_storage))) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
}
targetPrivate = __SCNetworkReachabilityCreatePrivate(allocator);
if (!targetPrivate) {
return NULL;
}
targetPrivate->type = reachabilityTypeAddressPair;
if (localAddress) {
targetPrivate->localAddress = CFAllocatorAllocate(NULL, localAddress->sa_len, 0);
bcopy(localAddress, targetPrivate->localAddress, localAddress->sa_len);
}
if (remoteAddress) {
targetPrivate->remoteAddress = CFAllocatorAllocate(NULL, remoteAddress->sa_len, 0);
bcopy(remoteAddress, targetPrivate->remoteAddress, remoteAddress->sa_len);
}
return (SCNetworkReachabilityRef)targetPrivate;
}
SCNetworkReachabilityRef
SCNetworkReachabilityCreateWithName(CFAllocatorRef allocator,
const char *nodename)
{
SCNetworkReachabilityPrivateRef targetPrivate;
if (!nodename) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
targetPrivate = __SCNetworkReachabilityCreatePrivate(allocator);
if (!targetPrivate) {
return NULL;
}
targetPrivate->type = reachabilityTypeName;
targetPrivate->flags |= kSCNetworkFlagsFirstResolvePending;
targetPrivate->name = CFAllocatorAllocate(NULL, strlen(nodename) + 1, 0);
strcpy((char *)targetPrivate->name, nodename);
return (SCNetworkReachabilityRef)targetPrivate;
}
CFTypeID
SCNetworkReachabilityGetTypeID(void)
{
pthread_once(&initialized, __SCNetworkReachabilityInitialize);
return __kSCNetworkReachabilityTypeID;
}
CFArrayRef
SCNetworkReachabilityCopyResolvedAddress(SCNetworkReachabilityRef target,
int *error_num)
{
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
if (!isA_SCNetworkReachability(target)) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
if (targetPrivate->type != reachabilityTypeName) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
if (error_num) {
*error_num = targetPrivate->resolvedAddressError;
}
if (targetPrivate->resolvedAddress || (targetPrivate->resolvedAddressError != NETDB_SUCCESS)) {
if (targetPrivate->resolvedAddress) {
return CFRetain(targetPrivate->resolvedAddress);
} else {
_SCErrorSet(kSCStatusOK);
return NULL;
}
}
_SCErrorSet(kSCStatusReachabilityUnknown);
return NULL;
}
static void
__SCNetworkReachabilitySetResolvedAddress(SCNetworkReachabilityRef target,
CFArrayRef addresses,
int error_num)
{
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
if (targetPrivate->resolvedAddress) {
CFRelease(targetPrivate->resolvedAddress);
}
targetPrivate->resolvedAddress = addresses ? CFRetain(addresses) : NULL;
targetPrivate->resolvedAddressError = error_num;
return;
}
#ifdef CHECK_IPV6_REACHABILITY
static void
__SCNetworkReachabilityCallbackSetResolvedAddress(int32_t status, struct addrinfo *res, void *context)
{
Boolean ok;
struct addrinfo *resP;
SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)context;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
ok = (status == 0) && (res != NULL);
SCLog(_sc_debug, LOG_DEBUG,
CFSTR("process async DNS complete%s"),
ok ? "" : ", host not found");
if (ok) {
CFMutableArrayRef addresses;
addresses = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
for (resP = res; resP; resP = resP->ai_next) {
CFDataRef newAddress;
newAddress = CFDataCreate(NULL, (void *)resP->ai_addr, resP->ai_addr->sa_len);
CFArrayAppendValue(addresses, newAddress);
CFRelease(newAddress);
}
__SCNetworkReachabilitySetResolvedAddress(target, addresses, NETDB_SUCCESS);
CFRelease(addresses);
} else {
SCLog(_sc_debug, LOG_INFO, CFSTR("getaddrinfo() failed: %s"), gai_strerror(status));
__SCNetworkReachabilitySetResolvedAddress(target, (CFArrayRef)kCFNull, status);
}
if (res) freeaddrinfo(res);
if (targetPrivate->rls) {
SCLog(_sc_debug, LOG_INFO, CFSTR("DNS request completed"));
CFRunLoopSourceSignal(targetPrivate->rls);
__signalRunLoop(target, targetPrivate->rls, targetPrivate->rlList);
}
return;
}
#else
static void
__SCNetworkReachabilityCallbackSetResolvedAddress(struct hostent *h, int error, void *context)
{
SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)context;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
SCLog(_sc_debug, LOG_DEBUG,
CFSTR("process async DNS complete%s"),
(h == NULL) ? ", host not found" : "");
if (h && h->h_length) {
CFMutableArrayRef addresses;
union {
struct sockaddr sa;
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
struct sockaddr_storage ss;
} addr;
char **ha = h->h_addr_list;
addresses = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
bzero(&addr, sizeof(addr));
while (*ha) {
CFDataRef newAddress;
switch (h->h_length) {
case sizeof(struct in_addr) :
addr.sin.sin_family = AF_INET;
addr.sin.sin_len = sizeof(struct sockaddr_in);
bcopy(*ha, &addr.sin.sin_addr, h->h_length);
break;
case sizeof(struct in6_addr) :
addr.sin6.sin6_family = AF_INET6;
addr.sin6.sin6_len = sizeof(struct sockaddr_in6);
bcopy(*ha, &addr.sin6.sin6_addr, h->h_length);
break;
}
newAddress = CFDataCreate(NULL, (void *)&addr, addr.sa.sa_len);
CFArrayAppendValue(addresses, newAddress);
CFRelease(newAddress);
ha++;
}
__SCNetworkReachabilitySetResolvedAddress(target, addresses, NETDB_SUCCESS);
CFRelease(addresses);
} else {
SCLog(_sc_debug, LOG_INFO, CFSTR("getipnodebyname() failed: %s"), __netdb_error(error));
__SCNetworkReachabilitySetResolvedAddress(target, (CFArrayRef)kCFNull, error);
}
if (h) freehostent(h);
if (targetPrivate->rls) {
SCLog(_sc_debug, LOG_INFO, CFSTR("DNS request completed"));
CFRunLoopSourceSignal(targetPrivate->rls);
__signalRunLoop(target, targetPrivate->rls, targetPrivate->rlList);
}
return;
}
#endif
static int
rankReachability(SCNetworkConnectionFlags flags)
{
int rank = 0;
if (flags & kSCNetworkFlagsReachable) rank = 2;
if (flags & kSCNetworkFlagsConnectionRequired) rank = 1;
return rank;
}
#ifdef CHECK_IPV6_REACHABILITY
static void
getaddrinfo_async_handleCFReply(CFMachPortRef port, void *msg, CFIndex size, void *info)
{
SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)info;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
pthread_mutex_lock(&targetPrivate->lock);
getaddrinfo_async_handle_reply(msg);
if (port == targetPrivate->dnsPort) {
CFRelease(targetPrivate->dnsRLS);
targetPrivate->dnsRLS = NULL;
CFRelease(targetPrivate->dnsPort);
targetPrivate->dnsPort = NULL;
}
pthread_mutex_unlock(&targetPrivate->lock);
return;
}
#else
static void
getipnodebyname_async_handleCFReply(CFMachPortRef port, void *msg, CFIndex size, void *info)
{
SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)info;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
pthread_mutex_lock(&targetPrivate->lock);
getipnodebyname_async_handleReply(msg);
if (port == targetPrivate->dnsPort) {
CFRelease(targetPrivate->dnsRLS);
targetPrivate->dnsRLS = NULL;
CFRelease(targetPrivate->dnsPort);
targetPrivate->dnsPort = NULL;
}
pthread_mutex_unlock(&targetPrivate->lock);
return;
}
#endif
static Boolean
checkResolverReachability(SCDynamicStoreRef *storeP,
SCNetworkConnectionFlags *flags,
Boolean *haveDNS)
{
int i;
Boolean ok = TRUE;
*flags = kSCNetworkFlagsReachable;
*haveDNS = FALSE;
if (needDNS) {
if (hn_store) {
needDNS = FALSE;
}
res_init();
}
for (i = 0; i < _res.nscount; i++) {
SCNetworkConnectionFlags ns_flags = 0;
if (_res.nsaddr_list[i].sin_addr.s_addr == 0) {
continue;
}
*haveDNS = TRUE;
if (_res.nsaddr_list[i].sin_len == 0) {
_res.nsaddr_list[i].sin_len = sizeof(_res.nsaddr_list[i]);
}
ok = checkAddress(storeP, (struct sockaddr *)&_res.nsaddr_list[i], &ns_flags, NULL);
if (!ok) {
break;
}
if (rankReachability(ns_flags) < rankReachability(*flags)) {
*flags = ns_flags;
}
}
if (!*haveDNS) {
*flags = 0;
}
return ok;
}
static Boolean
startAsyncDNSQuery(SCNetworkReachabilityRef target) {
CFMachPortContext context = { 0, (void *)target, CFRetain, CFRelease, CFCopyDescription };
int error;
#ifdef CHECK_IPV6_REACHABILITY
struct addrinfo hints;
#endif
CFIndex i;
CFIndex n;
mach_port_t port;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
#ifdef CHECK_IPV6_REACHABILITY
bzero(&hints, sizeof(hints));
hints.ai_flags = AI_ADDRCONFIG;
error = getaddrinfo_async_start(&port,
targetPrivate->name,
NULL,
&hints,
__SCNetworkReachabilityCallbackSetResolvedAddress,
(void *)target);
if (error != 0) {
__SCNetworkReachabilitySetResolvedAddress(target, (CFArrayRef)kCFNull, error);
return FALSE;
}
targetPrivate->dnsPort = CFMachPortCreateWithPort(NULL,
port,
getaddrinfo_async_handleCFReply,
&context,
NULL);
#else
port = getipnodebyname_async_start(targetPrivate->name,
AF_INET,
0,
&error,
__SCNetworkReachabilityCallbackSetResolvedAddress,
(void *)target);
if (port == MACH_PORT_NULL) {
__SCNetworkReachabilitySetResolvedAddress(target, (CFArrayRef)kCFNull, error);
return FALSE;
}
targetPrivate->dnsPort = CFMachPortCreateWithPort(NULL,
port,
getipnodebyname_async_handleCFReply,
&context,
NULL);
#endif
targetPrivate->dnsRLS = CFMachPortCreateRunLoopSource(NULL, targetPrivate->dnsPort, 0);
n = CFArrayGetCount(targetPrivate->rlList);
for (i = 0; i < n; i += 3) {
CFRunLoopRef rl = (CFRunLoopRef)CFArrayGetValueAtIndex(targetPrivate->rlList, i+1);
CFStringRef rlMode = (CFStringRef) CFArrayGetValueAtIndex(targetPrivate->rlList, i+2);
CFRunLoopAddSource(rl, targetPrivate->dnsRLS, rlMode);
}
return TRUE;
}
static Boolean
__SCNetworkReachabilityGetFlags(SCDynamicStoreRef *storeP,
SCNetworkReachabilityRef target,
SCNetworkConnectionFlags *flags,
uint16_t *if_index,
Boolean async)
{
CFMutableArrayRef addresses = NULL;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
SCNetworkConnectionFlags my_flags = 0;
uint16_t my_index = 0;
Boolean ok = TRUE;
*flags = 0;
if (if_index) {
*if_index = 0;
}
if (!isA_SCNetworkReachability(target)) {
_SCErrorSet(kSCStatusInvalidArgument);
return FALSE;
}
switch (targetPrivate->type) {
case reachabilityTypeAddress :
case reachabilityTypeAddressPair : {
if (targetPrivate->localAddress) {
if (isAddressZero(targetPrivate->localAddress, &my_flags)) {
goto checkRemote;
}
ok = checkAddress(storeP, targetPrivate->localAddress, &my_flags, &my_index);
if (!ok) {
goto error;
}
if (!(my_flags & kSCNetworkFlagsIsLocalAddress)) {
goto error;
}
}
checkRemote :
if (targetPrivate->remoteAddress) {
my_flags = 0;
my_index = 0;
if (isAddressZero(targetPrivate->remoteAddress, &my_flags)) {
break;
}
ok = checkAddress(storeP, targetPrivate->remoteAddress, &my_flags, &my_index);
if (!ok) {
goto error;
}
}
break;
}
case reachabilityTypeName : {
int error;
#ifndef CHECK_IPV6_REACHABILITY
struct hostent *h;
#endif
Boolean haveDNS = FALSE;
#ifdef CHECK_IPV6_REACHABILITY
struct addrinfo hints;
#endif
SCNetworkConnectionFlags ns_flags;
#ifdef CHECK_IPV6_REACHABILITY
struct addrinfo *res;
#endif
addresses = (CFMutableArrayRef)SCNetworkReachabilityCopyResolvedAddress(target, &error);
if (addresses || (error != NETDB_SUCCESS)) {
goto checkResolvedAddress;
}
ok = checkResolverReachability(storeP, &ns_flags, &haveDNS);\
if (!ok) {
goto error;
}
if (rankReachability(ns_flags) < 2) {
if (!checkAddressZero(storeP, &my_flags, &my_index)) {
goto error;
}
if (async && targetPrivate->rls) {
#ifdef CHECK_IPV6_REACHABILITY
__SCNetworkReachabilityCallbackSetResolvedAddress(EAI_NODATA,
NULL,
(void *)target);
#else
__SCNetworkReachabilityCallbackSetResolvedAddress(NULL,
HOST_NOT_FOUND,
(void *)target);
#endif
my_flags |= (targetPrivate->flags & kSCNetworkFlagsFirstResolvePending);
SCLog(_sc_debug, LOG_INFO, CFSTR("no DNS servers are reachable"));
CFRunLoopSourceSignal(targetPrivate->rls);
__signalRunLoop(target, targetPrivate->rls, targetPrivate->rlList);
}
break;
}
if (async) {
my_flags = targetPrivate->flags;
my_index = targetPrivate->if_index;
if (targetPrivate->dnsPort) {
break;
}
SCLog(_sc_debug, LOG_INFO, CFSTR("start DNS query for \"%s\""), targetPrivate->name);
if (!startAsyncDNSQuery(target)) {
goto checkResolvedAddress;
}
break;
}
SCLog(_sc_debug, LOG_INFO, CFSTR("check DNS for \"%s\""), targetPrivate->name);
#ifdef CHECK_IPV6_REACHABILITY
bzero(&hints, sizeof(hints));
hints.ai_flags = AI_ADDRCONFIG;
error = getaddrinfo(targetPrivate->name, NULL, &hints, &res);
__SCNetworkReachabilityCallbackSetResolvedAddress(error, res, (void *)target);
#else
h = getipnodebyname(targetPrivate->name, AF_INET, 0, &error);
__SCNetworkReachabilityCallbackSetResolvedAddress(h, error, (void *)target);
#endif
addresses = (CFMutableArrayRef)SCNetworkReachabilityCopyResolvedAddress(target, &error);
checkResolvedAddress :
my_flags = 0;
my_index = 0;
if (isA_CFArray(addresses)) {
CFIndex i;
CFIndex n = CFArrayGetCount(addresses);
for (i = 0; i < n; i++) {
SCNetworkConnectionFlags ns_flags = 0;
uint16_t ns_if_index = 0;
struct sockaddr *sa;
sa = (struct sockaddr *)CFDataGetBytePtr(CFArrayGetValueAtIndex(addresses, i));
ok = checkAddress(storeP, sa, &ns_flags, &ns_if_index);
if (!ok) {
goto error;
}
if (rankReachability(ns_flags) > rankReachability(my_flags)) {
my_flags = ns_flags;
my_index = ns_if_index;
if (rankReachability(my_flags) == 2) {
break;
}
}
}
} else {
if ((error == HOST_NOT_FOUND) && !haveDNS) {
ok = checkAddressZero(storeP, &my_flags, &my_index);
if (!ok) {
goto error;
}
if ((my_flags & kSCNetworkFlagsReachable) &&
(my_flags & kSCNetworkFlagsConnectionRequired)) {
break;
}
my_flags = 0;
my_index = 0;
}
}
break;
}
}
*flags = my_flags;
if (if_index) {
*if_index = my_index;
}
error :
if (addresses) CFRelease(addresses);
return ok;
}
Boolean
SCNetworkReachabilityGetFlags(SCNetworkReachabilityRef target,
SCNetworkConnectionFlags *flags)
{
Boolean ok;
SCDynamicStoreRef store = NULL;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
if (!isA_SCNetworkReachability(target)) {
_SCErrorSet(kSCStatusInvalidArgument);
return FALSE;
}
if (targetPrivate->rlList) {
*flags = targetPrivate->flags & ~kSCNetworkFlagsFirstResolvePending;
return TRUE;
}
ok = __SCNetworkReachabilityGetFlags(&store, target, flags, NULL, FALSE);
*flags &= ~kSCNetworkFlagsFirstResolvePending;
if (store) CFRelease(store);
return ok;
}
static void
__SCNetworkReachabilityReachabilitySetNotifications(SCDynamicStoreRef store)
{
CFStringRef key;
CFMutableArrayRef keys;
CFStringRef pattern;
CFMutableArrayRef patterns;
keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCEntNetIPv4);
CFArrayAppendValue(keys, key);
CFRelease(key);
key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
kSCDynamicStoreDomainState,
kSCEntNetDNS);
CFArrayAppendValue(keys, key);
CFRelease(key);
key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
kSCDynamicStoreDomainState,
kSCEntNetIPv4);
CFArrayAppendValue(keys, key);
CFRelease(key);
pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCCompAnyRegex,
kSCEntNetIPv4);
CFArrayAppendValue(patterns, pattern);
CFRelease(pattern);
pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCCompAnyRegex,
kSCEntNetInterface);
CFArrayAppendValue(patterns, pattern);
CFRelease(pattern);
pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCCompAnyRegex,
kSCEntNetPPP);
CFArrayAppendValue(patterns, pattern);
CFRelease(pattern);
pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainState,
kSCCompAnyRegex,
kSCEntNetIPv4);
CFArrayAppendValue(patterns, pattern);
CFRelease(pattern);
pattern = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
kSCDynamicStoreDomainState,
kSCCompAnyRegex,
kSCEntNetIPv4);
CFArrayAppendValue(patterns, pattern);
CFRelease(pattern);
pattern = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
kSCDynamicStoreDomainState,
kSCCompAnyRegex,
kSCEntNetIPv6);
CFArrayAppendValue(patterns, pattern);
CFRelease(pattern);
(void)SCDynamicStoreSetNotificationKeys(store, keys, patterns);
CFRelease(keys);
CFRelease(patterns);
return;
}
static void
__SCNetworkReachabilityReachabilityHandleChanges(SCDynamicStoreRef store,
CFArrayRef changedKeys,
void *info)
{
Boolean dnsChanged = FALSE;
CFIndex i;
CFStringRef key;
CFIndex nTargets;
const void * targets_q[N_QUICK];
const void ** targets = targets_q;
pthread_mutex_lock(&hn_lock);
nTargets = CFSetGetCount(hn_targets);
if (nTargets == 0) {
goto done;
}
if (CFArrayGetCount(changedKeys) == 0) {
goto done;
}
SCLog(_sc_debug, LOG_INFO, CFSTR("process configuration change"));
key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
kSCDynamicStoreDomainState,
kSCEntNetDNS);
if (CFArrayContainsValue(changedKeys,
CFRangeMake(0, CFArrayGetCount(changedKeys)),
key)) {
dnsChanged = TRUE;
needDNS = TRUE;
}
CFRelease(key);
if (!dnsChanged) {
Boolean haveDNS = FALSE;
SCNetworkConnectionFlags ns_flags;
Boolean ok;
ok = checkResolverReachability(&store, &ns_flags, &haveDNS);\
if (!ok || (rankReachability(ns_flags) < 2)) {
dnsChanged = TRUE;
}
}
SCLog(_sc_debug && dnsChanged, LOG_INFO, CFSTR(" DNS changed"));
if (nTargets > (CFIndex)(sizeof(targets_q) / sizeof(CFTypeRef)))
targets = CFAllocatorAllocate(NULL, nTargets * sizeof(CFTypeRef), 0);
CFSetGetValues(hn_targets, targets);
for (i = 0; i < nTargets; i++) {
SCNetworkReachabilityRef target = targets[i];
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
pthread_mutex_lock(&targetPrivate->lock);
if (dnsChanged) {
if (targetPrivate->dnsPort) {
#ifdef CHECK_IPV6_REACHABILITY
lu_async_call_cancel(CFMachPortGetPort(targetPrivate->dnsPort));
#else
getipnodebyname_async_cancel(CFMachPortGetPort(targetPrivate->dnsPort));
#endif
CFRelease(targetPrivate->dnsRLS);
targetPrivate->dnsRLS = NULL;
CFRelease(targetPrivate->dnsPort);
targetPrivate->dnsPort = NULL;
}
__SCNetworkReachabilitySetResolvedAddress(target, NULL, NETDB_SUCCESS);
}
CFRunLoopSourceSignal(targetPrivate->rls);
__signalRunLoop(target, targetPrivate->rls, targetPrivate->rlList);
pthread_mutex_unlock(&targetPrivate->lock);
}
if (targets != targets_q) CFAllocatorDeallocate(NULL, targets);
done :
pthread_mutex_unlock(&hn_lock);
return;
}
static Boolean
__isScheduled(CFTypeRef obj, CFRunLoopRef runLoop, CFStringRef runLoopMode, CFMutableArrayRef rlList)
{
CFIndex i;
CFIndex n = CFArrayGetCount(rlList);
for (i = 0; i < n; i += 3) {
if (obj && !CFEqual(obj, CFArrayGetValueAtIndex(rlList, i))) {
continue;
}
if (runLoop && !CFEqual(runLoop, CFArrayGetValueAtIndex(rlList, i+1))) {
continue;
}
if (runLoopMode && !CFEqual(runLoopMode, CFArrayGetValueAtIndex(rlList, i+2))) {
continue;
}
return TRUE;
}
return FALSE;
}
static void
__schedule(CFTypeRef obj, CFRunLoopRef runLoop, CFStringRef runLoopMode, CFMutableArrayRef rlList)
{
CFArrayAppendValue(rlList, obj);
CFArrayAppendValue(rlList, runLoop);
CFArrayAppendValue(rlList, runLoopMode);
return;
}
static Boolean
__unschedule(CFTypeRef obj, CFRunLoopRef runLoop, CFStringRef runLoopMode, CFMutableArrayRef rlList, Boolean all)
{
CFIndex i = 0;
Boolean found = FALSE;
CFIndex n = CFArrayGetCount(rlList);
while (i < n) {
if (obj && !CFEqual(obj, CFArrayGetValueAtIndex(rlList, i))) {
i += 3;
continue;
}
if (runLoop && !CFEqual(runLoop, CFArrayGetValueAtIndex(rlList, i+1))) {
i += 3;
continue;
}
if (runLoopMode && !CFEqual(runLoopMode, CFArrayGetValueAtIndex(rlList, i+2))) {
i += 3;
continue;
}
found = TRUE;
CFArrayRemoveValueAtIndex(rlList, i + 2);
CFArrayRemoveValueAtIndex(rlList, i + 1);
CFArrayRemoveValueAtIndex(rlList, i);
if (!all) {
return found;
}
n -= 3;
}
return found;
}
static void
rlsPerform(void *info)
{
void *context_info;
void (*context_release)(const void *);
SCNetworkConnectionFlags flags;
uint16_t if_index;
Boolean ok;
SCNetworkReachabilityCallBack rlsFunction;
SCDynamicStoreRef store = NULL;
SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)info;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
SCLog(_sc_debug, LOG_DEBUG, CFSTR("process reachability change"));
pthread_mutex_lock(&targetPrivate->lock);
ok = __SCNetworkReachabilityGetFlags(&store, target, &flags, &if_index, TRUE);
if (store) CFRelease(store);
if (!ok) {
flags = 0;
if_index = 0;
}
if ((targetPrivate->flags == flags) && (targetPrivate->if_index == if_index)) {
pthread_mutex_unlock(&targetPrivate->lock);
SCLog(_sc_debug, LOG_DEBUG, CFSTR("flags/interface match"));
return;
}
targetPrivate->flags = flags;
targetPrivate->if_index = if_index;
rlsFunction = targetPrivate->rlsFunction;
if (NULL != targetPrivate->rlsContext.retain) {
context_info = (void *)targetPrivate->rlsContext.retain(targetPrivate->rlsContext.info);
context_release = targetPrivate->rlsContext.release;
} else {
context_info = targetPrivate->rlsContext.info;
context_release = NULL;
}
pthread_mutex_unlock(&targetPrivate->lock);
if (rlsFunction) {
SCLog(_sc_debug, LOG_DEBUG, CFSTR("flags/interface have changed"));
(*rlsFunction)(target, flags, context_info);
}
if (context_release) {
context_release(context_info);
}
return;
}
Boolean
SCNetworkReachabilitySetCallback(SCNetworkReachabilityRef target,
SCNetworkReachabilityCallBack callout,
SCNetworkReachabilityContext *context)
{
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
pthread_mutex_lock(&targetPrivate->lock);
if (targetPrivate->rlsContext.release) {
targetPrivate->rlsContext.release(targetPrivate->rlsContext.info);
}
targetPrivate->rlsFunction = callout;
targetPrivate->rlsContext.info = NULL;
targetPrivate->rlsContext.retain = NULL;
targetPrivate->rlsContext.release = NULL;
targetPrivate->rlsContext.copyDescription = NULL;
if (context) {
bcopy(context, &targetPrivate->rlsContext, sizeof(SCNetworkReachabilityContext));
if (context->retain) {
targetPrivate->rlsContext.info = (void *)context->retain(context->info);
}
}
pthread_mutex_unlock(&targetPrivate->lock);
return TRUE;
}
Boolean
SCNetworkReachabilityScheduleWithRunLoop(SCNetworkReachabilityRef target,
CFRunLoopRef runLoop,
CFStringRef runLoopMode)
{
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
Boolean init = FALSE;
Boolean ok = FALSE;
if (!isA_SCNetworkReachability(target) || runLoop == NULL || runLoopMode == NULL) {
_SCErrorSet(kSCStatusInvalidArgument);
return FALSE;
}
pthread_mutex_lock(&hn_lock);
pthread_mutex_lock(&targetPrivate->lock);
if (!hn_store) {
hn_store = SCDynamicStoreCreate(NULL,
CFSTR("SCNetworkReachability"),
__SCNetworkReachabilityReachabilityHandleChanges,
NULL);
if (!hn_store) {
SCLog(_sc_verbose, LOG_INFO, CFSTR("SCDynamicStoreCreate() failed"));
goto done;
}
__SCNetworkReachabilityReachabilitySetNotifications(hn_store);
hn_storeRLS = SCDynamicStoreCreateRunLoopSource(NULL, hn_store, 0);
hn_rlList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
hn_targets = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
}
if (!targetPrivate->rls) {
CFRunLoopSourceContext context = { 0 , (void *)target , CFRetain , CFRelease , CFCopyDescription , CFEqual , CFHash , NULL , NULL , rlsPerform };
targetPrivate->rls = CFRunLoopSourceCreate(NULL, 0, &context);
targetPrivate->rlList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
init = TRUE;
}
if (!__isScheduled(NULL, runLoop, runLoopMode, targetPrivate->rlList)) {
CFRunLoopAddSource(runLoop, targetPrivate->rls, runLoopMode);
if (targetPrivate->dnsRLS) {
CFRunLoopAddSource(runLoop, targetPrivate->dnsRLS, runLoopMode);
}
}
__schedule(target, runLoop, runLoopMode, targetPrivate->rlList);
if (!__isScheduled(NULL, runLoop, runLoopMode, hn_rlList)) {
CFRunLoopAddSource(runLoop, hn_storeRLS, runLoopMode);
}
__schedule(target, runLoop, runLoopMode, hn_rlList);
CFSetAddValue(hn_targets, target);
if (init) {
SCNetworkConnectionFlags flags;
uint16_t if_index;
SCDynamicStoreRef store = NULL;
if (__SCNetworkReachabilityGetFlags(&store, target, &flags, &if_index, TRUE)) {
targetPrivate->flags = flags;
targetPrivate->if_index = if_index;
CFRunLoopSourceSignal(targetPrivate->rls);
__signalRunLoop(target, targetPrivate->rls, targetPrivate->rlList);
} else {
targetPrivate->flags = 0;
targetPrivate->if_index = 0;
}
if (store) CFRelease(store);
}
ok = TRUE;
done :
pthread_mutex_unlock(&targetPrivate->lock);
pthread_mutex_unlock(&hn_lock);
return ok;
}
Boolean
SCNetworkReachabilityUnscheduleFromRunLoop(SCNetworkReachabilityRef target,
CFRunLoopRef runLoop,
CFStringRef runLoopMode)
{
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
CFIndex n;
Boolean ok = FALSE;
if (!isA_SCNetworkReachability(target) || runLoop == NULL || runLoopMode == NULL) {
_SCErrorSet(kSCStatusInvalidArgument);
goto done;
}
pthread_mutex_lock(&hn_lock);
pthread_mutex_lock(&targetPrivate->lock);
if (!targetPrivate->rls) {
goto done;
}
if (!__unschedule(NULL, runLoop, runLoopMode, targetPrivate->rlList, FALSE)) {
goto done;
}
n = CFArrayGetCount(targetPrivate->rlList);
if (n == 0 || !__isScheduled(NULL, runLoop, runLoopMode, targetPrivate->rlList)) {
CFRunLoopRemoveSource(runLoop, targetPrivate->rls, runLoopMode);
if (targetPrivate->dnsRLS) {
CFRunLoopRemoveSource(runLoop, targetPrivate->dnsRLS, runLoopMode);
}
if (n == 0) {
CFRelease(targetPrivate->rls);
targetPrivate->rls = NULL;
CFRelease(targetPrivate->rlList);
targetPrivate->rlList = NULL;
CFSetRemoveValue(hn_targets, target);
if (targetPrivate->dnsPort) {
#ifdef CHECK_IPV6_REACHABILITY
lu_async_call_cancel(CFMachPortGetPort(targetPrivate->dnsPort));
#else
getipnodebyname_async_cancel(CFMachPortGetPort(targetPrivate->dnsPort));
#endif
CFRelease(targetPrivate->dnsRLS);
targetPrivate->dnsRLS = NULL;
CFRelease(targetPrivate->dnsPort);
targetPrivate->dnsPort = NULL;
}
}
}
(void)__unschedule(target, runLoop, runLoopMode, hn_rlList, FALSE);
n = CFArrayGetCount(hn_rlList);
if (n == 0 || !__isScheduled(NULL, runLoop, runLoopMode, hn_rlList)) {
CFRunLoopRemoveSource(runLoop, hn_storeRLS, runLoopMode);
if (n == 0) {
CFRelease(hn_targets);
CFRelease(hn_rlList);
CFRelease(hn_storeRLS);
CFRelease(hn_store);
hn_store = NULL;
needDNS = TRUE;
}
}
ok = TRUE;
done :
pthread_mutex_unlock(&targetPrivate->lock);
pthread_mutex_unlock(&hn_lock);
return ok;
}