2 * Copyright (c) 2016, 2017 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
25 #import "SCTestUtils.h"
27 #import <NetworkExtension/NEPolicySession.h>
28 #import <network_information.h>
30 #define REACHABILITY_TEST_HOSTNAME "apple.com"
32 @interface SCTestReachability : SCTest
33 @property SCNetworkReachabilityRef target;
34 @property dispatch_queue_t callbackQ;
37 @implementation SCTestReachability
41 return @"reachability";
44 + (NSString *)commandDescription
46 return @"Tests the SCNetworkReachability code path";
49 - (instancetype)initWithOptions:(NSDictionary *)options
51 self = [super initWithOptions:options];
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) {
61 NSString *addressString = self.options[kSCTestReachabilityAddress];
62 struct sockaddr *sa = _SC_string_to_sockaddr(addressString.UTF8String, AF_INET, NULL, 0);
64 sa = _SC_string_to_sockaddr(addressString.UTF8String, AF_INET6, NULL, 0);
66 SCTestLog("Invalid address");
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];
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);
83 sa = _SC_string_to_sockaddr(addressString.UTF8String, AF_INET6, NULL, 0);
85 SCTestLog("Invalid address");
89 _target = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, sa);
97 if (self.target != NULL) {
98 CFRelease(self.target);
104 myReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info)
106 #pragma unused(target)
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);
117 if (self.options[kSCTestReachabilityHost] != nil && self.options[kSCTestReachabilityAddress] != nil) {
118 SCTestLog("Please specify either a host or address");
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);
127 SCNetworkReachabilityFlags flags;
128 SCNetworkReachabilityGetFlags(self.target, &flags);
129 SCTestLog("Reachability: %#x", flags);
130 [self cleanupAndExitWithErrorCode:0];
134 - (void)cleanupAndExitWithErrorCode:(int)error
136 [super cleanupAndExitWithErrorCode:error];
139 - (NSString *)primaryInterfaceName
143 nwi_ifstate_t ifstate;
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");
157 name = nwi_ifstate_get_ifname(ifstate);
158 nsName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
159 nwi_state_release(nwi);
175 BOOL allUnitTestsPassed = YES;
177 allUnitTestsPassed &= [self unitTestBasicReachabilityCheck];
178 allUnitTestsPassed &= [self unitTestReachabilityWithPolicy];
179 allUnitTestsPassed &= [self unitTestScopedReachabilityWithPolicy];
181 if(![self tearDown]) {
185 return allUnitTestsPassed;
188 - (BOOL)unitTestBasicReachabilityCheck
190 SCNetworkReachabilityFlags flags;
191 SCTestReachability *test;
193 test = [[SCTestReachability alloc] initWithOptions:self.options];
194 if ([test primaryInterfaceName] == nil) {
198 if (test.target == NULL) {
199 NSDictionary *options = @{kSCTestReachabilityHost:@REACHABILITY_TEST_HOSTNAME};
200 test = [[SCTestReachability alloc] initWithOptions:options];
204 SCNetworkReachabilityGetFlags(test.target, &flags);
206 SCTestLog("Reachability reported not reachable");
210 SCTestLog("Verified that basic reachability check succeeds");
214 - (BOOL)unitTestReachabilityWithPolicy
216 NEPolicyCondition *condition1;
217 NEPolicyCondition *condition2;
218 SCNetworkReachabilityFlags flags;
222 NEPolicyResult *result;
223 NEPolicySession *session;
224 SCTestReachability *test;
226 test = [[SCTestReachability alloc] initWithOptions:self.options];
227 if ([test primaryInterfaceName] == nil) {
231 if (test.target == NULL) {
232 NSDictionary *options = @{kSCTestReachabilityHost:@REACHABILITY_TEST_HOSTNAME};
233 test = [[SCTestReachability alloc] initWithOptions:options];
236 SCNetworkReachabilityGetFlags(test.target, &flags);
238 SCTestLog("Reachability reported not reachable");
242 session = [[NEPolicySession alloc] init];
243 if (session == nil) {
244 SCTestLog("Failed to create NEPolicySession");
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];
254 SCTestLog("Failed to add policy");
258 ok = [session apply];
260 SCTestLog("Failed to apply policy");
264 SCNetworkReachabilityGetFlags(test.target, &flags);
266 SCTestLog("Reachability reported as reachable, in presence of a drop policy");
270 ok = [session removeAllPolicies];
272 SCTestLog("Failed to remove policies from session");
276 ok = [session apply];
278 SCTestLog("Failed to apply policy");
282 SCNetworkReachabilityGetFlags(test.target, &flags);
284 SCTestLog("Reachability reported as not reachable, in absence of a drop policy");
288 SCTestLog("Verified that SCNetworkReachability reports reachability corresponding to the NECP Policies");
292 - (BOOL)unitTestScopedReachabilityWithPolicy
294 NEPolicyCondition *condition1;
295 NEPolicyCondition *condition2;
296 SCNetworkReachabilityFlags flags;
300 NSString *primaryInterface;
301 NEPolicyResult *result;
302 NEPolicySession *session;
303 SCTestReachability *test;
305 test = [[SCTestReachability alloc] initWithOptions:self.options];
306 primaryInterface = [test primaryInterfaceName];
307 if (primaryInterface == nil) {
311 if (test.target == NULL) {
312 NSDictionary *options = @{kSCTestReachabilityHost:@REACHABILITY_TEST_HOSTNAME,
313 kSCTestReachabilityInterface:primaryInterface};
314 test = [[SCTestReachability alloc] initWithOptions:options];
317 SCNetworkReachabilityGetFlags(test.target, &flags);
319 SCTestLog("Reachability reported not reachable");
323 session = [[NEPolicySession alloc] init];
324 if (session == nil) {
325 SCTestLog("Failed to create NEPolicySession");
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];
335 SCTestLog("Failed to add policy");
339 ok = [session apply];
341 SCTestLog("Failed to apply policy");
345 SCNetworkReachabilityGetFlags(test.target, &flags);
347 SCTestLog("Reachability reported as reachable, in presence of a drop policy");
351 ok = [session removeAllPolicies];
353 SCTestLog("Failed to remove policies from session");
357 ok = [session apply];
359 SCTestLog("Failed to apply policy");
363 SCNetworkReachabilityGetFlags(test.target, &flags);
365 SCTestLog("Reachability reported as not reachable, in absence of a drop policy");
369 SCTestLog("Verified that SCNetworkReachability for scoped queries report reachability corresponding to the NECP Policies");