/* * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Modification History * * January 19, 2003 Allan Nathanson * - add advanced reachability APIs */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define KERNEL_PRIVATE #include #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 { /* base CFType information */ CFRuntimeBase cfBase; /* lock */ pthread_mutex_t lock; /* address type */ addressType type; /* target host name */ const char *name; CFArrayRef resolvedAddress; /* CFArray[CFData] */ int resolvedAddressError; /* local & remote addresses */ struct sockaddr *localAddress; struct sockaddr *remoteAddress; /* current reachability flags */ SCNetworkConnectionFlags flags; uint16_t if_index; /* run loop source, callout, context, rl scheduling info */ CFRunLoopSourceRef rls; SCNetworkReachabilityCallBack rlsFunction; SCNetworkReachabilityContext rlsContext; CFMutableArrayRef rlList; /* async DNS query info */ CFMachPortRef dnsPort; CFRunLoopSourceRef dnsRLS; } SCNetworkReachabilityPrivate, *SCNetworkReachabilityPrivateRef; static CFTypeID __kSCNetworkReachabilityTypeID = _kCFRuntimeNotATypeID; static const CFRuntimeClass __SCNetworkReachabilityClass = { 0, // version "SCNetworkReachability", // className NULL, // init NULL, // copy __SCNetworkReachabilityDeallocate, // dealloc NULL, // equal NULL, // hash NULL, // copyFormattingDesc __SCNetworkReachabilityCopyDescription // copyDebugDesc }; static pthread_once_t initialized = PTHREAD_ONCE_INIT; static Boolean needDNS = TRUE; /* host "something has changed" notifications */ 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 /* CHECK_IPV6_REACHABILITY */ 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; } /* get first runLoop for this object */ for (i = 0; i < n; i += 3) { if (!CFEqual(obj, CFArrayGetValueAtIndex(rlList, i))) { continue; } rl1 = (CFRunLoopRef)CFArrayGetValueAtIndex(rlList, i+1); break; } if (!rl1) { /* if not scheduled */ return; } /* check if we have another runLoop for this object */ 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)) { /* we've got more than one runLoop */ rl = NULL; break; } } if (rl) { /* if we only have one runLoop */ CFRunLoopWakeUp(rl); return; } /* more than one different runLoop, so we must pick one */ 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)) { /* we've found a runLoop that's "ready" */ CFRelease(rlMode); CFRunLoopWakeUp(rl); return; } if (rlMode) CFRelease(rlMode); } /* didn't choose one above, so choose first */ 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; } } /* * grab a snapshot of the PPP configuration from the dynamic store */ { 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) { /* if we could not access the dynamic store */ goto done; } sc_status = kSCStatusOK; /* * look for the service which matches the provided interface */ 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; // if not an IPv4 or IPv6 entity } s_if = CFDictionaryGetValue(s_dict, kSCPropInterfaceName); if (!isA_CFString(s_if)) { continue; // if no interface } if (!CFEqual(ppp_if, s_if)) { continue; // if not this interface } /* * extract service ID, get PPP "state" entity (for status), and get * the "setup" entity (for dial-on-traffic flag) */ 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); // get PPP status 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 : /* if we're really UP and RUNNING */ break; case PPP_ONHOLD : /* if we're effectively UP and RUNNING */ break; case PPP_IDLE : case PPP_STATERESERVED : /* if we're not connected at all */ SCLog(_sc_debug, LOG_INFO, CFSTR(" PPP link idle, dial-on-traffic to connect")); *flags |= kSCNetworkFlagsConnectionRequired; break; default : /* if we're in the process of [dis]connecting */ SCLog(_sc_debug, LOG_INFO, CFSTR(" PPP link, connection in progress")); *flags |= kSCNetworkFlagsConnectionRequired; break; } // check PPP dial-on-traffic status 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; } } /* * grab a snapshot of the PPP configuration from the dynamic store */ { 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) { /* if we could not access the dynamic store */ goto done; } sc_status = kSCStatusOK; /* * look for an available service which will provide connectivity * for the requested address family. */ 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; // if not an IPv4 or IPv6 entity } // extract service ID components = CFStringCreateArrayBySeparatingStrings(NULL, s_key, CFSTR("/")); if (CFArrayGetCount(components) != 5) { continue; } service = CFArrayGetValueAtIndex(components, 3); // check for PPP entity p_key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, service, kSCEntNetPPP); p_dict = CFDictionaryGetValue(dict, p_key); CFRelease(p_key); if (isA_CFDictionary(p_dict)) { CFNumberRef num; /* * we have a PPP service for this address family */ found = TRUE; *flags |= kSCNetworkFlagsReachable; *flags |= kSCNetworkFlagsTransientConnection; *flags |= kSCNetworkFlagsConnectionRequired; /* * get PPP dial-on-traffic status */ 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) /* 8 * sizeof(struct sockaddr_in6) = 192 */ 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 : /* * if no code for this address family (yet) */ 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)) { /* special case: check for available paths off the system */ 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; /* Both destination and device */ 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; } /* * Type, seq, pid identify our response. * Routing sockets are broadcasters on input. */ 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 /* DEBUG */ if ((rti_info[RTAX_IFP] == NULL) || (rti_info[RTAX_IFP]->sa_family != AF_LINK)) { /* no interface info */ goto done; // ??? } sdl = (struct sockaddr_dl *) rti_info[RTAX_IFP]; if ((sdl->sdl_nlen == 0) || (sdl->sdl_nlen > IFNAMSIZ)) { /* no interface name */ goto done; // ??? } /* get the interface flags */ bzero(&ifr, sizeof(ifr)); bcopy(sdl->sdl_data, ifr.ifr_name, sdl->sdl_len); 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_len <= IFNAMSIZ) ? sdl->sdl_len : 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) { /* * We have an interface which "claims" to be a valid path * off of the system. */ *flags |= kSCNetworkFlagsTransientConnection; /* * Check if this is a dial-on-demand PPP link that isn't * connected yet. */ 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) { /* * Check if 0.0.0.0 */ 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(" { "), 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 /* CHECK_IPV6_REACHABILITY */ CFStringAppendFormat(result, NULL, CFSTR(" (%s)"), __netdb_error(targetPrivate->resolvedAddressError)); #endif /* CHECK_IPV6_REACHABILITY */ } } 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:")); /* release resources */ 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; /* initialize runtime */ pthread_once(&initialized, __SCNetworkReachabilityInitialize); SCLog(_sc_verbose, LOG_DEBUG, CFSTR("__SCNetworkReachabilityCreatePrivate:")); /* allocate target */ 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); /* initialize runtime */ 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 { /* if status is known but no resolved addresses to return */ _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); } /* save the resolved address[es] */ __SCNetworkReachabilitySetResolvedAddress(target, addresses, NETDB_SUCCESS); CFRelease(addresses); } else { SCLog(_sc_debug, LOG_INFO, CFSTR("getaddrinfo() failed: %s"), gai_strerror(status)); /* save the error associated with the attempt to resolve the name */ __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 /* CHECK_IPV6_REACHABILITY */ 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++; } /* save the resolved address[es] */ __SCNetworkReachabilitySetResolvedAddress(target, addresses, NETDB_SUCCESS); CFRelease(addresses); } else { SCLog(_sc_debug, LOG_INFO, CFSTR("getipnodebyname() failed: %s"), __netdb_error(error)); /* save the error associated with the attempt to resolve the name */ __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 /* CHECK_IPV6_REACHABILITY */ /* * rankReachability() * Not reachable == 0 * Connection Required == 1 * Reachable == 2 */ 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 /* CHECK_IPV6_REACHABILITY */ 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 /* CHECK_IPV6_REACHABILITY */ static Boolean checkResolverReachability(SCDynamicStoreRef *storeP, SCNetworkConnectionFlags *flags, Boolean *haveDNS) { int i; Boolean ok = TRUE; /* * We first assume that all of the configured DNS servers * are available. Since we don't know which name server will * be consulted to resolve the specified nodename we need to * check the availability of ALL name servers. We can only * proceed if we know that our query can be answered. */ *flags = kSCNetworkFlagsReachable; *haveDNS = FALSE; if (needDNS) { if (hn_store) { /* if we are actively watching at least one host */ 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) { /* not today */ break; } if (rankReachability(ns_flags) < rankReachability(*flags)) { /* return the worst case result */ *flags = ns_flags; } } if (!*haveDNS) { /* if no DNS server addresses */ *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 /* CHECK_IPV6_REACHABILITY */ 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) { /* save the error associated with the attempt to resolve the name */ __SCNetworkReachabilitySetResolvedAddress(target, (CFArrayRef)kCFNull, error); return FALSE; } targetPrivate->dnsPort = CFMachPortCreateWithPort(NULL, port, getaddrinfo_async_handleCFReply, &context, NULL); #else /* CHECK_IPV6_REACHABILITY */ port = getipnodebyname_async_start(targetPrivate->name, AF_INET, 0, &error, __SCNetworkReachabilityCallbackSetResolvedAddress, (void *)target); if (port == MACH_PORT_NULL) { /* save the error associated with the attempt to resolve the name */ __SCNetworkReachabilitySetResolvedAddress(target, (CFArrayRef)kCFNull, error); return FALSE; } targetPrivate->dnsPort = CFMachPortCreateWithPort(NULL, port, getipnodebyname_async_handleCFReply, &context, NULL); #endif /* CHECK_IPV6_REACHABILITY */ 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 : { /* * Check "local" address */ if (targetPrivate->localAddress) { /* * Check if 0.0.0.0 */ if (isAddressZero(targetPrivate->localAddress, &my_flags)) { goto checkRemote; } /* * Check "local" address */ ok = checkAddress(storeP, targetPrivate->localAddress, &my_flags, &my_index); if (!ok) { goto error; /* not today */ } if (!(my_flags & kSCNetworkFlagsIsLocalAddress)) { goto error; /* not reachable, non-"local" address */ } } checkRemote : /* * Check "remote" address */ if (targetPrivate->remoteAddress) { /* * in cases where we have "local" and "remote" addresses * we need to re-initialize the to-be-returned flags. */ my_flags = 0; my_index = 0; /* * Check if 0.0.0.0 */ if (isAddressZero(targetPrivate->remoteAddress, &my_flags)) { break; } /* * Check "remote" address */ ok = checkAddress(storeP, targetPrivate->remoteAddress, &my_flags, &my_index); if (!ok) { goto error; /* not today */ } } break; } case reachabilityTypeName : { int error; #ifndef CHECK_IPV6_REACHABILITY struct hostent *h; #endif /* CHECK_IPV6_REACHABILITY */ Boolean haveDNS = FALSE; #ifdef CHECK_IPV6_REACHABILITY struct addrinfo hints; #endif /* CHECK_IPV6_REACHABILITY */ SCNetworkConnectionFlags ns_flags; #ifdef CHECK_IPV6_REACHABILITY struct addrinfo *res; #endif /* CHECK_IPV6_REACHABILITY */ addresses = (CFMutableArrayRef)SCNetworkReachabilityCopyResolvedAddress(target, &error); if (addresses || (error != NETDB_SUCCESS)) { /* if resolved or an error had been detected */ goto checkResolvedAddress; } /* check the reachability of the DNS servers */ ok = checkResolverReachability(storeP, &ns_flags, &haveDNS);\ if (!ok) { /* if we could not get DNS server info */ goto error; } if (rankReachability(ns_flags) < 2) { /* * if DNS servers are not (or are no longer) reachable, set * flags based on the availability of configured (but not * active) services. */ if (!checkAddressZero(storeP, &my_flags, &my_index)) { goto error; } if (async && targetPrivate->rls) { /* * return HOST_NOT_FOUND, set flags appropriately, * and schedule notification. */ #ifdef CHECK_IPV6_REACHABILITY __SCNetworkReachabilityCallbackSetResolvedAddress(EAI_NODATA, NULL, (void *)target); #else /* CHECK_IPV6_REACHABILITY */ __SCNetworkReachabilityCallbackSetResolvedAddress(NULL, HOST_NOT_FOUND, (void *)target); #endif /* CHECK_IPV6_REACHABILITY */ 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) { /* for async requests we return the last known status */ my_flags = targetPrivate->flags; my_index = targetPrivate->if_index; if (targetPrivate->dnsPort) { /* if request already in progress */ break; } SCLog(_sc_debug, LOG_INFO, CFSTR("start DNS query for \"%s\""), targetPrivate->name); /* * initiate an async DNS query */ if (!startAsyncDNSQuery(target)) { /* if we could not initiate the request, process error */ goto checkResolvedAddress; } /* if request initiated */ break; } SCLog(_sc_debug, LOG_INFO, CFSTR("check DNS for \"%s\""), targetPrivate->name); /* * OK, all of the DNS name servers are available. Let's * resolve the nodename into an address. */ #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 /* CHECK_IPV6_REACHABILITY */ h = getipnodebyname(targetPrivate->name, AF_INET, 0, &error); __SCNetworkReachabilityCallbackSetResolvedAddress(h, error, (void *)target); #endif /* CHECK_IPV6_REACHABILITY */ addresses = (CFMutableArrayRef)SCNetworkReachabilityCopyResolvedAddress(target, &error); checkResolvedAddress : /* * We first assume that the requested host is NOT available. * Then, check each address for accessibility and return the * best status available. */ 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; /* not today */ } if (rankReachability(ns_flags) > rankReachability(my_flags)) { /* return the best case result */ my_flags = ns_flags; my_index = ns_if_index; if (rankReachability(my_flags) == 2) { /* we're in luck */ break; } } } } else { if ((error == HOST_NOT_FOUND) && !haveDNS) { /* * No DNS servers are defined. Set flags based on * the availability of configured (but not active) * services. */ ok = checkAddressZero(storeP, &my_flags, &my_index); if (!ok) { goto error; /* not today */ } if ((my_flags & kSCNetworkFlagsReachable) && (my_flags & kSCNetworkFlagsConnectionRequired)) { /* * Since we might pick up a set of DNS servers when this connection * is established, don't reply with a "HOST NOT FOUND" error just yet. */ break; } /* Host not found, not reachable! */ 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) { /* if being watched, return current (OK, last known) status */ *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); /* * Setup:/Network/Global/IPv4 (for the ServiceOrder) */ key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainSetup, kSCEntNetIPv4); CFArrayAppendValue(keys, key); CFRelease(key); /* * State:/Network/Global/DNS */ key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetDNS); CFArrayAppendValue(keys, key); CFRelease(key); /* * State:/Network/Global/IPv4 */ key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4); CFArrayAppendValue(keys, key); CFRelease(key); /* Setup: per-service IPv4 info */ pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetIPv4); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); /* Setup: per-service Interface info */ pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetInterface); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); /* Setup: per-service PPP info */ pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetPPP); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); /* State: per-service IPv4 info */ pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); /* State: per-interface IPv4 info */ pattern = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); /* State: per-interface IPv6 info */ 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) { /* if no addresses being monitored */ goto done; } if (CFArrayGetCount(changedKeys) == 0) { /* if no changes */ 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; /* the DNS server(s) have changed */ needDNS = TRUE; /* ... and we need to res_init() on the next query */ } CFRelease(key); if (!dnsChanged) { /* * if the DNS configuration didn't change we still need to * check that the DNS servers are accessible. */ Boolean haveDNS = FALSE; SCNetworkConnectionFlags ns_flags; Boolean ok; /* check the reachability of the DNS servers */ ok = checkResolverReachability(&store, &ns_flags, &haveDNS);\ if (!ok || (rankReachability(ns_flags) < 2)) { /* if DNS servers are not reachable */ 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) { /* cancel the outstanding DNS query */ #ifdef CHECK_IPV6_REACHABILITY lu_async_call_cancel(CFMachPortGetPort(targetPrivate->dnsPort)); #else /* CHECK_IPV6_REACHABILITY */ getipnodebyname_async_cancel(CFMachPortGetPort(targetPrivate->dnsPort)); #endif /* CHECK_IPV6_REACHABILITY */ CFRelease(targetPrivate->dnsRLS); targetPrivate->dnsRLS = NULL; CFRelease(targetPrivate->dnsPort); targetPrivate->dnsPort = NULL; } /* schedule request to resolve the name again */ __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); /* update reachability, notify if status changed */ ok = __SCNetworkReachabilityGetFlags(&store, target, &flags, &if_index, TRUE); if (store) CFRelease(store); if (!ok) { /* if reachability status not available */ flags = 0; if_index = 0; } if ((targetPrivate->flags == flags) && (targetPrivate->if_index == if_index)) { /* if reachability flags and interface have not changed */ pthread_mutex_unlock(&targetPrivate->lock); SCLog(_sc_debug, LOG_DEBUG, CFSTR("flags/interface match")); return; } /* update flags / interface */ targetPrivate->flags = flags; targetPrivate->if_index = if_index; /* callout */ 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) { /* let go of the current context */ 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; } /* schedule the SCNetworkReachability run loop source */ pthread_mutex_lock(&hn_lock); pthread_mutex_lock(&targetPrivate->lock); if (!hn_store) { /* * if we are not monitoring any hosts */ 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 // version , (void *)target // info , CFRetain // retain , CFRelease // release , CFCopyDescription // copyDescription , CFEqual // equal , CFHash // hash , NULL // schedule , NULL // cancel , rlsPerform // perform }; targetPrivate->rls = CFRunLoopSourceCreate(NULL, 0, &context); targetPrivate->rlList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); init = TRUE; } if (!__isScheduled(NULL, runLoop, runLoopMode, targetPrivate->rlList)) { /* * if we do not already have host notifications scheduled with * this runLoop / runLoopMode */ CFRunLoopAddSource(runLoop, targetPrivate->rls, runLoopMode); if (targetPrivate->dnsRLS) { /* if we have an active async DNS query too */ CFRunLoopAddSource(runLoop, targetPrivate->dnsRLS, runLoopMode); } } __schedule(target, runLoop, runLoopMode, targetPrivate->rlList); /* schedule the SCNetworkReachability run loop source */ if (!__isScheduled(NULL, runLoop, runLoopMode, hn_rlList)) { /* * if we do not already have SC notifications scheduled with * this runLoop / runLoopMode */ 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 we have yet to schedule SC notifications for this address * - initialize current reachability status */ if (__SCNetworkReachabilityGetFlags(&store, target, &flags, &if_index, TRUE)) { /* * if reachability status available * - set flags * - schedule notification to report status via callback */ targetPrivate->flags = flags; targetPrivate->if_index = if_index; CFRunLoopSourceSignal(targetPrivate->rls); __signalRunLoop(target, targetPrivate->rls, targetPrivate->rlList); } else { /* if reachability status not available, async lookup started */ 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) { /* if not currently scheduled */ goto done; } if (!__unschedule(NULL, runLoop, runLoopMode, targetPrivate->rlList, FALSE)) { /* if not currently scheduled */ goto done; } n = CFArrayGetCount(targetPrivate->rlList); if (n == 0 || !__isScheduled(NULL, runLoop, runLoopMode, targetPrivate->rlList)) { /* * if this host is no longer scheduled for this runLoop / runLoopMode */ CFRunLoopRemoveSource(runLoop, targetPrivate->rls, runLoopMode); if (targetPrivate->dnsRLS) { /* if we have an active async DNS query too */ CFRunLoopRemoveSource(runLoop, targetPrivate->dnsRLS, runLoopMode); } if (n == 0) { /* * if this host is no longer scheduled */ CFRelease(targetPrivate->rls); /* cleanup SCNetworkReachability resources */ targetPrivate->rls = NULL; CFRelease(targetPrivate->rlList); targetPrivate->rlList = NULL; CFSetRemoveValue(hn_targets, target); /* cleanup notification resources */ if (targetPrivate->dnsPort) { /* if we have an active async DNS query too */ #ifdef CHECK_IPV6_REACHABILITY lu_async_call_cancel(CFMachPortGetPort(targetPrivate->dnsPort)); #else /* CHECK_IPV6_REACHABILITY */ getipnodebyname_async_cancel(CFMachPortGetPort(targetPrivate->dnsPort)); #endif /* CHECK_IPV6_REACHABILITY */ 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)) { /* * if we no longer have any addresses scheduled for * this runLoop / runLoopMode */ CFRunLoopRemoveSource(runLoop, hn_storeRLS, runLoopMode); if (n == 0) { /* * if we are no longer monitoring any addresses */ CFRelease(hn_targets); CFRelease(hn_rlList); CFRelease(hn_storeRLS); CFRelease(hn_store); hn_store = NULL; /* * until we start monitoring again, ensure that * all subsequent reachability-by-name checks * call res_init() */ needDNS = TRUE; } } ok = TRUE; done : pthread_mutex_unlock(&targetPrivate->lock); pthread_mutex_unlock(&hn_lock); return ok; }