| /* |
| Copyright (c) 2011, Tony Million. |
| All rights reserved. |
| |
| Redistribution and use in source and binary forms, with or without |
| modification, are permitted provided that the following conditions are met: |
| |
| 1. Redistributions of source code must retain the above copyright notice, this |
| list of conditions and the following disclaimer. |
| |
| 2. Redistributions in binary form must reproduce the above copyright notice, |
| this list of conditions and the following disclaimer in the documentation |
| and/or other materials provided with the distribution. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "Reachability.h" |
| |
| #import <sys/socket.h> |
| #import <netinet/in.h> |
| #import <netinet6/in6.h> |
| #import <arpa/inet.h> |
| #import <ifaddrs.h> |
| #import <netdb.h> |
| |
| |
| NSString *const kReachabilityChangedNotification = @"kReachabilityChangedNotification"; |
| |
| |
| @interface Reachability () |
| |
| @property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef; |
| @property (nonatomic, strong) dispatch_queue_t reachabilitySerialQueue; |
| @property (nonatomic, strong) id reachabilityObject; |
| |
| -(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags; |
| -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; |
| |
| @end |
| |
| |
| static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags) |
| { |
| return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c", |
| #if TARGET_OS_IPHONE |
| (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', |
| #else |
| 'X', |
| #endif |
| (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', |
| (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', |
| (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', |
| (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', |
| (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', |
| (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', |
| (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', |
| (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; |
| } |
| |
| // Start listening for reachability notifications on the current run loop |
| static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) |
| { |
| #pragma unused (target) |
| |
| Reachability *reachability = ((__bridge Reachability*)info); |
| |
| // We probably don't need an autoreleasepool here, as GCD docs state each queue has its own autorelease pool, |
| // but what the heck eh? |
| @autoreleasepool |
| { |
| [reachability reachabilityChanged:flags]; |
| } |
| } |
| |
| |
| @implementation Reachability |
| |
| #pragma mark - Class Constructor Methods |
| |
| +(Reachability*)reachabilityWithHostName:(NSString*)hostname |
| { |
| return [Reachability reachabilityWithHostname:hostname]; |
| } |
| |
| +(Reachability*)reachabilityWithHostname:(NSString*)hostname |
| { |
| SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]); |
| if (ref) |
| { |
| id reachability = [[self alloc] initWithReachabilityRef:ref]; |
| |
| return reachability; |
| } |
| |
| return nil; |
| } |
| |
| +(Reachability *)reachabilityWithAddress:(void *)hostAddress |
| { |
| SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress); |
| if (ref) |
| { |
| id reachability = [[self alloc] initWithReachabilityRef:ref]; |
| |
| return reachability; |
| } |
| |
| return nil; |
| } |
| |
| +(Reachability *)reachabilityForInternetConnection |
| { |
| struct sockaddr_in zeroAddress; |
| bzero(&zeroAddress, sizeof(zeroAddress)); |
| zeroAddress.sin_len = sizeof(zeroAddress); |
| zeroAddress.sin_family = AF_INET; |
| |
| return [self reachabilityWithAddress:&zeroAddress]; |
| } |
| |
| +(Reachability*)reachabilityForLocalWiFi |
| { |
| struct sockaddr_in localWifiAddress; |
| bzero(&localWifiAddress, sizeof(localWifiAddress)); |
| localWifiAddress.sin_len = sizeof(localWifiAddress); |
| localWifiAddress.sin_family = AF_INET; |
| // IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0 |
| localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); |
| |
| return [self reachabilityWithAddress:&localWifiAddress]; |
| } |
| |
| |
| // Initialization methods |
| |
| -(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref |
| { |
| self = [super init]; |
| if (self != nil) |
| { |
| self.reachableOnWWAN = YES; |
| self.reachabilityRef = ref; |
| |
| // We need to create a serial queue. |
| // We allocate this once for the lifetime of the notifier. |
| |
| self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL); |
| } |
| |
| return self; |
| } |
| |
| -(void)dealloc |
| { |
| [self stopNotifier]; |
| |
| if(self.reachabilityRef) |
| { |
| CFRelease(self.reachabilityRef); |
| self.reachabilityRef = nil; |
| } |
| |
| self.reachableBlock = nil; |
| self.unreachableBlock = nil; |
| self.reachabilitySerialQueue = nil; |
| } |
| |
| #pragma mark - Notifier Methods |
| |
| // Notifier |
| // NOTE: This uses GCD to trigger the blocks - they *WILL NOT* be called on THE MAIN THREAD |
| // - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS. |
| // INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you want) |
| |
| -(BOOL)startNotifier |
| { |
| // allow start notifier to be called multiple times |
| if(self.reachabilityObject && (self.reachabilityObject == self)) |
| { |
| return YES; |
| } |
| |
| |
| SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL }; |
| context.info = (__bridge void *)self; |
| |
| if(SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context)) |
| { |
| // Set it as our reachability queue, which will retain the queue |
| if(SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue)) |
| { |
| // this should do a retain on ourself, so as long as we're in notifier mode we shouldn't disappear out from under ourselves |
| // woah |
| self.reachabilityObject = self; |
| return YES; |
| } |
| else |
| { |
| #ifdef DEBUG |
| NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError())); |
| #endif |
| |
| // UH OH - FAILURE - stop any callbacks! |
| SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); |
| } |
| } |
| else |
| { |
| #ifdef DEBUG |
| NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError())); |
| #endif |
| } |
| |
| // if we get here we fail at the internet |
| self.reachabilityObject = nil; |
| return NO; |
| } |
| |
| -(void)stopNotifier |
| { |
| // First stop, any callbacks! |
| SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); |
| |
| // Unregister target from the GCD serial dispatch queue. |
| SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL); |
| |
| self.reachabilityObject = nil; |
| } |
| |
| #pragma mark - reachability tests |
| |
| // This is for the case where you flick the airplane mode; |
| // you end up getting something like this: |
| //Reachability: WR ct----- |
| //Reachability: -- ------- |
| //Reachability: WR ct----- |
| //Reachability: -- ------- |
| // We treat this as 4 UNREACHABLE triggers - really apple should do better than this |
| |
| #define testcase (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection) |
| |
| -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags |
| { |
| BOOL connectionUP = YES; |
| |
| if(!(flags & kSCNetworkReachabilityFlagsReachable)) |
| connectionUP = NO; |
| |
| if( (flags & testcase) == testcase ) |
| connectionUP = NO; |
| |
| #if TARGET_OS_IPHONE |
| if(flags & kSCNetworkReachabilityFlagsIsWWAN) |
| { |
| // We're on 3G. |
| if(!self.reachableOnWWAN) |
| { |
| // We don't want to connect when on 3G. |
| connectionUP = NO; |
| } |
| } |
| #endif |
| |
| return connectionUP; |
| } |
| |
| -(BOOL)isReachable |
| { |
| SCNetworkReachabilityFlags flags; |
| |
| if(!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) |
| return NO; |
| |
| return [self isReachableWithFlags:flags]; |
| } |
| |
| -(BOOL)isReachableViaWWAN |
| { |
| #if TARGET_OS_IPHONE |
| |
| SCNetworkReachabilityFlags flags = 0; |
| |
| if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) |
| { |
| // Check we're REACHABLE |
| if(flags & kSCNetworkReachabilityFlagsReachable) |
| { |
| // Now, check we're on WWAN |
| if(flags & kSCNetworkReachabilityFlagsIsWWAN) |
| { |
| return YES; |
| } |
| } |
| } |
| #endif |
| |
| return NO; |
| } |
| |
| -(BOOL)isReachableViaWiFi |
| { |
| SCNetworkReachabilityFlags flags = 0; |
| |
| if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) |
| { |
| // Check we're reachable |
| if((flags & kSCNetworkReachabilityFlagsReachable)) |
| { |
| #if TARGET_OS_IPHONE |
| // Check we're NOT on WWAN |
| if((flags & kSCNetworkReachabilityFlagsIsWWAN)) |
| { |
| return NO; |
| } |
| #endif |
| return YES; |
| } |
| } |
| |
| return NO; |
| } |
| |
| |
| // WWAN may be available, but not active until a connection has been established. |
| // WiFi may require a connection for VPN on Demand. |
| -(BOOL)isConnectionRequired |
| { |
| return [self connectionRequired]; |
| } |
| |
| -(BOOL)connectionRequired |
| { |
| SCNetworkReachabilityFlags flags; |
| |
| if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) |
| { |
| return (flags & kSCNetworkReachabilityFlagsConnectionRequired); |
| } |
| |
| return NO; |
| } |
| |
| // Dynamic, on demand connection? |
| -(BOOL)isConnectionOnDemand |
| { |
| SCNetworkReachabilityFlags flags; |
| |
| if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) |
| { |
| return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && |
| (flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand))); |
| } |
| |
| return NO; |
| } |
| |
| // Is user intervention required? |
| -(BOOL)isInterventionRequired |
| { |
| SCNetworkReachabilityFlags flags; |
| |
| if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) |
| { |
| return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && |
| (flags & kSCNetworkReachabilityFlagsInterventionRequired)); |
| } |
| |
| return NO; |
| } |
| |
| |
| #pragma mark - reachability status stuff |
| |
| -(NetworkStatus)currentReachabilityStatus |
| { |
| if([self isReachable]) |
| { |
| if([self isReachableViaWiFi]) |
| return ReachableViaWiFi; |
| |
| #if TARGET_OS_IPHONE |
| return ReachableViaWWAN; |
| #endif |
| } |
| |
| return NotReachable; |
| } |
| |
| -(SCNetworkReachabilityFlags)reachabilityFlags |
| { |
| SCNetworkReachabilityFlags flags = 0; |
| |
| if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) |
| { |
| return flags; |
| } |
| |
| return 0; |
| } |
| |
| -(NSString*)currentReachabilityString |
| { |
| NetworkStatus temp = [self currentReachabilityStatus]; |
| |
| if(temp == ReachableViaWWAN) |
| { |
| // Updated for the fact that we have CDMA phones now! |
| return NSLocalizedString(@"Cellular", @""); |
| } |
| if (temp == ReachableViaWiFi) |
| { |
| return NSLocalizedString(@"WiFi", @""); |
| } |
| |
| return NSLocalizedString(@"No Connection", @""); |
| } |
| |
| -(NSString*)currentReachabilityFlags |
| { |
| return reachabilityFlags([self reachabilityFlags]); |
| } |
| |
| #pragma mark - Callback function calls this method |
| |
| -(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags |
| { |
| if([self isReachableWithFlags:flags]) |
| { |
| if(self.reachableBlock) |
| { |
| self.reachableBlock(self); |
| } |
| } |
| else |
| { |
| if(self.unreachableBlock) |
| { |
| self.unreachableBlock(self); |
| } |
| } |
| |
| // this makes sure the change notification happens on the MAIN THREAD |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| [[NSNotificationCenter defaultCenter] postNotificationName:kReachabilityChangedNotification |
| object:self]; |
| }); |
| } |
| |
| #pragma mark - Debug Description |
| |
| - (NSString *) description |
| { |
| NSString *description = [NSString stringWithFormat:@"<%@: %#x (%@)>", |
| NSStringFromClass([self class]), (unsigned int) self, [self currentReachabilityFlags]]; |
| return description; |
| } |
| |
| @end |