123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- //
- // RealReachability.m
- // RealReachability
- //
- // Created by Dustturtle on 16/1/9.
- // Copyright © 2016 Dustturtle. All rights reserved.
- //
- #include <ifaddrs.h>
- #import "RealReachability.h"
- #import "FSMEngine.h"
- #import "PingHelper.h"
- #import <UIKit/UIKit.h>
- #import <CoreTelephony/CTTelephonyNetworkInfo.h>
- #if (!defined(DEBUG))
- #define NSLog(...)
- #endif
- #define kDefaultHost @"www.apple.com"
- #define kDefaultCheckInterval 2.0f
- #define kDefaultPingTimeout 2.0f
- #define kMinAutoCheckInterval 0.3f
- #define kMaxAutoCheckInterval 60.0f
- NSString *const kRealReachabilityChangedNotification = @"kRealReachabilityChangedNotification";
- NSString *const kRRVPNStatusChangedNotification = @"kRRVPNStatusChangedNotification";
- @interface RealReachability()
- {
- BOOL _vpnFlag;
- }
- @property (nonatomic, strong) FSMEngine *engine;
- @property (nonatomic, assign) BOOL isNotifying;
- @property (nonatomic,strong) NSArray *typeStrings5G;
- @property (nonatomic,strong) NSArray *typeStrings4G;
- @property (nonatomic,strong) NSArray *typeStrings3G;
- @property (nonatomic,strong) NSArray *typeStrings2G;
- @property (nonatomic, assign) ReachabilityStatus previousStatus;
- /// main helper
- @property (nonatomic, strong) PingHelper *pingHelper;
- /// for double check
- @property (nonatomic, strong) PingHelper *pingChecker;
- @end
- @implementation RealReachability
- #pragma mark - Life Circle
- - (id)init
- {
- if ((self = [super init]))
- {
- _engine = [[FSMEngine alloc] init];
- [_engine start];
-
- _typeStrings2G = @[CTRadioAccessTechnologyEdge,
- CTRadioAccessTechnologyGPRS,
- CTRadioAccessTechnologyCDMA1x];
-
- _typeStrings3G = @[CTRadioAccessTechnologyHSDPA,
- CTRadioAccessTechnologyWCDMA,
- CTRadioAccessTechnologyHSUPA,
- CTRadioAccessTechnologyCDMAEVDORev0,
- CTRadioAccessTechnologyCDMAEVDORevA,
- CTRadioAccessTechnologyCDMAEVDORevB,
- CTRadioAccessTechnologyeHRPD];
-
- _typeStrings4G = @[CTRadioAccessTechnologyLTE];
-
- _typeStrings5G = @[@"CTRadioAccessTechnologyNR", @"CTRadioAccessTechnologyNRNSA"];
-
- _hostForPing = kDefaultHost;
- _hostForCheck = kDefaultHost;
- _autoCheckInterval = kDefaultCheckInterval;
- _pingTimeout = kDefaultPingTimeout;
-
- _vpnFlag = NO;
-
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(appBecomeActive)
- name:UIApplicationDidBecomeActiveNotification
- object:nil];
-
- _localObserver = [[LocalConnection alloc] init];
- _pingHelper = [[PingHelper alloc] init];
- _pingChecker = [[PingHelper alloc] init];
- }
- return self;
- }
- - (void)dealloc
- {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
-
- self.engine = nil;
-
- [self.localObserver stopNotifier];
- _localObserver = nil;
- }
- #pragma mark - Handle system event
- - (void)appBecomeActive
- {
- if (self.isNotifying)
- {
- [self reachabilityWithBlock:nil];
- }
- }
- #pragma mark - Singlton Method
- + (instancetype)sharedInstance
- {
- static id sharedRRInstance = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- sharedRRInstance = [[self alloc] init];
- });
-
- return sharedRRInstance;
- }
- #pragma mark - actions
- - (void)startNotifier
- {
- if (self.isNotifying)
- {
- // avoid duplicate action
- return;
- }
-
- self.isNotifying = YES;
- self.previousStatus = RealStatusUnknown;
-
- NSDictionary *inputDic = @{kEventKeyID:@(RREventLoad)};
- [self.engine receiveInput:inputDic];
-
- [self.localObserver startNotifier];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(localConnectionHandler:)
- name:kLocalConnectionChangedNotification
- object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(localConnectionHandler:)
- name:kLocalConnectionInitializedNotification
- object:nil];
-
- self.pingHelper.host = _hostForPing;
- self.pingHelper.timeout = self.pingTimeout;
-
- self.pingChecker.host = _hostForCheck;
- self.pingChecker.timeout = self.pingTimeout;
-
- [self autoCheckReachability];
- }
- - (void)stopNotifier
- {
- if (!self.isNotifying)
- {
- // avoid duplicate action
- return;
- }
-
- [[NSNotificationCenter defaultCenter] removeObserver:self
- name:kLocalConnectionChangedNotification
- object:nil];
- [[NSNotificationCenter defaultCenter] removeObserver:self
- name:kLocalConnectionInitializedNotification
- object:nil];
-
- NSDictionary *inputDic = @{kEventKeyID:@(RREventUnLoad)};
- [self.engine receiveInput:inputDic];
-
- [self.localObserver stopNotifier];
-
- self.isNotifying = NO;
- }
- #pragma mark - outside invoke
- - (void)reachabilityWithBlock:(void (^)(ReachabilityStatus status))asyncHandler
- {
- // logic optimization: no need to ping when Local connection unavailable!
- if ([self.localObserver currentLocalConnectionStatus] == LC_UnReachable)
- {
- if (asyncHandler != nil)
- {
- asyncHandler(RealStatusNotReachable);
- }
- return;
- }
-
- // special case, VPN on; just skipping (ICMP not working now).
- if ([self isVPNOn])
- {
- ReachabilityStatus status = [self currentReachabilityStatus];
- if (asyncHandler != nil)
- {
- asyncHandler(status);
- }
- return;
- }
-
- __weak __typeof(self)weakSelf = self;
- [self.pingHelper pingWithBlock:^(BOOL isSuccess, NSTimeInterval latency)
- {
- __strong __typeof(weakSelf)strongSelf = weakSelf;
- strongSelf.latency = latency;
- if (isSuccess)
- {
- ReachabilityStatus status = [self currentReachabilityStatus];
-
- // Post the notification if the state changed here.
- NSDictionary *inputDic = @{kEventKeyID:@(RREventPingCallback), kEventKeyParam:@(YES)};
- NSInteger rtn = [strongSelf.engine receiveInput:inputDic];
- if (rtn == 0) // state changed & state available, post notification.
- {
- if ([strongSelf.engine isCurrentStateAvailable])
- {
- strongSelf.previousStatus = status;
- __weak __typeof(self)weakSelf = strongSelf;
- dispatch_async(dispatch_get_main_queue(), ^{
- __strong __typeof(weakSelf)strongSelf = weakSelf;
- [[NSNotificationCenter defaultCenter] postNotificationName:kRealReachabilityChangedNotification
- object:strongSelf];
- });
- }
- }
-
- if (asyncHandler != nil)
- {
- ReachabilityStatus currentStatus = [strongSelf currentReachabilityStatus];
- asyncHandler(currentStatus);
- }
- }
- else
- {
- if ([self isVPNOn])
- {
- // special case, VPN connected. Just ignore the ping result.
- }
- else
- {
- // delay 1 seconds, then make a double check.
- dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1*NSEC_PER_SEC));
- __weak __typeof(self)weakSelf = self;
- dispatch_after(time, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- __strong __typeof(weakSelf)self = weakSelf;
- [self makeDoubleCheck:asyncHandler];
- });
- }
- }
- }];
- }
- - (ReachabilityStatus)currentReachabilityStatus
- {
- RRStateID currentID = self.engine.currentStateID;
-
- switch (currentID)
- {
- case RRStateUnReachable:
- {
- return RealStatusNotReachable;
- }
- case RRStateWIFI:
- {
- return RealStatusViaWiFi;
- }
- case RRStateWWAN:
- {
- return RealStatusViaWWAN;
- }
- case RRStateLoading:
- {
- // status on loading, return local status temporary.
- return (ReachabilityStatus)(self.localObserver.currentLocalConnectionStatus);
- }
-
- default:
- {
- NSLog(@"No normal status matched, return unreachable temporary");
- return RealStatusNotReachable;
- }
- }
- }
- - (ReachabilityStatus)previousReachabilityStatus
- {
- return self.previousStatus;
- }
- - (void)setHostForPing:(NSString *)hostForPing
- {
- _hostForPing = nil;
- _hostForPing = [hostForPing copy];
-
- self.pingHelper.host = _hostForPing;
- }
- - (void)setHostForCheck:(NSString *)hostForCheck
- {
- _hostForCheck = nil;
- _hostForCheck = [hostForCheck copy];
-
- self.pingChecker.host = _hostForCheck;
- }
- - (void)setPingTimeout:(NSTimeInterval)pingTimeout
- {
- _pingTimeout = pingTimeout;
- self.pingHelper.timeout = pingTimeout;
- self.pingChecker.timeout = pingTimeout;
- }
- - (WWANAccessType)currentWWANtype
- {
- if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
- {
- CTTelephonyNetworkInfo *teleInfo = [[CTTelephonyNetworkInfo alloc] init];
- NSString *accessString = @"";
-
- if (@available(iOS 12.0, *))
- {
- // dual sim, maybe dual cards, simple choose one.
- NSDictionary *infoDic = teleInfo.serviceCurrentRadioAccessTechnology;
- NSArray *accessStrings = infoDic.allValues;
- if (accessStrings.count > 0)
- {
- accessString = accessStrings[0];
- }
- }
- else
- {
- accessString = teleInfo.currentRadioAccessTechnology;
- }
-
- if ([accessString length] > 0)
- {
- return [self accessTypeForString:accessString];
- }
- else
- {
- return WWANTypeUnknown;
- }
- }
- else
- {
- return WWANTypeUnknown;
- }
- }
- #pragma mark - inner methods
- - (void)makeDoubleCheck:(void (^)(ReachabilityStatus status))asyncHandler
- {
- __weak __typeof(self)weakSelf = self;
- [self.pingChecker pingWithBlock:^(BOOL isSuccess, NSTimeInterval latency) {
- __strong __typeof(weakSelf)strongSelf = weakSelf;
- strongSelf.latency = latency;
- ReachabilityStatus status = [strongSelf currentReachabilityStatus];
-
- NSDictionary *inputDic = @{kEventKeyID:@(RREventPingCallback), kEventKeyParam:@(isSuccess)};
- NSInteger rtn = [strongSelf.engine receiveInput:inputDic];
- if (rtn == 0) // state changed & state available, post notification.
- {
- if ([strongSelf.engine isCurrentStateAvailable])
- {
- strongSelf.previousStatus = status;
- __weak __typeof(self)weakSelf = strongSelf;
- dispatch_async(dispatch_get_main_queue(), ^{
- __strong __typeof(weakSelf)strongSelf = weakSelf;
- [[NSNotificationCenter defaultCenter] postNotificationName:kRealReachabilityChangedNotification
- object:strongSelf];
- });
- }
- }
-
- if (asyncHandler != nil)
- {
- ReachabilityStatus currentStatus = [strongSelf currentReachabilityStatus];
- asyncHandler(currentStatus);
- }
- }];
- }
- - (NSString *)paramValueFromStatus:(LocalConnectionStatus)status
- {
- switch (status)
- {
- case LC_UnReachable:
- {
- return kParamValueUnReachable;
- }
- case LC_WiFi:
- {
- return kParamValueWIFI;
- }
- case LC_WWAN:
- {
- return kParamValueWWAN;
- }
-
- default:
- {
- NSLog(@"RealReachability error! paramValueFromStatus not matched!");
- return @"";
- }
- }
- }
- // auto checking after every autoCheckInterval minutes
- - (void)autoCheckReachability
- {
- if (!self.isNotifying)
- {
- return;
- }
-
- if (self.autoCheckInterval < kMinAutoCheckInterval)
- {
- self.autoCheckInterval = kMinAutoCheckInterval;
- }
-
- if (self.autoCheckInterval > kMaxAutoCheckInterval)
- {
- self.autoCheckInterval = kMaxAutoCheckInterval;
- }
-
-
- dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.autoCheckInterval*60*NSEC_PER_SEC));
- __weak __typeof(self)weakSelf = self;
- dispatch_after(time, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- __strong __typeof(weakSelf)strongSelf = weakSelf;
- [strongSelf reachabilityWithBlock:nil];
- [strongSelf autoCheckReachability];
- });
- }
- - (WWANAccessType)accessTypeForString:(NSString *)accessString
- {
- if ([self.typeStrings5G containsObject:accessString])
- {
- return WWANType5G;
- }
- else if ([self.typeStrings4G containsObject:accessString])
- {
- return WWANType4G;
- }
- else if ([self.typeStrings3G containsObject:accessString])
- {
- return WWANType3G;
- }
- else if ([self.typeStrings2G containsObject:accessString])
- {
- return WWANType2G;
- }
- else
- {
- return WWANTypeUnknown;
- }
- }
- #pragma mark - Notification observer
- - (void)localConnectionHandler:(NSNotification *)notification
- {
- LocalConnection *lc = (LocalConnection *)notification.object;
- LocalConnectionStatus lcStatus = [lc currentLocalConnectionStatus];
- //NSLog(@"currentLocalConnectionStatus:%@, receive notification:%@",@(lcStatus), notification.name);
- ReachabilityStatus status = [self currentReachabilityStatus];
-
- NSDictionary *inputDic = @{kEventKeyID:@(RREventLocalConnectionCallback), kEventKeyParam:[self paramValueFromStatus:lcStatus]};
- NSInteger rtn = [self.engine receiveInput:inputDic];
-
- if (rtn == 0) // state changed & state available, post notification.
- {
- if ([self.engine isCurrentStateAvailable])
- {
- self.previousStatus = status;
-
- // already in main thread.
- if ([notification.name isEqualToString:kLocalConnectionChangedNotification])
- {
- [[NSNotificationCenter defaultCenter] postNotificationName:kRealReachabilityChangedNotification
- object:self];
- }
-
- if (lcStatus != LC_UnReachable)
- {
- // To make sure your reachability is "Real".
- [self reachabilityWithBlock:nil];
- }
- }
- }
- }
- - (BOOL)isVPNOn
- {
- BOOL flag = NO;
- NSString *version = [UIDevice currentDevice].systemVersion;
- // need two ways to judge this.
- if (version.doubleValue >= 9.0)
- {
- NSDictionary *dict = CFBridgingRelease(CFNetworkCopySystemProxySettings());
- NSArray *keys = [dict[@"__SCOPED__"] allKeys];
- for (NSString *key in keys) {
- if ([key rangeOfString:@"tap"].location != NSNotFound ||
- [key rangeOfString:@"tun"].location != NSNotFound ||
- [key rangeOfString:@"ipsec"].location != NSNotFound ||
- [key rangeOfString:@"ppp"].location != NSNotFound){
- flag = YES;
- break;
- }
- }
- }
- else
- {
- struct ifaddrs *interfaces = NULL;
- struct ifaddrs *temp_addr = NULL;
- int success = 0;
-
- // retrieve the current interfaces - returns 0 on success
- success = getifaddrs(&interfaces);
- if (success == 0)
- {
- // Loop through linked list of interfaces
- temp_addr = interfaces;
- while (temp_addr != NULL)
- {
- NSString *string = [NSString stringWithFormat:@"%s" , temp_addr->ifa_name];
- if ([string rangeOfString:@"tap"].location != NSNotFound ||
- [string rangeOfString:@"tun"].location != NSNotFound ||
- [string rangeOfString:@"ipsec"].location != NSNotFound ||
- [string rangeOfString:@"ppp"].location != NSNotFound)
- {
- flag = YES;
- break;
- }
- temp_addr = temp_addr->ifa_next;
- }
- }
-
- // Free memory
- freeifaddrs(interfaces);
- }
-
- if (_vpnFlag != flag)
- {
- // reset flag
- _vpnFlag = flag;
-
- // post notification
- __weak __typeof(self)weakSelf = self;
- dispatch_async(dispatch_get_main_queue(), ^{
- __strong __typeof(weakSelf)strongSelf = weakSelf;
- [[NSNotificationCenter defaultCenter] postNotificationName:kRRVPNStatusChangedNotification
- object:strongSelf];
- });
- }
-
- return flag;
- }
- @end
|