]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSReachabilityTracker.m
Security-58286.270.3.0.1.tar.gz
[apple/security.git] / keychain / ckks / CKKSReachabilityTracker.m
1 /*
2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #if OCTAGON
25
26 #import <Foundation/Foundation.h>
27 #import <CloudKit/CloudKit.h>
28
29 #import <dispatch/dispatch.h>
30 #import <sys/types.h>
31 #import <sys/socket.h>
32 #import <netinet/in.h>
33
34 #import "keychain/ckks/CKKS.h"
35 #import "keychain/ckks/CKKSGroupOperation.h"
36 #import "keychain/ckks/CKKSResultOperation.h"
37 #import "keychain/ckks/CKKSReachabilityTracker.h"
38 #import "keychain/ckks/CKKSAnalytics.h"
39
40 // force reachability timeout every now and then
41 #define REACHABILITY_TIMEOUT (12 * 3600 * NSEC_PER_SEC)
42
43 @interface CKKSReachabilityTracker ()
44 @property bool haveNetwork;
45 @property dispatch_queue_t queue;
46 @property NSOperationQueue* operationQueue;
47 @property (assign) SCNetworkReachabilityRef reachability;
48 @property dispatch_source_t timer;
49 @end
50
51 @implementation CKKSReachabilityTracker
52
53 static void
54 callout(SCNetworkReachabilityRef reachability,
55 SCNetworkReachabilityFlags flags,
56 void *context)
57 {
58 CKKSReachabilityTracker *tracker = (__bridge id)context;
59 [tracker _onqueueRecheck:flags];
60 }
61
62 - (instancetype)init {
63 if((self = [super init])) {
64 _queue = dispatch_queue_create("reachabiltity-tracker", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
65 _operationQueue = [[NSOperationQueue alloc] init];
66
67 dispatch_sync(_queue, ^{
68 [self _onQueueResetReachabilityDependency];
69 });
70
71 __weak __typeof(self) weakSelf = self;
72
73 if(!SecCKKSTestsEnabled()) {
74 struct sockaddr_in zeroAddress;
75 bzero(&zeroAddress, sizeof(zeroAddress));
76 zeroAddress.sin_len = sizeof(zeroAddress);
77 zeroAddress.sin_family = AF_INET;
78
79 _reachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);
80
81 SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
82 SCNetworkReachabilitySetDispatchQueue(_reachability, _queue);
83 SCNetworkReachabilitySetCallback(_reachability, callout, &context);
84 }
85
86 [weakSelf recheck];
87 }
88 return self;
89 }
90
91 -(NSString*)description {
92 return [NSString stringWithFormat: @"<CKKSReachabilityTracker: %@>", self.haveNetwork ? @"online" : @"offline"];
93 }
94
95 -(bool)currentReachability {
96 __block bool currentReachability = false;
97 dispatch_sync(self.queue, ^{
98 currentReachability = self.haveNetwork;
99 });
100 return currentReachability;
101 }
102
103 -(void)_onQueueRunreachabilityDependency
104 {
105 dispatch_assert_queue(self.queue);
106 // We're have network now, or timer expired, either way, execute dependency
107 if (self.reachabilityDependency) {
108 [self.operationQueue addOperation: self.reachabilityDependency];
109 self.reachabilityDependency = nil;
110 }
111 if (self.timer) {
112 dispatch_source_cancel(self.timer);
113 self.timer = nil;
114 }
115 }
116
117 -(void)_onQueueResetReachabilityDependency {
118 dispatch_assert_queue(self.queue);
119
120 if(self.reachabilityDependency == nil || ![self.reachabilityDependency isPending]) {
121 __weak __typeof(self) weakSelf = self;
122
123 secnotice("ckksnetwork", "Network unavailable");
124 self.reachabilityDependency = [CKKSResultOperation named:@"network-available-dependency" withBlock: ^{
125 __typeof(self) strongSelf = weakSelf;
126 if (strongSelf == nil) {
127 return;
128 }
129 if (strongSelf.haveNetwork) {
130 secnotice("ckksnetwork", "Network available");
131 } else {
132 secnotice("ckksnetwork", "Network still not available, retrying after waiting %2.1f hours",
133 ((float)(REACHABILITY_TIMEOUT/NSEC_PER_SEC)) / 3600);
134 }
135 }];
136
137 /*
138 * Make sure we are not stuck forever and retry every REACHABILITY_TIMEOUT
139 */
140 self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
141 0,
142 (dispatch_source_timer_flags_t)0,
143 self.queue);
144 dispatch_source_set_event_handler(self.timer, ^{
145 __typeof(self) strongSelf = weakSelf;
146 if (strongSelf == nil) {
147 return;
148 }
149 if (strongSelf.timer) {
150 [[CKKSAnalytics logger] noteEvent:CKKSEventReachabilityTimerExpired];
151 [strongSelf _onQueueRunreachabilityDependency];
152 }
153 });
154
155 dispatch_source_set_timer(self.timer,
156 dispatch_time(DISPATCH_TIME_NOW, REACHABILITY_TIMEOUT),
157 DISPATCH_TIME_FOREVER, //one-shot
158 30 * NSEC_PER_SEC);
159 dispatch_resume(self.timer);
160 }
161 }
162
163 -(void)_onqueueRecheck:(SCNetworkReachabilityFlags)flags {
164 dispatch_assert_queue(self.queue);
165
166 const SCNetworkReachabilityFlags reachabilityFlags =
167 kSCNetworkReachabilityFlagsReachable
168 | kSCNetworkReachabilityFlagsConnectionAutomatic
169 #if TARGET_OS_IPHONE
170 | kSCNetworkReachabilityFlagsIsWWAN
171 #endif
172 ;
173
174 bool hadNetwork = self.haveNetwork;
175 self.haveNetwork = !!(flags & reachabilityFlags);
176
177 if(hadNetwork != self.haveNetwork) {
178 if(self.haveNetwork) {
179 // We're have network now
180 [self _onQueueRunreachabilityDependency];
181 } else {
182 [self _onQueueResetReachabilityDependency];
183 }
184 }
185 }
186
187 + (SCNetworkReachabilityFlags)getReachabilityFlags:(SCNetworkReachabilityRef)target
188 {
189 SCNetworkReachabilityFlags flags;
190 if (SCNetworkReachabilityGetFlags(target, &flags))
191 return flags;
192 return 0;
193 }
194
195 -(void)recheck {
196 dispatch_sync(self.queue, ^{
197 SCNetworkReachabilityFlags flags = [CKKSReachabilityTracker getReachabilityFlags:self.reachability];
198 [self _onqueueRecheck:flags];
199 });
200 }
201
202 -(bool)isNetworkError:(NSError *)error {
203 if (error == nil)
204 return false;
205 return ([error.domain isEqualToString:CKErrorDomain] &&
206 (error.code == CKErrorNetworkUnavailable
207 || error.code == CKErrorNetworkFailure));
208 }
209
210 @end
211
212 #endif // OCTAGON
213