]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSReachabilityTracker.m
Security-59754.41.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 <nw/private.h>
35
36 #import "keychain/ckks/CKKS.h"
37 #import "keychain/ckks/CKKSGroupOperation.h"
38 #import "keychain/ckks/CKKSResultOperation.h"
39 #import "keychain/ckks/CKKSReachabilityTracker.h"
40 #import "keychain/ckks/CKKSAnalytics.h"
41 #import "keychain/ot/ObjCImprovements.h"
42
43 // force reachability timeout every now and then
44 #define REACHABILITY_TIMEOUT (12 * 3600 * NSEC_PER_SEC)
45
46 @interface CKKSReachabilityTracker ()
47 @property bool haveNetwork;
48 @property dispatch_queue_t queue;
49 @property NSOperationQueue* operationQueue;
50 @property nw_path_monitor_t networkMonitor;
51 @property dispatch_source_t timer;
52 @end
53
54 @implementation CKKSReachabilityTracker
55
56 - (instancetype)init {
57 if((self = [super init])) {
58 _queue = dispatch_queue_create("reachabiltity-tracker", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
59 _operationQueue = [[NSOperationQueue alloc] init];
60
61 dispatch_sync(_queue, ^{
62 [self _onQueueResetReachabilityDependency];
63 });
64
65 WEAKIFY(self);
66
67 if(!SecCKKSTestsEnabled()) {
68 _networkMonitor = nw_path_monitor_create();
69 nw_path_monitor_set_queue(self.networkMonitor, _queue);
70 nw_path_monitor_set_update_handler(self.networkMonitor, ^(nw_path_t _Nonnull path) {
71 STRONGIFY(self);
72 bool networkAvailable = (nw_path_get_status(path) == nw_path_status_satisfied);
73
74 ckksinfo_global("ckksnetwork", "nw_path update: network is %@", networkAvailable ? @"available" : @"unavailable");
75 [self _onqueueSetNetworkReachability:networkAvailable];
76 });
77 nw_path_monitor_start(self.networkMonitor);
78 }
79 }
80 return self;
81 }
82
83 - (NSString*)description {
84 return [NSString stringWithFormat: @"<CKKSReachabilityTracker: %@>", self.haveNetwork ? @"online" : @"offline"];
85 }
86
87 - (bool)currentReachability {
88 __block bool currentReachability = false;
89 dispatch_sync(self.queue, ^{
90 currentReachability = self.haveNetwork;
91 });
92 return currentReachability;
93 }
94
95 - (void)_onQueueRunReachabilityDependency
96 {
97 dispatch_assert_queue(self.queue);
98 // We have network now, or the timer expired. Either way, execute dependency
99 if (self.reachabilityDependency) {
100 [self.operationQueue addOperation: self.reachabilityDependency];
101 self.reachabilityDependency = nil;
102 }
103 if (self.timer) {
104 dispatch_source_cancel(self.timer);
105 self.timer = nil;
106 }
107 }
108
109 - (void)_onQueueResetReachabilityDependency {
110 dispatch_assert_queue(self.queue);
111
112 if(self.reachabilityDependency == nil || ![self.reachabilityDependency isPending]) {
113 WEAKIFY(self);
114
115 ckksnotice_global("network", "Network unavailable");
116 self.reachabilityDependency = [CKKSResultOperation named:@"network-available-dependency" withBlock: ^{
117 STRONGIFY(self);
118 if (self.haveNetwork) {
119 ckksnotice_global("network", "Network available");
120 } else {
121 ckksnotice_global("network", "Network still not available, retrying after waiting %2.1f hours",
122 ((float)(REACHABILITY_TIMEOUT/NSEC_PER_SEC)) / 3600);
123 }
124 }];
125
126 /*
127 * Make sure we are not stuck forever and retry every REACHABILITY_TIMEOUT
128 */
129 self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
130 0,
131 (dispatch_source_timer_flags_t)0,
132 self.queue);
133 dispatch_source_set_event_handler(self.timer, ^{
134 STRONGIFY(self);
135 if (self == nil) {
136 return;
137 }
138 if (self.timer) {
139 [[CKKSAnalytics logger] noteEvent:CKKSEventReachabilityTimerExpired];
140 [self _onQueueRunReachabilityDependency];
141 }
142 });
143
144 dispatch_source_set_timer(self.timer,
145 dispatch_time(DISPATCH_TIME_NOW, REACHABILITY_TIMEOUT),
146 DISPATCH_TIME_FOREVER, //one-shot
147 30 * NSEC_PER_SEC);
148 dispatch_resume(self.timer);
149 }
150 }
151
152 - (void)_onqueueSetNetworkReachability:(bool)haveNetwork {
153 dispatch_assert_queue(self.queue);
154
155 bool hadNetwork = self.haveNetwork;
156 self.haveNetwork = !!(haveNetwork);
157
158 if(hadNetwork != self.haveNetwork) {
159 if(self.haveNetwork) {
160 // We're have network now
161 [self _onQueueRunReachabilityDependency];
162 } else {
163 [self _onQueueResetReachabilityDependency];
164 }
165 }
166 }
167
168 - (void)setNetworkReachability:(bool)reachable
169 {
170 dispatch_sync(self.queue, ^{
171 [self _onqueueSetNetworkReachability:reachable];
172 });
173 }
174
175 - (bool)isNetworkError:(NSError *)error {
176 return [CKKSReachabilityTracker isNetworkError:error];
177 }
178
179 + (bool)isNetworkError:(NSError *)error {
180 if (error == nil) {
181 return false;
182 }
183
184 if([CKKSReachabilityTracker isNetworkFailureError:error]) {
185 return true;
186 }
187
188 if ([error.domain isEqualToString:CKErrorDomain] &&
189 (error.code == CKErrorNetworkUnavailable)) {
190 return true;
191 }
192
193 if ([error.domain isEqualToString:NSURLErrorDomain] &&
194 error.code == NSURLErrorTimedOut) {
195 return true;
196 }
197
198 return false;
199 }
200
201 + (bool)isNetworkFailureError:(NSError *)error
202 {
203 if (error == nil) {
204 return false;
205 }
206 if ([error.domain isEqualToString:CKErrorDomain] && error.code == CKErrorNetworkFailure) {
207 return true;
208 }
209
210 return false;
211 }
212
213 @end
214
215 #endif // OCTAGON
216