123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508 |
- /*
- 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
- +(instancetype)reachabilityWithHostName:(NSString*)hostname
- {
- return [Reachability reachabilityWithHostname:hostname];
- }
- +(instancetype)reachabilityWithHostname:(NSString*)hostname
- {
- SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]);
- if (ref)
- {
- id reachability = [[self alloc] initWithReachabilityRef:ref];
- return reachability;
- }
-
- return nil;
- }
- +(instancetype)reachabilityWithAddress:(void *)hostAddress
- {
- SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
- if (ref)
- {
- id reachability = [[self alloc] initWithReachabilityRef:ref];
-
- return reachability;
- }
-
- return nil;
- }
- +(instancetype)reachabilityForInternetConnection
- {
- struct sockaddr_in zeroAddress;
- bzero(&zeroAddress, sizeof(zeroAddress));
- zeroAddress.sin_len = sizeof(zeroAddress);
- zeroAddress.sin_family = AF_INET;
-
- return [self reachabilityWithAddress:&zeroAddress];
- }
- +(instancetype)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];
- }
- +(instancetype)reachabilityWithURL:(NSURL*)url
- {
- id reachability;
- NSString *host = url.host;
- BOOL isIpAddress = [self isIpAddress:host];
- if (isIpAddress)
- {
- NSNumber *port = url.port ?: [url.scheme isEqualToString:@"https"] ? @(443) : @(80);
- struct sockaddr_in address;
- address.sin_len = sizeof(address);
- address.sin_family = AF_INET;
- address.sin_port = htons([port intValue]);
- address.sin_addr.s_addr = inet_addr([host UTF8String]);
- reachability = [self reachabilityWithAddress:&address];
- }
- else
- {
- reachability = [self reachabilityWithHostname:host];
- }
- return reachability;
- }
- +(BOOL)isIpAddress:(NSString*)host
- {
- struct in_addr pin;
- return 1 == inet_aton([host UTF8String], &pin);
- }
- // Initialization methods
- -(instancetype)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.reachabilityBlock = 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);
- }
- }
-
- if(self.reachabilityBlock)
- {
- self.reachabilityBlock(self, flags);
- }
-
- // 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:@"<%@: %p (%@)>",
- NSStringFromClass([self class]), self, [self currentReachabilityFlags]];
- return description;
- }
- @end
|