]>
Commit | Line | Data |
---|---|---|
43bfd57e | 1 | /* |
1ef45fa4 | 2 | * Copyright (c) 2016, 2017 Apple Inc. All rights reserved. |
43bfd57e A |
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 | #import "SCTest.h" | |
25 | #import "SCTestUtils.h" | |
26 | #import <arpa/inet.h> | |
27 | #import <NetworkExtension/NEPolicySession.h> | |
28 | #import <network_information.h> | |
29 | ||
30 | #define REACHABILITY_TEST_HOSTNAME "apple.com" | |
31 | ||
32 | @interface SCTestReachability : SCTest | |
33 | @property SCNetworkReachabilityRef target; | |
34 | @property dispatch_queue_t callbackQ; | |
35 | @end | |
36 | ||
37 | @implementation SCTestReachability | |
38 | ||
39 | + (NSString *)command | |
40 | { | |
41 | return @"reachability"; | |
42 | } | |
43 | ||
44 | + (NSString *)commandDescription | |
45 | { | |
46 | return @"Tests the SCNetworkReachability code path"; | |
47 | } | |
48 | ||
49 | - (instancetype)initWithOptions:(NSDictionary *)options | |
50 | { | |
51 | self = [super initWithOptions:options]; | |
52 | if (self) { | |
53 | NSMutableDictionary *reachOptions; | |
54 | if (self.options[kSCTestReachabilityInterface] != nil) { | |
55 | reachOptions = [[NSMutableDictionary alloc] init]; | |
56 | [reachOptions setObject:self.options[kSCTestReachabilityInterface] forKey:(__bridge NSString *)kSCNetworkReachabilityOptionInterface]; | |
57 | if (self.options[kSCTestReachabilityHost] != nil) { | |
58 | [reachOptions setObject:self.options[kSCTestReachabilityHost] forKey:(__bridge NSString *)kSCNetworkReachabilityOptionNodeName]; | |
59 | } else if (self.options[kSCTestReachabilityAddress] != nil) { | |
60 | NSData *data; | |
61 | NSString *addressString = self.options[kSCTestReachabilityAddress]; | |
62 | struct sockaddr *sa = _SC_string_to_sockaddr(addressString.UTF8String, AF_INET, NULL, 0); | |
63 | if (sa == NULL) { | |
64 | sa = _SC_string_to_sockaddr(addressString.UTF8String, AF_INET6, NULL, 0); | |
65 | if (sa == NULL) { | |
66 | SCTestLog("Invalid address"); | |
67 | ERR_EXIT; | |
68 | } | |
69 | } | |
70 | data = [NSData dataWithBytes:sa length:(sa->sa_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)]; | |
71 | [reachOptions setObject:data forKey:(__bridge NSString *)kSCNetworkReachabilityOptionRemoteAddress]; | |
72 | } | |
73 | ||
74 | ||
75 | _target = SCNetworkReachabilityCreateWithOptions(kCFAllocatorDefault, (__bridge CFDictionaryRef)reachOptions); | |
76 | } else if (self.options[kSCTestReachabilityHost]) { | |
77 | NSString *host = self.options[kSCTestReachabilityHost]; | |
78 | _target = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, host.UTF8String); | |
79 | } else if (self.options[kSCTestReachabilityAddress]) { | |
80 | NSString *addressString = self.options[kSCTestReachabilityAddress]; | |
81 | struct sockaddr *sa = _SC_string_to_sockaddr(addressString.UTF8String, AF_INET, NULL, 0); | |
82 | if (sa == NULL) { | |
83 | sa = _SC_string_to_sockaddr(addressString.UTF8String, AF_INET6, NULL, 0); | |
84 | if (sa == NULL) { | |
85 | SCTestLog("Invalid address"); | |
86 | ERR_EXIT; | |
87 | } | |
88 | } | |
89 | _target = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, sa); | |
90 | } | |
91 | } | |
92 | return self; | |
93 | } | |
94 | ||
95 | - (void)dealloc | |
96 | { | |
97 | if (self.target != NULL) { | |
98 | CFRelease(self.target); | |
99 | self.target = NULL; | |
100 | } | |
101 | } | |
102 | ||
103 | void | |
1ef45fa4 | 104 | myReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) |
43bfd57e | 105 | { |
1ef45fa4 A |
106 | #pragma unused(target) |
107 | #pragma unused(info) | |
43bfd57e A |
108 | struct tm tm_now; |
109 | struct timeval tv_now; | |
110 | (void)gettimeofday(&tv_now, NULL); | |
111 | (void)localtime_r(&tv_now.tv_sec, &tm_now); | |
112 | SCTestLog("%2d:%02d:%02d.%03d Reachability changed: %#x", tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec, tv_now.tv_usec / 1000, flags); | |
113 | } | |
114 | ||
115 | - (void)start | |
116 | { | |
117 | if (self.options[kSCTestReachabilityHost] != nil && self.options[kSCTestReachabilityAddress] != nil) { | |
118 | SCTestLog("Please specify either a host or address"); | |
119 | ERR_EXIT; | |
120 | } | |
121 | ||
122 | if (self.options[kSCTestReachabilityWatch]) { | |
123 | self.callbackQ = dispatch_queue_create("SCTestReachability callback queue", NULL); | |
124 | SCNetworkReachabilitySetCallback(self.target, myReachabilityCallback, NULL); | |
125 | SCNetworkReachabilitySetDispatchQueue(self.target, self.callbackQ); | |
126 | } else { | |
127 | SCNetworkReachabilityFlags flags; | |
128 | SCNetworkReachabilityGetFlags(self.target, &flags); | |
129 | SCTestLog("Reachability: %#x", flags); | |
130 | [self cleanupAndExitWithErrorCode:0]; | |
131 | } | |
132 | } | |
133 | ||
134 | - (void)cleanupAndExitWithErrorCode:(int)error | |
135 | { | |
136 | [super cleanupAndExitWithErrorCode:error]; | |
137 | } | |
138 | ||
139 | - (NSString *)primaryInterfaceName | |
140 | { | |
141 | const char *name; | |
142 | NSString *nsName; | |
143 | nwi_ifstate_t ifstate; | |
144 | nwi_state_t nwi; | |
145 | ||
146 | nwi = nwi_state_copy(); | |
147 | ifstate = nwi_state_get_first_ifstate(nwi, AF_INET); | |
148 | if (ifstate == NULL) { | |
149 | ifstate = nwi_state_get_first_ifstate(nwi, AF_INET6); | |
150 | if (ifstate == NULL) { | |
151 | // May be we are in airplane mode | |
152 | SCTestLog("No primary interface found"); | |
153 | return nil; | |
154 | } | |
155 | } | |
156 | ||
157 | name = nwi_ifstate_get_ifname(ifstate); | |
158 | nsName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding]; | |
159 | nwi_state_release(nwi); | |
160 | ||
161 | return nsName; | |
162 | } | |
163 | ||
164 | - (BOOL)setup | |
165 | { | |
166 | return YES; | |
167 | } | |
168 | ||
169 | - (BOOL)unitTest | |
170 | { | |
171 | if(![self setup]) { | |
172 | return NO; | |
173 | } | |
174 | ||
175 | BOOL allUnitTestsPassed = YES; | |
176 | ||
177 | allUnitTestsPassed &= [self unitTestBasicReachabilityCheck]; | |
178 | allUnitTestsPassed &= [self unitTestReachabilityWithPolicy]; | |
179 | allUnitTestsPassed &= [self unitTestScopedReachabilityWithPolicy]; | |
180 | ||
181 | if(![self tearDown]) { | |
182 | return NO; | |
183 | } | |
184 | ||
185 | return allUnitTestsPassed; | |
186 | } | |
187 | ||
188 | - (BOOL)unitTestBasicReachabilityCheck | |
189 | { | |
190 | SCNetworkReachabilityFlags flags; | |
191 | SCTestReachability *test; | |
192 | ||
193 | test = [[SCTestReachability alloc] initWithOptions:self.options]; | |
194 | if ([test primaryInterfaceName] == nil) { | |
195 | return YES; | |
196 | } | |
197 | ||
198 | if (test.target == NULL) { | |
199 | NSDictionary *options = @{kSCTestReachabilityHost:@REACHABILITY_TEST_HOSTNAME}; | |
200 | test = [[SCTestReachability alloc] initWithOptions:options]; | |
201 | } | |
202 | ||
203 | ||
204 | SCNetworkReachabilityGetFlags(test.target, &flags); | |
205 | if (flags == 0) { | |
206 | SCTestLog("Reachability reported not reachable"); | |
207 | return NO; | |
208 | } | |
209 | ||
210 | SCTestLog("Verified that basic reachability check succeeds"); | |
211 | return YES; | |
212 | } | |
213 | ||
214 | - (BOOL)unitTestReachabilityWithPolicy | |
215 | { | |
216 | NEPolicyCondition *condition1; | |
217 | NEPolicyCondition *condition2; | |
218 | SCNetworkReachabilityFlags flags; | |
219 | BOOL ok; | |
220 | NEPolicy *policy; | |
221 | NSUInteger policyID; | |
222 | NEPolicyResult *result; | |
223 | NEPolicySession *session; | |
224 | SCTestReachability *test; | |
225 | ||
226 | test = [[SCTestReachability alloc] initWithOptions:self.options]; | |
227 | if ([test primaryInterfaceName] == nil) { | |
228 | return YES; | |
229 | } | |
230 | ||
231 | if (test.target == NULL) { | |
232 | NSDictionary *options = @{kSCTestReachabilityHost:@REACHABILITY_TEST_HOSTNAME}; | |
233 | test = [[SCTestReachability alloc] initWithOptions:options]; | |
234 | } | |
235 | ||
236 | SCNetworkReachabilityGetFlags(test.target, &flags); | |
237 | if (flags == 0) { | |
238 | SCTestLog("Reachability reported not reachable"); | |
239 | return NO; | |
240 | } | |
241 | ||
242 | session = [[NEPolicySession alloc] init]; | |
243 | if (session == nil) { | |
244 | SCTestLog("Failed to create NEPolicySession"); | |
245 | return NO; | |
246 | } | |
247 | ||
248 | result = [NEPolicyResult drop]; | |
249 | condition1 = [NEPolicyCondition allInterfaces]; | |
250 | condition2 = [NEPolicyCondition effectivePID:getpid()]; | |
251 | policy = [[NEPolicy alloc ] initWithOrder:10 result:result conditions:@[condition1, condition2]]; | |
252 | policyID = [session addPolicy:policy]; | |
253 | if (policyID == 0) { | |
254 | SCTestLog("Failed to add policy"); | |
255 | return NO; | |
256 | } | |
257 | ||
258 | ok = [session apply]; | |
259 | if (!ok) { | |
260 | SCTestLog("Failed to apply policy"); | |
261 | return NO; | |
262 | } | |
263 | ||
264 | SCNetworkReachabilityGetFlags(test.target, &flags); | |
265 | if (flags != 0) { | |
266 | SCTestLog("Reachability reported as reachable, in presence of a drop policy"); | |
267 | return NO; | |
268 | } | |
269 | ||
270 | ok = [session removeAllPolicies]; | |
271 | if (!ok) { | |
272 | SCTestLog("Failed to remove policies from session"); | |
273 | return NO; | |
274 | } | |
275 | ||
276 | ok = [session apply]; | |
277 | if (!ok) { | |
278 | SCTestLog("Failed to apply policy"); | |
279 | return NO; | |
280 | } | |
281 | ||
282 | SCNetworkReachabilityGetFlags(test.target, &flags); | |
283 | if (flags == 0) { | |
284 | SCTestLog("Reachability reported as not reachable, in absence of a drop policy"); | |
285 | return NO; | |
286 | } | |
287 | ||
288 | SCTestLog("Verified that SCNetworkReachability reports reachability corresponding to the NECP Policies"); | |
289 | return YES; | |
290 | } | |
291 | ||
292 | - (BOOL)unitTestScopedReachabilityWithPolicy | |
293 | { | |
294 | NEPolicyCondition *condition1; | |
295 | NEPolicyCondition *condition2; | |
296 | SCNetworkReachabilityFlags flags; | |
297 | BOOL ok; | |
298 | NEPolicy *policy; | |
299 | NSUInteger policyID; | |
300 | NSString *primaryInterface; | |
301 | NEPolicyResult *result; | |
302 | NEPolicySession *session; | |
303 | SCTestReachability *test; | |
304 | ||
305 | test = [[SCTestReachability alloc] initWithOptions:self.options]; | |
306 | primaryInterface = [test primaryInterfaceName]; | |
307 | if (primaryInterface == nil) { | |
308 | return YES; | |
309 | } | |
310 | ||
311 | if (test.target == NULL) { | |
312 | NSDictionary *options = @{kSCTestReachabilityHost:@REACHABILITY_TEST_HOSTNAME, | |
313 | kSCTestReachabilityInterface:primaryInterface}; | |
314 | test = [[SCTestReachability alloc] initWithOptions:options]; | |
315 | } | |
316 | ||
317 | SCNetworkReachabilityGetFlags(test.target, &flags); | |
318 | if (flags == 0) { | |
319 | SCTestLog("Reachability reported not reachable"); | |
320 | return NO; | |
321 | } | |
322 | ||
323 | session = [[NEPolicySession alloc] init]; | |
324 | if (session == nil) { | |
325 | SCTestLog("Failed to create NEPolicySession"); | |
326 | return NO; | |
327 | } | |
328 | ||
329 | result = [NEPolicyResult drop]; | |
330 | condition1 = [NEPolicyCondition scopedInterface:primaryInterface]; | |
331 | condition2 = [NEPolicyCondition effectivePID:getpid()]; | |
332 | policy = [[NEPolicy alloc ] initWithOrder:10 result:result conditions:@[condition1, condition2]]; | |
333 | policyID = [session addPolicy:policy]; | |
334 | if (policyID == 0) { | |
335 | SCTestLog("Failed to add policy"); | |
336 | return NO; | |
337 | } | |
338 | ||
339 | ok = [session apply]; | |
340 | if (!ok) { | |
341 | SCTestLog("Failed to apply policy"); | |
342 | return NO; | |
343 | } | |
344 | ||
345 | SCNetworkReachabilityGetFlags(test.target, &flags); | |
346 | if (flags != 0) { | |
347 | SCTestLog("Reachability reported as reachable, in presence of a drop policy"); | |
348 | return NO; | |
349 | } | |
350 | ||
351 | ok = [session removeAllPolicies]; | |
352 | if (!ok) { | |
353 | SCTestLog("Failed to remove policies from session"); | |
354 | return NO; | |
355 | } | |
356 | ||
357 | ok = [session apply]; | |
358 | if (!ok) { | |
359 | SCTestLog("Failed to apply policy"); | |
360 | return NO; | |
361 | } | |
362 | ||
363 | SCNetworkReachabilityGetFlags(test.target, &flags); | |
364 | if (flags == 0) { | |
365 | SCTestLog("Reachability reported as not reachable, in absence of a drop policy"); | |
366 | return NO; | |
367 | } | |
368 | ||
369 | SCTestLog("Verified that SCNetworkReachability for scoped queries report reachability corresponding to the NECP Policies"); | |
370 | return YES; | |
371 | } | |
372 | ||
373 | - (BOOL)tearDown | |
374 | { | |
375 | return YES; | |
376 | } | |
377 | ||
378 | @end |