RealReachability.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. //
  2. // RealReachability.m
  3. // RealReachability
  4. //
  5. // Created by Dustturtle on 16/1/9.
  6. // Copyright © 2016 Dustturtle. All rights reserved.
  7. //
  8. #include <ifaddrs.h>
  9. #import "RealReachability.h"
  10. #import "FSMEngine.h"
  11. #import "PingHelper.h"
  12. #import <UIKit/UIKit.h>
  13. #import <CoreTelephony/CTTelephonyNetworkInfo.h>
  14. #if (!defined(DEBUG))
  15. #define NSLog(...)
  16. #endif
  17. #define kDefaultHost @"www.apple.com"
  18. #define kDefaultCheckInterval 2.0f
  19. #define kDefaultPingTimeout 2.0f
  20. #define kMinAutoCheckInterval 0.3f
  21. #define kMaxAutoCheckInterval 60.0f
  22. NSString *const kRealReachabilityChangedNotification = @"kRealReachabilityChangedNotification";
  23. NSString *const kRRVPNStatusChangedNotification = @"kRRVPNStatusChangedNotification";
  24. @interface RealReachability()
  25. {
  26. BOOL _vpnFlag;
  27. }
  28. @property (nonatomic, strong) FSMEngine *engine;
  29. @property (nonatomic, assign) BOOL isNotifying;
  30. @property (nonatomic,strong) NSArray *typeStrings5G;
  31. @property (nonatomic,strong) NSArray *typeStrings4G;
  32. @property (nonatomic,strong) NSArray *typeStrings3G;
  33. @property (nonatomic,strong) NSArray *typeStrings2G;
  34. @property (nonatomic, assign) ReachabilityStatus previousStatus;
  35. /// main helper
  36. @property (nonatomic, strong) PingHelper *pingHelper;
  37. /// for double check
  38. @property (nonatomic, strong) PingHelper *pingChecker;
  39. @end
  40. @implementation RealReachability
  41. #pragma mark - Life Circle
  42. - (id)init
  43. {
  44. if ((self = [super init]))
  45. {
  46. _engine = [[FSMEngine alloc] init];
  47. [_engine start];
  48. _typeStrings2G = @[CTRadioAccessTechnologyEdge,
  49. CTRadioAccessTechnologyGPRS,
  50. CTRadioAccessTechnologyCDMA1x];
  51. _typeStrings3G = @[CTRadioAccessTechnologyHSDPA,
  52. CTRadioAccessTechnologyWCDMA,
  53. CTRadioAccessTechnologyHSUPA,
  54. CTRadioAccessTechnologyCDMAEVDORev0,
  55. CTRadioAccessTechnologyCDMAEVDORevA,
  56. CTRadioAccessTechnologyCDMAEVDORevB,
  57. CTRadioAccessTechnologyeHRPD];
  58. _typeStrings4G = @[CTRadioAccessTechnologyLTE];
  59. _typeStrings5G = @[@"CTRadioAccessTechnologyNR", @"CTRadioAccessTechnologyNRNSA"];
  60. _hostForPing = kDefaultHost;
  61. _hostForCheck = kDefaultHost;
  62. _autoCheckInterval = kDefaultCheckInterval;
  63. _pingTimeout = kDefaultPingTimeout;
  64. _vpnFlag = NO;
  65. [[NSNotificationCenter defaultCenter] addObserver:self
  66. selector:@selector(appBecomeActive)
  67. name:UIApplicationDidBecomeActiveNotification
  68. object:nil];
  69. _localObserver = [[LocalConnection alloc] init];
  70. _pingHelper = [[PingHelper alloc] init];
  71. _pingChecker = [[PingHelper alloc] init];
  72. }
  73. return self;
  74. }
  75. - (void)dealloc
  76. {
  77. [[NSNotificationCenter defaultCenter] removeObserver:self];
  78. self.engine = nil;
  79. [self.localObserver stopNotifier];
  80. _localObserver = nil;
  81. }
  82. #pragma mark - Handle system event
  83. - (void)appBecomeActive
  84. {
  85. if (self.isNotifying)
  86. {
  87. [self reachabilityWithBlock:nil];
  88. }
  89. }
  90. #pragma mark - Singlton Method
  91. + (instancetype)sharedInstance
  92. {
  93. static id sharedRRInstance = nil;
  94. static dispatch_once_t onceToken;
  95. dispatch_once(&onceToken, ^{
  96. sharedRRInstance = [[self alloc] init];
  97. });
  98. return sharedRRInstance;
  99. }
  100. #pragma mark - actions
  101. - (void)startNotifier
  102. {
  103. if (self.isNotifying)
  104. {
  105. // avoid duplicate action
  106. return;
  107. }
  108. self.isNotifying = YES;
  109. self.previousStatus = RealStatusUnknown;
  110. NSDictionary *inputDic = @{kEventKeyID:@(RREventLoad)};
  111. [self.engine receiveInput:inputDic];
  112. [self.localObserver startNotifier];
  113. [[NSNotificationCenter defaultCenter] addObserver:self
  114. selector:@selector(localConnectionHandler:)
  115. name:kLocalConnectionChangedNotification
  116. object:nil];
  117. [[NSNotificationCenter defaultCenter] addObserver:self
  118. selector:@selector(localConnectionHandler:)
  119. name:kLocalConnectionInitializedNotification
  120. object:nil];
  121. self.pingHelper.host = _hostForPing;
  122. self.pingHelper.timeout = self.pingTimeout;
  123. self.pingChecker.host = _hostForCheck;
  124. self.pingChecker.timeout = self.pingTimeout;
  125. [self autoCheckReachability];
  126. }
  127. - (void)stopNotifier
  128. {
  129. if (!self.isNotifying)
  130. {
  131. // avoid duplicate action
  132. return;
  133. }
  134. [[NSNotificationCenter defaultCenter] removeObserver:self
  135. name:kLocalConnectionChangedNotification
  136. object:nil];
  137. [[NSNotificationCenter defaultCenter] removeObserver:self
  138. name:kLocalConnectionInitializedNotification
  139. object:nil];
  140. NSDictionary *inputDic = @{kEventKeyID:@(RREventUnLoad)};
  141. [self.engine receiveInput:inputDic];
  142. [self.localObserver stopNotifier];
  143. self.isNotifying = NO;
  144. }
  145. #pragma mark - outside invoke
  146. - (void)reachabilityWithBlock:(void (^)(ReachabilityStatus status))asyncHandler
  147. {
  148. // logic optimization: no need to ping when Local connection unavailable!
  149. if ([self.localObserver currentLocalConnectionStatus] == LC_UnReachable)
  150. {
  151. if (asyncHandler != nil)
  152. {
  153. asyncHandler(RealStatusNotReachable);
  154. }
  155. return;
  156. }
  157. // special case, VPN on; just skipping (ICMP not working now).
  158. if ([self isVPNOn])
  159. {
  160. ReachabilityStatus status = [self currentReachabilityStatus];
  161. if (asyncHandler != nil)
  162. {
  163. asyncHandler(status);
  164. }
  165. return;
  166. }
  167. __weak __typeof(self)weakSelf = self;
  168. [self.pingHelper pingWithBlock:^(BOOL isSuccess, NSTimeInterval latency)
  169. {
  170. __strong __typeof(weakSelf)strongSelf = weakSelf;
  171. strongSelf.latency = latency;
  172. if (isSuccess)
  173. {
  174. ReachabilityStatus status = [self currentReachabilityStatus];
  175. // Post the notification if the state changed here.
  176. NSDictionary *inputDic = @{kEventKeyID:@(RREventPingCallback), kEventKeyParam:@(YES)};
  177. NSInteger rtn = [strongSelf.engine receiveInput:inputDic];
  178. if (rtn == 0) // state changed & state available, post notification.
  179. {
  180. if ([strongSelf.engine isCurrentStateAvailable])
  181. {
  182. strongSelf.previousStatus = status;
  183. __weak __typeof(self)weakSelf = strongSelf;
  184. dispatch_async(dispatch_get_main_queue(), ^{
  185. __strong __typeof(weakSelf)strongSelf = weakSelf;
  186. [[NSNotificationCenter defaultCenter] postNotificationName:kRealReachabilityChangedNotification
  187. object:strongSelf];
  188. });
  189. }
  190. }
  191. if (asyncHandler != nil)
  192. {
  193. ReachabilityStatus currentStatus = [strongSelf currentReachabilityStatus];
  194. asyncHandler(currentStatus);
  195. }
  196. }
  197. else
  198. {
  199. if ([self isVPNOn])
  200. {
  201. // special case, VPN connected. Just ignore the ping result.
  202. }
  203. else
  204. {
  205. // delay 1 seconds, then make a double check.
  206. dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1*NSEC_PER_SEC));
  207. __weak __typeof(self)weakSelf = self;
  208. dispatch_after(time, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  209. __strong __typeof(weakSelf)self = weakSelf;
  210. [self makeDoubleCheck:asyncHandler];
  211. });
  212. }
  213. }
  214. }];
  215. }
  216. - (ReachabilityStatus)currentReachabilityStatus
  217. {
  218. RRStateID currentID = self.engine.currentStateID;
  219. switch (currentID)
  220. {
  221. case RRStateUnReachable:
  222. {
  223. return RealStatusNotReachable;
  224. }
  225. case RRStateWIFI:
  226. {
  227. return RealStatusViaWiFi;
  228. }
  229. case RRStateWWAN:
  230. {
  231. return RealStatusViaWWAN;
  232. }
  233. case RRStateLoading:
  234. {
  235. // status on loading, return local status temporary.
  236. return (ReachabilityStatus)(self.localObserver.currentLocalConnectionStatus);
  237. }
  238. default:
  239. {
  240. NSLog(@"No normal status matched, return unreachable temporary");
  241. return RealStatusNotReachable;
  242. }
  243. }
  244. }
  245. - (ReachabilityStatus)previousReachabilityStatus
  246. {
  247. return self.previousStatus;
  248. }
  249. - (void)setHostForPing:(NSString *)hostForPing
  250. {
  251. _hostForPing = nil;
  252. _hostForPing = [hostForPing copy];
  253. self.pingHelper.host = _hostForPing;
  254. }
  255. - (void)setHostForCheck:(NSString *)hostForCheck
  256. {
  257. _hostForCheck = nil;
  258. _hostForCheck = [hostForCheck copy];
  259. self.pingChecker.host = _hostForCheck;
  260. }
  261. - (void)setPingTimeout:(NSTimeInterval)pingTimeout
  262. {
  263. _pingTimeout = pingTimeout;
  264. self.pingHelper.timeout = pingTimeout;
  265. self.pingChecker.timeout = pingTimeout;
  266. }
  267. - (WWANAccessType)currentWWANtype
  268. {
  269. if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
  270. {
  271. CTTelephonyNetworkInfo *teleInfo = [[CTTelephonyNetworkInfo alloc] init];
  272. NSString *accessString = @"";
  273. if (@available(iOS 12.0, *))
  274. {
  275. // dual sim, maybe dual cards, simple choose one.
  276. NSDictionary *infoDic = teleInfo.serviceCurrentRadioAccessTechnology;
  277. NSArray *accessStrings = infoDic.allValues;
  278. if (accessStrings.count > 0)
  279. {
  280. accessString = accessStrings[0];
  281. }
  282. }
  283. else
  284. {
  285. accessString = teleInfo.currentRadioAccessTechnology;
  286. }
  287. if ([accessString length] > 0)
  288. {
  289. return [self accessTypeForString:accessString];
  290. }
  291. else
  292. {
  293. return WWANTypeUnknown;
  294. }
  295. }
  296. else
  297. {
  298. return WWANTypeUnknown;
  299. }
  300. }
  301. #pragma mark - inner methods
  302. - (void)makeDoubleCheck:(void (^)(ReachabilityStatus status))asyncHandler
  303. {
  304. __weak __typeof(self)weakSelf = self;
  305. [self.pingChecker pingWithBlock:^(BOOL isSuccess, NSTimeInterval latency) {
  306. __strong __typeof(weakSelf)strongSelf = weakSelf;
  307. strongSelf.latency = latency;
  308. ReachabilityStatus status = [strongSelf currentReachabilityStatus];
  309. NSDictionary *inputDic = @{kEventKeyID:@(RREventPingCallback), kEventKeyParam:@(isSuccess)};
  310. NSInteger rtn = [strongSelf.engine receiveInput:inputDic];
  311. if (rtn == 0) // state changed & state available, post notification.
  312. {
  313. if ([strongSelf.engine isCurrentStateAvailable])
  314. {
  315. strongSelf.previousStatus = status;
  316. __weak __typeof(self)weakSelf = strongSelf;
  317. dispatch_async(dispatch_get_main_queue(), ^{
  318. __strong __typeof(weakSelf)strongSelf = weakSelf;
  319. [[NSNotificationCenter defaultCenter] postNotificationName:kRealReachabilityChangedNotification
  320. object:strongSelf];
  321. });
  322. }
  323. }
  324. if (asyncHandler != nil)
  325. {
  326. ReachabilityStatus currentStatus = [strongSelf currentReachabilityStatus];
  327. asyncHandler(currentStatus);
  328. }
  329. }];
  330. }
  331. - (NSString *)paramValueFromStatus:(LocalConnectionStatus)status
  332. {
  333. switch (status)
  334. {
  335. case LC_UnReachable:
  336. {
  337. return kParamValueUnReachable;
  338. }
  339. case LC_WiFi:
  340. {
  341. return kParamValueWIFI;
  342. }
  343. case LC_WWAN:
  344. {
  345. return kParamValueWWAN;
  346. }
  347. default:
  348. {
  349. NSLog(@"RealReachability error! paramValueFromStatus not matched!");
  350. return @"";
  351. }
  352. }
  353. }
  354. // auto checking after every autoCheckInterval minutes
  355. - (void)autoCheckReachability
  356. {
  357. if (!self.isNotifying)
  358. {
  359. return;
  360. }
  361. if (self.autoCheckInterval < kMinAutoCheckInterval)
  362. {
  363. self.autoCheckInterval = kMinAutoCheckInterval;
  364. }
  365. if (self.autoCheckInterval > kMaxAutoCheckInterval)
  366. {
  367. self.autoCheckInterval = kMaxAutoCheckInterval;
  368. }
  369. dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.autoCheckInterval*60*NSEC_PER_SEC));
  370. __weak __typeof(self)weakSelf = self;
  371. dispatch_after(time, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  372. __strong __typeof(weakSelf)strongSelf = weakSelf;
  373. [strongSelf reachabilityWithBlock:nil];
  374. [strongSelf autoCheckReachability];
  375. });
  376. }
  377. - (WWANAccessType)accessTypeForString:(NSString *)accessString
  378. {
  379. if ([self.typeStrings5G containsObject:accessString])
  380. {
  381. return WWANType5G;
  382. }
  383. else if ([self.typeStrings4G containsObject:accessString])
  384. {
  385. return WWANType4G;
  386. }
  387. else if ([self.typeStrings3G containsObject:accessString])
  388. {
  389. return WWANType3G;
  390. }
  391. else if ([self.typeStrings2G containsObject:accessString])
  392. {
  393. return WWANType2G;
  394. }
  395. else
  396. {
  397. return WWANTypeUnknown;
  398. }
  399. }
  400. #pragma mark - Notification observer
  401. - (void)localConnectionHandler:(NSNotification *)notification
  402. {
  403. LocalConnection *lc = (LocalConnection *)notification.object;
  404. LocalConnectionStatus lcStatus = [lc currentLocalConnectionStatus];
  405. //NSLog(@"currentLocalConnectionStatus:%@, receive notification:%@",@(lcStatus), notification.name);
  406. ReachabilityStatus status = [self currentReachabilityStatus];
  407. NSDictionary *inputDic = @{kEventKeyID:@(RREventLocalConnectionCallback), kEventKeyParam:[self paramValueFromStatus:lcStatus]};
  408. NSInteger rtn = [self.engine receiveInput:inputDic];
  409. if (rtn == 0) // state changed & state available, post notification.
  410. {
  411. if ([self.engine isCurrentStateAvailable])
  412. {
  413. self.previousStatus = status;
  414. // already in main thread.
  415. if ([notification.name isEqualToString:kLocalConnectionChangedNotification])
  416. {
  417. [[NSNotificationCenter defaultCenter] postNotificationName:kRealReachabilityChangedNotification
  418. object:self];
  419. }
  420. if (lcStatus != LC_UnReachable)
  421. {
  422. // To make sure your reachability is "Real".
  423. [self reachabilityWithBlock:nil];
  424. }
  425. }
  426. }
  427. }
  428. - (BOOL)isVPNOn
  429. {
  430. BOOL flag = NO;
  431. NSString *version = [UIDevice currentDevice].systemVersion;
  432. // need two ways to judge this.
  433. if (version.doubleValue >= 9.0)
  434. {
  435. NSDictionary *dict = CFBridgingRelease(CFNetworkCopySystemProxySettings());
  436. NSArray *keys = [dict[@"__SCOPED__"] allKeys];
  437. for (NSString *key in keys) {
  438. if ([key rangeOfString:@"tap"].location != NSNotFound ||
  439. [key rangeOfString:@"tun"].location != NSNotFound ||
  440. [key rangeOfString:@"ipsec"].location != NSNotFound ||
  441. [key rangeOfString:@"ppp"].location != NSNotFound){
  442. flag = YES;
  443. break;
  444. }
  445. }
  446. }
  447. else
  448. {
  449. struct ifaddrs *interfaces = NULL;
  450. struct ifaddrs *temp_addr = NULL;
  451. int success = 0;
  452. // retrieve the current interfaces - returns 0 on success
  453. success = getifaddrs(&interfaces);
  454. if (success == 0)
  455. {
  456. // Loop through linked list of interfaces
  457. temp_addr = interfaces;
  458. while (temp_addr != NULL)
  459. {
  460. NSString *string = [NSString stringWithFormat:@"%s" , temp_addr->ifa_name];
  461. if ([string rangeOfString:@"tap"].location != NSNotFound ||
  462. [string rangeOfString:@"tun"].location != NSNotFound ||
  463. [string rangeOfString:@"ipsec"].location != NSNotFound ||
  464. [string rangeOfString:@"ppp"].location != NSNotFound)
  465. {
  466. flag = YES;
  467. break;
  468. }
  469. temp_addr = temp_addr->ifa_next;
  470. }
  471. }
  472. // Free memory
  473. freeifaddrs(interfaces);
  474. }
  475. if (_vpnFlag != flag)
  476. {
  477. // reset flag
  478. _vpnFlag = flag;
  479. // post notification
  480. __weak __typeof(self)weakSelf = self;
  481. dispatch_async(dispatch_get_main_queue(), ^{
  482. __strong __typeof(weakSelf)strongSelf = weakSelf;
  483. [[NSNotificationCenter defaultCenter] postNotificationName:kRRVPNStatusChangedNotification
  484. object:strongSelf];
  485. });
  486. }
  487. return flag;
  488. }
  489. @end