SCNetworkReachability.c [plain text]
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCValidation.h>
#include <SystemConfiguration/SCPrivate.h>
#include <CoreFoundation/CFRuntime.h>
#include <pthread.h>
#include <notify.h>
#include <dnsinfo.h>
#include <netinet/in.h>
#include <arpa/inet.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/ppp_msg.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;
Boolean haveDNS;
CFMachPortRef dnsPort;
CFRunLoopSourceRef dnsRLS;
struct timeval dnsQueryStart;
} 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 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;
typedef struct {
dns_config_t *config;
int refs;
} dns_configuration_t;
static pthread_mutex_t dns_lock = PTHREAD_MUTEX_INITIALIZER;
static dns_configuration_t *dns_configuration = NULL;
static int dns_token;
static Boolean dns_token_valid = FALSE;
static __inline__ CFTypeRef
isA_SCNetworkReachability(CFTypeRef obj)
{
return (isA_CFType(obj, SCNetworkReachabilityGetTypeID()));
}
static void
__log_query_time(Boolean found, Boolean async, struct timeval *start)
{
struct timeval dnsQueryComplete;
struct timeval dnsQueryElapsed;
if (!_sc_debug) {
return;
}
if (start->tv_sec == 0) {
return;
}
(void) gettimeofday(&dnsQueryComplete, NULL);
timersub(&dnsQueryComplete, start, &dnsQueryElapsed);
SCLog(TRUE, LOG_DEBUG,
CFSTR("%ssync DNS complete%s (query time = %d.%3.3d)"),
async ? "a" : "",
found ? "" : ", host not found",
dnsQueryElapsed.tv_sec,
dnsQueryElapsed.tv_usec / 1000);
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 != NULL) ? *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 == NULL) {
store = SCDynamicStoreCreate(NULL, CFSTR("SCNetworkReachability"), NULL, NULL);
if (store == NULL) {
SCLog(_sc_verbose, LOG_INFO, CFSTR("updatePPPStatus 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 == NULL) {
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 != NULL) CFRelease(dict);
if (storeP != NULL) *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 != NULL) ? *storeP : NULL;
const void * values_q[N_QUICK];
const void ** values = values_q;
if (sa == NULL) {
entity = kSCEntNetIPv4;
} else {
switch (sa->sa_family) {
case AF_INET :
entity = kSCEntNetIPv4;
break;
case AF_INET6 :
entity = kSCEntNetIPv6;
break;
default :
goto done;
}
}
if (store == NULL) {
store = SCDynamicStoreCreate(NULL, CFSTR("SCNetworkReachability"), NULL, NULL);
if (store == NULL) {
SCLog(_sc_debug, LOG_INFO, CFSTR(" status = unknown (could not access SCDynamicStore"));
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 == NULL) {
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 != NULL) CFRelease(dict);
if (storeP != NULL) *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 != NULL) ? *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
*flags = 0;
if (if_index != NULL) {
*if_index = 0;
}
if (address == NULL) {
goto checkAvailable;
}
switch (address->sa_family) {
case AF_INET :
case AF_INET6 :
if (_sc_debug) {
_SC_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;
}
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) {
_SC_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 checkAvailable;
}
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);
if (((struct sockaddr_in *)address)->sin_addr.s_addr == 0) {
statusMessage = "isReachable (this host)";
*flags |= kSCNetworkFlagsIsLocalAddress;
}
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 (!(rtm->rtm_flags & RTF_GATEWAY) &&
(rti_info[RTAX_GATEWAY] != NULL) &&
(rti_info[RTAX_GATEWAY]->sa_family == AF_LINK) &&
!(ifr.ifr_flags & IFF_POINTOPOINT)) {
*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 != NULL) {
*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 != NULL) *storeP = store;
if (sc_status != kSCStatusOK) {
_SCErrorSet(sc_status);
return FALSE;
}
return TRUE;
}
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 != NULL) {
_SC_sockaddr_to_string(targetPrivate->localAddress, buf, sizeof(buf));
CFStringAppendFormat(result, NULL, CFSTR("local address=%s"),
buf);
}
if (targetPrivate->remoteAddress != NULL) {
_SC_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 != NULL) {
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);
_SC_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 {
CFStringAppendFormat(result, NULL, CFSTR(" (%s)"),
gai_strerror(targetPrivate->resolvedAddressError));
}
} else if (targetPrivate->dnsPort) {
CFStringAppendFormat(result, NULL, CFSTR(" (DNS query active)"));
}
break;
}
}
if (targetPrivate->rls != NULL) {
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;
pthread_mutex_destroy(&targetPrivate->lock);
if (targetPrivate->name != NULL)
CFAllocatorDeallocate(NULL, (void *)targetPrivate->name);
if (targetPrivate->resolvedAddress != NULL)
CFRelease(targetPrivate->resolvedAddress);
if (targetPrivate->localAddress != NULL)
CFAllocatorDeallocate(NULL, (void *)targetPrivate->localAddress);
if (targetPrivate->remoteAddress != NULL)
CFAllocatorDeallocate(NULL, (void *)targetPrivate->remoteAddress);
if (targetPrivate->rlsContext.release != NULL) {
(*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);
size = sizeof(SCNetworkReachabilityPrivate) - sizeof(CFRuntimeBase);
targetPrivate = (SCNetworkReachabilityPrivateRef)_CFRuntimeCreateInstance(allocator,
__kSCNetworkReachabilityTypeID,
size,
NULL);
if (targetPrivate == NULL) {
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->haveDNS = FALSE;
targetPrivate->dnsPort = NULL;
targetPrivate->dnsRLS = NULL;
return targetPrivate;
}
SCNetworkReachabilityRef
SCNetworkReachabilityCreateWithAddress(CFAllocatorRef allocator,
const struct sockaddr *address)
{
SCNetworkReachabilityPrivateRef targetPrivate;
if ((address == NULL) ||
(address->sa_len == 0) ||
(address->sa_len > sizeof(struct sockaddr_storage))) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
targetPrivate = __SCNetworkReachabilityCreatePrivate(allocator);
if (targetPrivate == NULL) {
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 != NULL) {
if ((localAddress->sa_len == 0) ||
(localAddress->sa_len > sizeof(struct sockaddr_storage))) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
}
if (remoteAddress != NULL) {
if ((remoteAddress->sa_len == 0) ||
(remoteAddress->sa_len > sizeof(struct sockaddr_storage))) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
}
targetPrivate = __SCNetworkReachabilityCreatePrivate(allocator);
if (targetPrivate == NULL) {
return NULL;
}
targetPrivate->type = reachabilityTypeAddressPair;
if (localAddress != NULL) {
targetPrivate->localAddress = CFAllocatorAllocate(NULL, localAddress->sa_len, 0);
bcopy(localAddress, targetPrivate->localAddress, localAddress->sa_len);
}
if (remoteAddress != NULL) {
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)
{
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
SCNetworkReachabilityPrivateRef targetPrivate;
if (nodename == NULL) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
bzero(&sin, sizeof(sin));
sin.sin_len = sizeof(sin);
sin.sin_family = AF_INET;
if (inet_aton(nodename, &sin.sin_addr) == 1) {
return SCNetworkReachabilityCreateWithAddress(allocator, (struct sockaddr *)&sin);
}
bzero(&sin6, sizeof(sin6));
sin6.sin6_len = sizeof(sin6);
sin6.sin6_family = AF_INET6;
if (inet_pton(AF_INET6, nodename, &sin6.sin6_addr) == 1) {
char *p;
p = strchr(nodename, '%');
if (p != NULL) {
sin6.sin6_scope_id = if_nametoindex(p+1);
}
return SCNetworkReachabilityCreateWithAddress(allocator, (struct sockaddr *)&sin6);
}
targetPrivate = __SCNetworkReachabilityCreatePrivate(allocator);
if (targetPrivate == NULL) {
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 != NULL) {
return CFRetain(targetPrivate->resolvedAddress);
} else {
_SCErrorSet(kSCStatusOK);
return NULL;
}
}
_SCErrorSet(kSCStatusReachabilityUnknown);
return NULL;
}
static void
__SCNetworkReachabilitySetResolvedAddress(int32_t status,
struct addrinfo *res,
SCNetworkReachabilityRef target)
{
struct addrinfo *resP;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
if (targetPrivate->resolvedAddress != NULL) {
CFRelease(targetPrivate->resolvedAddress);
targetPrivate->resolvedAddress = NULL;
}
if ((status == 0) && (res != NULL)) {
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);
}
targetPrivate->resolvedAddress = addresses;
targetPrivate->resolvedAddressError = NETDB_SUCCESS;
} else {
SCLog(_sc_debug, LOG_INFO, CFSTR("getaddrinfo() failed: %s"), gai_strerror(status));
targetPrivate->resolvedAddress = CFRetain(kCFNull);
targetPrivate->resolvedAddressError = status;
}
if (res) freeaddrinfo(res);
if (targetPrivate->rls != NULL) {
SCLog(_sc_debug, LOG_INFO, CFSTR("DNS request completed"));
CFRunLoopSourceSignal(targetPrivate->rls);
_SC_signalRunLoop(target, targetPrivate->rls, targetPrivate->rlList);
}
return;
}
static void
__SCNetworkReachabilityCallbackSetResolvedAddress(int32_t status, struct addrinfo *res, void *context)
{
SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)context;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
__log_query_time(((status == 0) && (res != NULL)), TRUE, &targetPrivate->dnsQueryStart);
__SCNetworkReachabilitySetResolvedAddress(status, res, target);
return;
}
static int
rankReachability(SCNetworkConnectionFlags flags)
{
int rank = 0;
if (flags & kSCNetworkFlagsReachable) rank = 2;
if (flags & kSCNetworkFlagsConnectionRequired) rank = 1;
return rank;
}
static void
getaddrinfo_async_handleCFReply(CFMachPortRef port, void *msg, CFIndex size, void *info)
{
int32_t status;
SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)info;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
pthread_mutex_lock(&targetPrivate->lock);
status = getaddrinfo_async_handle_reply(msg);
if ((status == 0) &&
(targetPrivate->resolvedAddress == NULL) && (targetPrivate->resolvedAddressError == NETDB_SUCCESS)) {
goto again;
}
if (port == targetPrivate->dnsPort) {
CFRunLoopSourceInvalidate(targetPrivate->dnsRLS);
CFRelease(targetPrivate->dnsRLS);
targetPrivate->dnsRLS = NULL;
CFRelease(targetPrivate->dnsPort);
targetPrivate->dnsPort = NULL;
}
again :
pthread_mutex_unlock(&targetPrivate->lock);
return;
}
static Boolean
check_resolver_reachability(SCDynamicStoreRef *storeP,
dns_resolver_t *resolver,
SCNetworkConnectionFlags *flags,
Boolean *haveDNS)
{
int i;
Boolean ok = TRUE;
*flags = kSCNetworkFlagsReachable;
*haveDNS = FALSE;
for (i = 0; i < resolver->n_nameserver; i++) {
struct sockaddr *address = resolver->nameserver[i];
SCNetworkConnectionFlags ns_flags = 0;
*haveDNS = TRUE;
if (address->sa_family != AF_INET) {
continue;
}
ok = checkAddress(storeP, address, &ns_flags, NULL);
if (!ok) {
goto done;
}
if (rankReachability(ns_flags) < rankReachability(*flags)) {
*flags = ns_flags;
}
}
done :
return ok;
}
static Boolean
check_matching_resolvers(SCDynamicStoreRef *storeP,
dns_config_t *dns_config,
const char *fqdn,
SCNetworkConnectionFlags *flags,
Boolean *haveDNS)
{
int i;
Boolean matched = FALSE;
const char *name = fqdn;
while (!matched && (name != NULL)) {
int len;
len = strlen(name);
for (i = 0; i < dns_config->n_resolver; i++) {
char *domain;
dns_resolver_t *resolver;
resolver = dns_config->resolver[i];
domain = resolver->domain;
if (domain != NULL && (len == strlen(domain))) {
if (strcasecmp(name, domain) == 0) {
Boolean ok;
matched = TRUE;
ok = check_resolver_reachability(storeP, resolver, flags, haveDNS);
if (!ok) {
return FALSE;
}
}
}
}
if (!matched) {
name = strchr(name, '.');
if ((name != NULL) && (*name != '\0')) {
name++;
} else {
name = NULL;
}
}
}
return matched;
}
static dns_configuration_t *
dns_configuration_retain()
{
pthread_mutex_lock(&dns_lock);
if ((dns_configuration != NULL) && dns_token_valid) {
int check = 0;
uint32_t status;
status = notify_check(dns_token, &check);
if (status != NOTIFY_STATUS_OK) {
SCLog(TRUE, LOG_INFO, CFSTR("notify_check() failed, status=%lu"), status);
}
if ((status != NOTIFY_STATUS_OK) || (check != 0)) {
if (dns_configuration->refs == 0) {
dns_configuration_free(dns_configuration->config);
CFAllocatorDeallocate(NULL, dns_configuration);
}
dns_configuration = NULL;
}
}
if (dns_configuration == NULL) {
dns_config_t *new_config;
new_config = dns_configuration_copy();
if (new_config != NULL) {
dns_configuration = CFAllocatorAllocate(NULL, sizeof(dns_configuration_t), 0);
dns_configuration->config = new_config;
dns_configuration->refs = 0;
}
}
if (dns_configuration != NULL) {
dns_configuration->refs++;
}
pthread_mutex_unlock(&dns_lock);
return dns_configuration;
}
static void
dns_configuration_release(dns_configuration_t *config)
{
pthread_mutex_lock(&dns_lock);
config->refs--;
if (config->refs == 0) {
if ((dns_configuration != config)) {
dns_configuration_free(config->config);
CFAllocatorDeallocate(NULL, config);
}
}
pthread_mutex_unlock(&dns_lock);
return;
}
static Boolean
dns_configuration_watch()
{
int dns_check = 0;
const char *dns_key;
Boolean ok = FALSE;
uint32_t status;
pthread_mutex_lock(&dns_lock);
dns_key = dns_configuration_notify_key();
if (dns_key == NULL) {
SCLog(TRUE, LOG_INFO, CFSTR("dns_configuration_notify_key() failed"));
goto done;
}
status = notify_register_check(dns_key, &dns_token);
if (status == NOTIFY_STATUS_OK) {
dns_token_valid = TRUE;
} else {
SCLog(TRUE, LOG_INFO, CFSTR("notify_register_check() failed, status=%lu"), status);
goto done;
}
status = notify_check(dns_token, &dns_check);
if (status != NOTIFY_STATUS_OK) {
SCLog(TRUE, LOG_INFO, CFSTR("notify_check() failed, status=%lu"), status);
(void)notify_cancel(dns_token);
dns_token_valid = FALSE;
goto done;
}
ok = TRUE;
done :
pthread_mutex_unlock(&dns_lock);
return ok;
}
static void
dns_configuration_unwatch()
{
pthread_mutex_lock(&dns_lock);
(void)notify_cancel(dns_token);
dns_token_valid = FALSE;
if ((dns_configuration != NULL) && (dns_configuration->refs == 0)) {
dns_configuration_free(dns_configuration->config);
CFAllocatorDeallocate(NULL, dns_configuration);
dns_configuration = NULL;
}
pthread_mutex_unlock(&dns_lock);
return;
}
Boolean
_SC_checkResolverReachability(SCDynamicStoreRef *storeP,
SCNetworkConnectionFlags *flags,
Boolean *haveDNS,
const char * nodename)
{
dns_resolver_t *default_resolver;
dns_configuration_t *dns;
Boolean found = FALSE;
char *fqdn = (char *)nodename;
int i;
Boolean isFQDN = FALSE;
uint32_t len;
Boolean ok = TRUE;
*flags = kSCNetworkFlagsReachable;
*haveDNS = FALSE;
len = strlen(fqdn);
if (len == 0) {
*flags = 0;
return ok;
}
dns = dns_configuration_retain();
if (dns == NULL) {
goto done;
}
if (dns->config->n_resolver == 0) {
goto done;
}
*flags = kSCNetworkFlagsReachable;
if (fqdn[len - 1] == '.') {
isFQDN = TRUE;
while ((len > 0) && (fqdn[len-1] == '.')) {
if (fqdn == nodename) {
fqdn = strdup(nodename);
}
fqdn[--len] = '\0';
}
}
default_resolver = dns->config->resolver[0];
found = check_matching_resolvers(storeP, dns->config, fqdn, flags, haveDNS);
if (!found && !isFQDN && (dns->config->n_resolver > 1)) {
if (default_resolver->n_search > 0) {
for (i = 0; !found && (i < default_resolver->n_search); i++) {
int ret;
char *search_fqdn = NULL;
ret = asprintf(&search_fqdn, "%s.%s", fqdn, default_resolver->search[i]);
if (ret == -1) {
continue;
}
found = check_matching_resolvers(storeP, dns->config, search_fqdn, flags, haveDNS);
free(search_fqdn);
}
} else if (default_resolver->domain != NULL) {
char *dp;
int domain_parts = 0;
for (dp = default_resolver->domain; *dp != '\0'; dp++) {
if (*dp == '.') {
domain_parts++;
}
}
for (dp--; (dp >= default_resolver->domain) && (*dp == '.'); dp--) {
*dp = '\0';
domain_parts--;
}
if (dp >= default_resolver->domain) {
domain_parts++;
}
dp = default_resolver->domain;
for (i = LOCALDOMAINPARTS; !found && (i <= domain_parts); i++) {
int ret;
char *search_fqdn = NULL;
ret = asprintf(&search_fqdn, "%s.%s", fqdn, dp);
if (ret == -1) {
continue;
}
found = check_matching_resolvers(storeP, dns->config, search_fqdn, flags, haveDNS);
free(search_fqdn);
dp = strchr(dp, '.') + 1;
}
}
}
if (!found) {
ok = check_resolver_reachability(storeP, default_resolver, flags, haveDNS);
}
if (fqdn != nodename) free(fqdn);
done :
if (dns != NULL) {
dns_configuration_release(dns);
}
return ok;
}
static Boolean
startAsyncDNSQuery(SCNetworkReachabilityRef target) {
CFMachPortContext context = { 0, (void *)target, CFRetain, CFRelease, CFCopyDescription };
int error;
struct addrinfo hints;
CFIndex i;
CFIndex n;
mach_port_t port;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
(void) gettimeofday(&targetPrivate->dnsQueryStart, NULL);
bzero(&hints, sizeof(hints));
hints.ai_flags = AI_ADDRCONFIG;
#ifdef AI_PARALLEL
hints.ai_flags |= AI_PARALLEL;
#endif
error = getaddrinfo_async_start(&port,
targetPrivate->name,
NULL,
&hints,
__SCNetworkReachabilityCallbackSetResolvedAddress,
(void *)target);
if (error != 0) {
__SCNetworkReachabilityCallbackSetResolvedAddress(error, NULL, (void *)target);
return FALSE;
}
targetPrivate->dnsPort = CFMachPortCreateWithPort(NULL,
port,
getaddrinfo_async_handleCFReply,
&context,
NULL);
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 != NULL) {
*if_index = 0;
}
if (!isA_SCNetworkReachability(target)) {
_SCErrorSet(kSCStatusInvalidArgument);
return FALSE;
}
switch (targetPrivate->type) {
case reachabilityTypeAddress :
case reachabilityTypeAddressPair : {
if (targetPrivate->localAddress != NULL) {
ok = checkAddress(storeP, targetPrivate->localAddress, &my_flags, &my_index);
if (!ok) {
goto error;
}
if (!(my_flags & kSCNetworkFlagsIsLocalAddress)) {
goto error;
}
}
if (targetPrivate->remoteAddress != NULL) {
my_flags = 0;
my_index = 0;
ok = checkAddress(storeP, targetPrivate->remoteAddress, &my_flags, &my_index);
if (!ok) {
goto error;
}
}
break;
}
case reachabilityTypeName : {
struct timeval dnsQueryStart;
int error;
struct addrinfo hints;
SCNetworkConnectionFlags ns_flags;
struct addrinfo *res;
addresses = (CFMutableArrayRef)SCNetworkReachabilityCopyResolvedAddress(target, &error);
if ((addresses != NULL) || (error != NETDB_SUCCESS)) {
goto checkResolvedAddress;
}
ok = _SC_checkResolverReachability(storeP,
&ns_flags,
&targetPrivate->haveDNS,
targetPrivate->name);
if (!ok) {
goto error;
}
if (rankReachability(ns_flags) < 2) {
if (!checkAddress(storeP, NULL, &my_flags, &my_index)) {
goto error;
}
if (async && (targetPrivate->rls != NULL)) {
__SCNetworkReachabilityCallbackSetResolvedAddress(EAI_NODATA,
NULL,
(void *)target);
my_flags |= (targetPrivate->flags & kSCNetworkFlagsFirstResolvePending);
SCLog(_sc_debug, LOG_INFO, CFSTR("no DNS servers are reachable"));
CFRunLoopSourceSignal(targetPrivate->rls);
_SC_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);
if (_sc_debug) {
(void) gettimeofday(&dnsQueryStart, NULL);
}
bzero(&hints, sizeof(hints));
hints.ai_flags = AI_ADDRCONFIG;
#ifdef AI_PARALLEL
hints.ai_flags |= AI_PARALLEL;
#endif
error = getaddrinfo(targetPrivate->name, NULL, &hints, &res);
__log_query_time(((error == 0) && (res != NULL)), FALSE, &dnsQueryStart);
__SCNetworkReachabilitySetResolvedAddress(error, res, target);
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 == EAI_NODATA) && !targetPrivate->haveDNS) {
ok = checkAddress(storeP, NULL, &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 != NULL) {
*if_index = my_index;
}
error :
if (addresses != NULL) 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 != NULL) {
*flags = targetPrivate->flags & ~kSCNetworkFlagsFirstResolvePending;
return TRUE;
}
ok = __SCNetworkReachabilityGetFlags(&store, target, flags, NULL, FALSE);
*flags &= ~kSCNetworkFlagsFirstResolvePending;
if (store != NULL) CFRelease(store);
return ok;
}
static void
__SCNetworkReachabilityReachabilitySetNotifications(SCDynamicStoreRef store)
{
CFStringRef dns_key;
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);
dns_key = CFStringCreateWithCString(NULL,
dns_configuration_notify_key(),
kCFStringEncodingASCII);
key = CFStringCreateWithFormat(NULL, NULL, CFSTR("Notify:%@"), dns_key);
CFRelease(dns_key);
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 = 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 dnsConfigChanged = FALSE;
CFStringRef dnsKey;
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"));
dnsKey = CFStringCreateWithCString(NULL,
dns_configuration_notify_key(),
kCFStringEncodingASCII);
key = CFStringCreateWithFormat(NULL, NULL, CFSTR("Notify:%@"), dnsKey);
CFRelease(dnsKey);
if (CFArrayContainsValue(changedKeys,
CFRangeMake(0, CFArrayGetCount(changedKeys)),
key)) {
dnsConfigChanged = TRUE;
}
CFRelease(key);
SCLog(_sc_debug && dnsConfigChanged, LOG_INFO, CFSTR(" DNS configuration 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 (targetPrivate->type == reachabilityTypeName) {
Boolean dnsChanged = dnsConfigChanged;
if (!dnsChanged) {
SCNetworkConnectionFlags ns_flags;
Boolean ok;
ok = _SC_checkResolverReachability(&store,
&ns_flags,
&targetPrivate->haveDNS,
targetPrivate->name);
if (!ok || (rankReachability(ns_flags) < 2)) {
dnsChanged = TRUE;
}
}
if (dnsChanged) {
if (targetPrivate->dnsPort) {
lu_async_call_cancel(CFMachPortGetPort(targetPrivate->dnsPort));
CFRunLoopSourceInvalidate(targetPrivate->dnsRLS);
CFRelease(targetPrivate->dnsRLS);
targetPrivate->dnsRLS = NULL;
CFRelease(targetPrivate->dnsPort);
targetPrivate->dnsPort = NULL;
}
if (targetPrivate->resolvedAddress != NULL) {
CFRelease(targetPrivate->resolvedAddress);
targetPrivate->resolvedAddress = NULL;
}
targetPrivate->resolvedAddress = NULL;
targetPrivate->resolvedAddressError = NETDB_SUCCESS;
}
}
CFRunLoopSourceSignal(targetPrivate->rls);
_SC_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 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 != NULL) 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 (now %8.8x/%hu)"),
flags, if_index);
return;
} else {
SCLog(_sc_debug, LOG_DEBUG,
CFSTR("flags/interface have changed (was %8.8x/%hu, now %8.8x/%hu)"),
targetPrivate->flags, targetPrivate->if_index,
flags, if_index);
}
targetPrivate->flags = flags;
targetPrivate->if_index = if_index;
rlsFunction = targetPrivate->rlsFunction;
if (targetPrivate->rlsContext.retain != NULL) {
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 != NULL) {
(*rlsFunction)(target, flags, context_info);
}
if (context_release != NULL) {
(*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 != NULL) {
(*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 != NULL) {
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 == NULL) {
if (!dns_configuration_watch()) {
_SCErrorSet(kSCStatusFailed);
goto done;
}
hn_store = SCDynamicStoreCreate(NULL,
CFSTR("SCNetworkReachability"),
__SCNetworkReachabilityReachabilityHandleChanges,
NULL);
if (hn_store == NULL) {
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 == NULL) {
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 (!_SC_isScheduled(NULL, runLoop, runLoopMode, targetPrivate->rlList)) {
CFRunLoopAddSource(runLoop, targetPrivate->rls, runLoopMode);
if (targetPrivate->dnsRLS != NULL) {
CFRunLoopAddSource(runLoop, targetPrivate->dnsRLS, runLoopMode);
}
}
_SC_schedule(target, runLoop, runLoopMode, targetPrivate->rlList);
if (!_SC_isScheduled(NULL, runLoop, runLoopMode, hn_rlList)) {
CFRunLoopAddSource(runLoop, hn_storeRLS, runLoopMode);
}
_SC_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);
_SC_signalRunLoop(target, targetPrivate->rls, targetPrivate->rlList);
} else {
targetPrivate->flags = 0;
targetPrivate->if_index = 0;
}
if (store != NULL) 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 == NULL) {
goto done;
}
if (!_SC_unschedule(NULL, runLoop, runLoopMode, targetPrivate->rlList, FALSE)) {
goto done;
}
n = CFArrayGetCount(targetPrivate->rlList);
if (n == 0 || !_SC_isScheduled(NULL, runLoop, runLoopMode, targetPrivate->rlList)) {
CFRunLoopRemoveSource(runLoop, targetPrivate->rls, runLoopMode);
if (targetPrivate->dnsRLS != NULL) {
CFRunLoopRemoveSource(runLoop, targetPrivate->dnsRLS, runLoopMode);
}
if (n == 0) {
CFRunLoopSourceInvalidate(targetPrivate->rls);
CFRelease(targetPrivate->rls);
targetPrivate->rls = NULL;
CFRelease(targetPrivate->rlList);
targetPrivate->rlList = NULL;
CFSetRemoveValue(hn_targets, target);
if (targetPrivate->dnsPort) {
lu_async_call_cancel(CFMachPortGetPort(targetPrivate->dnsPort));
CFRunLoopSourceInvalidate(targetPrivate->dnsRLS);
CFRelease(targetPrivate->dnsRLS);
targetPrivate->dnsRLS = NULL;
CFRelease(targetPrivate->dnsPort);
targetPrivate->dnsPort = NULL;
}
}
}
(void)_SC_unschedule(target, runLoop, runLoopMode, hn_rlList, FALSE);
n = CFArrayGetCount(hn_rlList);
if (n == 0 || !_SC_isScheduled(NULL, runLoop, runLoopMode, hn_rlList)) {
CFRunLoopRemoveSource(runLoop, hn_storeRLS, runLoopMode);
if (n == 0) {
CFRelease(hn_targets);
hn_targets = NULL;
CFRelease(hn_rlList);
hn_rlList = NULL;
CFRunLoopSourceInvalidate(hn_storeRLS);
CFRelease(hn_storeRLS);
hn_storeRLS = NULL;
CFRelease(hn_store);
hn_store = NULL;
dns_configuration_unwatch();
}
}
ok = TRUE;
done :
pthread_mutex_unlock(&targetPrivate->lock);
pthread_mutex_unlock(&hn_lock);
return ok;
}