2 * Copyright (c) 2016 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"
26 #import <Network/Network_Private.h>
27 #import <CoreFoundation/CFXPCBridge.h>
28 #import <config_agent_info.h>
30 #define TEST_DOMAIN "filemaker.com"
31 #define PATH_QUIESCE_TIME 1.5 // seconds
33 @interface SCTestConfigAgent : SCTest
34 @property NSString *serviceID;
35 @property NSString *proxyKey;
36 @property NSString *dnsKey;
37 @property NSArray<NSArray<NSDictionary *> *> *testProxy;
38 @property NSArray<NWEndpoint *> *testDNS;
39 @property SCDynamicStoreRef store;
40 @property (copy) NSArray<NSArray<NSDictionary *> *> *pathProxy;
41 @property (copy) NSArray<NWEndpoint *> *pathDNS;
44 @implementation SCTestConfigAgent
46 - (instancetype)initWithOptions:(NSDictionary *)options
48 self = [super initWithOptions:options];
50 _serviceID = @"8F66B505-EAEF-4611-BD4D-C523FD9451F0";
51 _store = SCDynamicStoreCreate(kCFAllocatorDefault,
55 _proxyKey = (__bridge_transfer NSString *)[self copyStateKeyWithServiceID:(__bridge CFStringRef)(self.serviceID) forEntity:kSCEntNetProxies];
56 _dnsKey = (__bridge_transfer NSString *)[self copyStateKeyWithServiceID:(__bridge CFStringRef)(self.serviceID) forEntity:kSCEntNetDNS];
63 if (self.store != NULL) {
64 CFRelease(self.store);
71 return @"config_agent";
74 + (NSString *)commandDescription
76 return @"Tests the Config Agent code path";
81 if (self.options[kSCTestConfigAgentRemoveProxy]) {
82 [self removeFromSCDynamicStore:self.proxyKey];
85 if (self.options[kSCTestConfigAgentRemoveDNS]) {
86 [self removeFromSCDynamicStore:self.dnsKey];
89 NSDictionary *proxyConfig = [self parseProxyAgentOptions];
90 if (proxyConfig != nil) {
91 [self publishToSCDynamicStore:self.proxyKey value:proxyConfig];
92 self.testProxy = @[@[proxyConfig]];
95 NSDictionary *dnsConfig = [self parseDNSAgentOptions];
96 if (dnsConfig != nil) {
97 [self publishToSCDynamicStore:self.dnsKey value:dnsConfig];
98 self.testDNS = [self createDNSArray:dnsConfig];
101 [self cleanupAndExitWithErrorCode:0];
104 - (CFStringRef)copyStateKeyWithServiceID:(CFStringRef)serviceID
105 forEntity:(CFStringRef)entity
107 return SCDynamicStoreKeyCreateNetworkServiceEntity(kCFAllocatorDefault,
108 kSCDynamicStoreDomainState,
113 - (NSArray<NWEndpoint *> *)createDNSArray:(NSDictionary *)dnsConfig
115 NSArray<NSString *> *dnsServers;
116 NSMutableArray<NWEndpoint *> *dnsArray;
118 dnsServers = [dnsConfig objectForKey:(__bridge NSString *)kSCPropNetDNSServerAddresses];
119 if (dnsServers == nil || [dnsServers count] == 0) {
123 dnsArray = [[NSMutableArray alloc] init];
124 for (NSString *server in dnsServers) {
125 NWEndpoint *endpoint = (NWEndpoint *)[NWAddressEndpoint endpointWithHostname:server port:@"0"];
126 [dnsArray addObject:endpoint];
132 - (void)publishToSCDynamicStore:(NSString *)key
133 value:(NSDictionary *)value
136 BOOL ok = SCDynamicStoreSetValue(self.store, (__bridge CFStringRef)key, (__bridge CFPropertyListRef _Nonnull)(value));
138 int error = SCError();
139 if (error == kSCStatusNoKey) {
142 SCTestLog("Could not set in SCDynamicStore: Error: %s", SCErrorString(error));
147 - (void)removeFromSCDynamicStore:(NSString *)key
149 BOOL ok = SCDynamicStoreRemoveValue(self.store, (__bridge CFStringRef)key);
151 int error = SCError();
152 if (error == kSCStatusNoKey) {
155 SCTestLog("Could not remove key: %@, Error: %s", key, SCErrorString(error));
160 - (NSDictionary *)parseProxyAgentOptions
162 NSMutableDictionary *proxyConfig = [[NSMutableDictionary alloc] init];
164 #define NS_NUMBER(x) [NSNumber numberWithInt:x]
166 #define SET_PROXY_CONFIG(proxyType) \
168 if (self.options[kSCTestConfigAgent ## proxyType ## Proxy] != nil) { \
169 NSString *serverAndPortString = self.options[kSCTestConfigAgent ## proxyType ## Proxy]; \
170 NSArray<NSString *> *serverAndPortArray = [serverAndPortString componentsSeparatedByString:@":"]; \
171 if ([serverAndPortArray count] != 2) { \
172 SCTestLog("server address or port missing"); \
175 NSString *server = [serverAndPortArray objectAtIndex:0]; \
176 NSString *port = [serverAndPortArray objectAtIndex:1]; \
177 [proxyConfig setObject:server forKey:(__bridge NSString *)kSCPropNetProxies ## proxyType ## Proxy]; \
178 [proxyConfig setObject:NS_NUMBER(port.intValue) forKey:(__bridge NSString *)kSCPropNetProxies ## proxyType ## Port]; \
179 [proxyConfig setObject:NS_NUMBER(1) forKey:(__bridge NSString *)kSCPropNetProxies ## proxyType ## Enable]; \
183 SET_PROXY_CONFIG(HTTP);
184 SET_PROXY_CONFIG(HTTPS);
185 SET_PROXY_CONFIG(FTP);
186 SET_PROXY_CONFIG(Gopher);
187 SET_PROXY_CONFIG(SOCKS);
189 if ([proxyConfig count] > 0) {
190 NSString *matchDomain = self.options[kSCTestConfigAgentProxyMatchDomain] ? self.options[kSCTestConfigAgentProxyMatchDomain] : @TEST_DOMAIN;
191 [proxyConfig setObject:@[matchDomain] forKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains];
197 #undef SET_PROXY_CONFIG
200 - (NSDictionary *)parseDNSAgentOptions
202 NSMutableDictionary *dnsConfig;
203 NSString *dnsServerString;
204 NSString *dnsDomainString;
205 NSArray<NSString *> *dnsServers;
206 NSArray<NSString *> *dnsDomains;
208 dnsConfig = [[NSMutableDictionary alloc] init];
209 dnsServerString = self.options[kSCTestConfigAgentDNSServers];
210 if (dnsServerString == nil) {
214 dnsDomainString = self.options[kSCTestConfigAgentDNSDomains];
215 if (dnsDomainString == nil) {
216 dnsDomainString = @TEST_DOMAIN;
219 dnsServers = [dnsServerString componentsSeparatedByString:@","];
220 [dnsConfig setObject:dnsServers forKey:(__bridge NSString *)kSCPropNetDNSServerAddresses];
222 dnsDomains = [dnsDomainString componentsSeparatedByString:@","];
223 [dnsConfig setObject:dnsDomains forKey:(__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains];
228 - (void)cleanupAndExitWithErrorCode:(int)error
230 CFRelease(self.store);
231 [super cleanupAndExitWithErrorCode:error];
245 BOOL allUnitTestsPassed = YES;
246 allUnitTestsPassed &= [self unitTestInstallProxy];
247 allUnitTestsPassed &= [self unitTestInstallProxyWithLargeConfig];
248 allUnitTestsPassed &= [self unitTestInstallProxyWithConflictingDomain];
249 allUnitTestsPassed &= [self unitTestInstallDNS];
250 allUnitTestsPassed &= [self unitTestInstallDNSWithConflictingDomain];
252 if(![self tearDown]) {
256 return allUnitTestsPassed;
259 - (BOOL)unitTestInstallProxy
262 SCTestConfigAgent *test;
263 NSDictionary *proxyConfig;
266 NWHostEndpoint *hostEndpoint;
267 NWPathEvaluator *pathEvaluator;
268 NSMutableDictionary *dict;
270 test = [[SCTestConfigAgent alloc] initWithOptions:self.options];
271 proxyConfig = [test parseProxyAgentOptions];
272 if (proxyConfig == nil) {
273 // Use default options
274 proxyConfig = @{(__bridge NSString *)kSCPropNetProxiesHTTPEnable:@(1),
275 (__bridge NSString *)kSCPropNetProxiesHTTPPort:@(80),
276 (__bridge NSString *)kSCPropNetProxiesHTTPProxy:@"10.10.10.100",
277 (__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains:@[@TEST_DOMAIN],
281 hostname = [[proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains] objectAtIndex:0];
282 port = [proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesHTTPPort];
283 hostEndpoint = [NWHostEndpoint endpointWithHostname:hostname port:port.stringValue];
284 pathEvaluator = [[NWPathEvaluator alloc] initWithEndpoint:hostEndpoint parameters:NULL];
285 [pathEvaluator addObserver:test
287 options:NSKeyValueObservingOptionNew
291 [test publishToSCDynamicStore:test.proxyKey value:proxyConfig];
292 dict = [NSMutableDictionary dictionaryWithDictionary:proxyConfig];
293 [dict removeObjectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains];
294 test.testProxy = @[@[dict]];
296 // Wait for the path changes to quiesce
297 [test waitFor:PATH_QUIESCE_TIME];
298 if (![test.testProxy isEqualToArray:test.pathProxy]) {
299 SCTestLog("test proxy and applied proxy do not match. Test: %@, Applied: %@", test.testProxy, test.pathProxy);
303 SCTestLog("Verified the configured proxy is the same as applied proxy");
304 [test removeFromSCDynamicStore:test.proxyKey];
305 test.testProxy = nil;
306 // Wait for the path changes to quiesce
307 [test waitFor:PATH_QUIESCE_TIME];
308 if (test.pathProxy != nil) {
309 SCTestLog("proxy applied when there is no test proxy");
316 [pathEvaluator removeObserver:test
322 - (BOOL)unitTestInstallDNS
325 SCTestConfigAgent *test;
326 NSDictionary *dnsConfig;
328 NWHostEndpoint *hostEndpoint;
329 NWPathEvaluator *pathEvaluator;
331 test = [[SCTestConfigAgent alloc] initWithOptions:self.options];
332 dnsConfig = [test parseDNSAgentOptions];
333 if (dnsConfig == nil) {
334 dnsConfig = @{ (__bridge NSString *)kSCPropNetDNSServerAddresses:@[@"10.10.10.101", @"10.10.10.102", @"10.10.10.103"],
335 (__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains:@[@TEST_DOMAIN],
339 hostname = [[dnsConfig objectForKey:(__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains] objectAtIndex:0];
340 hostEndpoint = [NWHostEndpoint endpointWithHostname:hostname port:@"80"];
341 pathEvaluator = [[NWPathEvaluator alloc] initWithEndpoint:hostEndpoint parameters:NULL];
342 [pathEvaluator addObserver:test
344 options:NSKeyValueObservingOptionNew
348 [test publishToSCDynamicStore:test.dnsKey value:dnsConfig];
349 test.testDNS = [test createDNSArray:dnsConfig];
351 // Wait for the path changes to quiesce
352 [test waitFor:PATH_QUIESCE_TIME];
353 if (![test.testDNS isEqualToArray:test.pathDNS]) {
354 SCTestLog("test DNS and applied DNS do not match. Test: %@, Applied: %@", test.testDNS, test.pathDNS);
358 [test removeFromSCDynamicStore:test.dnsKey];
360 [test waitFor:PATH_QUIESCE_TIME];
362 SCTestLog("Verified that the configured DNS is same as applied DNS for a domain");
366 [pathEvaluator removeObserver:test
372 - (BOOL)unitTestInstallProxyWithLargeConfig
375 SCTestConfigAgent *test;
376 NSString *str = @"0123456789";
377 NSMutableString *largeStr;
378 NSDictionary *proxyConfig;
381 NWHostEndpoint *hostEndpoint;
382 NWPathEvaluator *pathEvaluator;
383 NSMutableDictionary *dict;
385 test = [[SCTestConfigAgent alloc] initWithOptions:self.options];
386 largeStr = [[NSMutableString alloc] init];
387 for (int i = 0; i < 200; i++) {
388 [largeStr appendString:str];
391 // We imitate a proxy config worth 2K bytes.
392 proxyConfig = @{(__bridge NSString *)kSCPropNetProxiesHTTPEnable:@(1),
393 (__bridge NSString *)kSCPropNetProxiesHTTPPort:@(80),
394 (__bridge NSString *)kSCPropNetProxiesHTTPProxy:@"10.10.10.100",
395 (__bridge NSString *)kSCPropNetProxiesProxyAutoConfigJavaScript:largeStr,
396 (__bridge NSString *)kSCPropNetProxiesProxyAutoConfigEnable:@(1),
397 (__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains:@[@TEST_DOMAIN],
400 hostname = [[proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains] objectAtIndex:0];
401 port = [proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesHTTPPort];
402 hostEndpoint = [NWHostEndpoint endpointWithHostname:hostname port:port.stringValue];
403 pathEvaluator = [[NWPathEvaluator alloc] initWithEndpoint:hostEndpoint parameters:NULL];
404 [pathEvaluator addObserver:test
406 options:NSKeyValueObservingOptionNew
410 [test publishToSCDynamicStore:test.proxyKey value:proxyConfig];
411 dict = [NSMutableDictionary dictionaryWithDictionary:proxyConfig];
412 [dict removeObjectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains];
413 test.testProxy = @[@[dict]];
415 // Wait for the path changes to quiesce
416 [test waitFor:PATH_QUIESCE_TIME];
417 if ([test.testProxy isEqualToArray:test.pathProxy]) {
418 SCTestLog("applied proxy does not contain Out of Band Agent UUID");
422 // Now we verify that we are able to fetch the proxy configuration from configd
423 for (NSArray<NSDictionary *> *config in test.pathProxy) {
424 xpc_object_t xpcConfig = _CFXPCCreateXPCObjectFromCFObject((__bridge CFArrayRef)config);
425 xpc_object_t fetchedConfig = config_agent_update_proxy_information(xpcConfig);
426 if (fetchedConfig != nil) {
427 NSArray *nsConfig = (__bridge_transfer NSArray *)(_CFXPCCreateCFObjectFromXPCObject(fetchedConfig));
428 test.pathProxy = @[nsConfig];
433 if (![test.testProxy isEqualToArray:test.pathProxy]) {
434 SCTestLog("Could not fetch proxy configuration from configd. Test: %@, Applied: %@", test.testProxy, test.pathProxy);
438 SCTestLog("Verified that the proxy configuration is successfully fetched from configd");
439 test.testProxy = nil;
440 [test removeFromSCDynamicStore:test.proxyKey];
442 // Wait for the path changes to quiesce
443 [test waitFor:PATH_QUIESCE_TIME];
444 if (test.pathProxy != nil) {
445 SCTestLog("proxy applied when there is no test proxy");
452 [pathEvaluator removeObserver:test
458 - (BOOL)unitTestInstallDNSWithConflictingDomain
461 SCTestConfigAgent *test;
462 NSDictionary *dnsConfig;
464 NWHostEndpoint *hostEndpoint;
465 NWPathEvaluator *pathEvaluator;
467 test = [[SCTestConfigAgent alloc] initWithOptions:self.options];
468 dnsConfig = @{ (__bridge NSString *)kSCPropNetDNSServerAddresses:@[@"10.10.10.101", @"10.10.10.102", @"10.10.10.103"],
469 (__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains:@[@TEST_DOMAIN],
472 hostname = [[dnsConfig objectForKey:(__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains] objectAtIndex:0];
473 hostEndpoint = [NWHostEndpoint endpointWithHostname:hostname port:@"80"];
474 pathEvaluator = [[NWPathEvaluator alloc] initWithEndpoint:hostEndpoint parameters:NULL];
475 [pathEvaluator addObserver:test
477 options:NSKeyValueObservingOptionNew
481 NSDictionary *duplicateDnsConfig;
482 NSString *anotherFakeServiceID;
483 NSString *anotherDNSKey;
488 [test publishToSCDynamicStore:test.dnsKey value:dnsConfig];
489 test.testDNS = [test createDNSArray:dnsConfig];
491 // Wait for the path changes to quiesce
492 [test waitFor:PATH_QUIESCE_TIME];
493 if (![test.testDNS isEqualToArray:test.pathDNS]) {
494 SCTestLog("test DNS and applied DNS do not match. Test: %@, Applied: %@", test.testDNS, test.pathDNS);
498 // Now install the conflicting DNS configuration
499 duplicateDnsConfig = @{ (__bridge NSString *)kSCPropNetDNSServerAddresses:@[@"10.10.10.104", @"10.10.10.105", @"10.10.10.106"],
500 (__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains:@[@TEST_DOMAIN],
503 anotherFakeServiceID = [NSUUID UUID].UUIDString;
504 anotherDNSKey = (__bridge_transfer NSString *)[self copyStateKeyWithServiceID:(__bridge CFStringRef)(anotherFakeServiceID) forEntity:kSCEntNetDNS];
506 [test publishToSCDynamicStore:anotherDNSKey value:duplicateDnsConfig];
507 array = [test.testDNS arrayByAddingObjectsFromArray:[test createDNSArray:duplicateDnsConfig]];
508 test.testDNS = array;
510 // Wait for the path changes to quiesce
511 [test waitFor:PATH_QUIESCE_TIME];
513 // Use NSSet for unordered comparison
514 testDNSSet = [NSSet setWithArray:test.testDNS];
515 pathDNSSet = [NSSet setWithArray:test.pathDNS];
516 success = [testDNSSet isEqualToSet:pathDNSSet];
517 [test removeFromSCDynamicStore:anotherDNSKey];
519 SCTestLog("test DNS and applied DNS for duplicate domains do not match. Test: %@, Applied: %@", test.testDNS, test.pathDNS);
523 [test removeFromSCDynamicStore:test.dnsKey];
525 [test waitFor:PATH_QUIESCE_TIME];
527 SCTestLog("Verified that the configured DNS with duplicate domains is same as applied DNS for a domain");
532 [pathEvaluator removeObserver:test
538 - (BOOL)unitTestInstallProxyWithConflictingDomain
541 SCTestConfigAgent *test;
542 NSDictionary *proxyConfig;
545 NWHostEndpoint *hostEndpoint;
546 NWPathEvaluator *pathEvaluator;
548 test = [[SCTestConfigAgent alloc] initWithOptions:self.options];
549 proxyConfig = @{(__bridge NSString *)kSCPropNetProxiesHTTPEnable:@(1),
550 (__bridge NSString *)kSCPropNetProxiesHTTPPort:@(80),
551 (__bridge NSString *)kSCPropNetProxiesHTTPProxy:@"10.10.10.100",
552 (__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains:@[@TEST_DOMAIN],
555 hostname = [[proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains] objectAtIndex:0];
556 port = [proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesHTTPPort];
557 hostEndpoint = [NWHostEndpoint endpointWithHostname:hostname port:port.stringValue];
558 pathEvaluator = [[NWPathEvaluator alloc] initWithEndpoint:hostEndpoint parameters:NULL];
559 [pathEvaluator addObserver:test
561 options:NSKeyValueObservingOptionNew
565 NSMutableDictionary *dict;
566 NSMutableDictionary *dict2;
567 NSDictionary *duplicateProxyConfig;
568 NSString *anotherFakeServiceID;
569 NSString *anotherProxyKey;
573 [test publishToSCDynamicStore:test.proxyKey value:proxyConfig];
574 dict = [NSMutableDictionary dictionaryWithDictionary:proxyConfig];
575 [dict removeObjectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains];
576 test.testProxy = @[@[dict]];
578 // Wait for the path changes to quiesce
579 [test waitFor:PATH_QUIESCE_TIME];
580 if (![test.testProxy isEqualToArray:test.pathProxy]) {
581 SCTestLog("test proxy and applied proxy do not match. Test: %@, Applied: %@", test.testProxy, test.pathProxy);
585 // Now install the conflicting Proxy configuration
586 duplicateProxyConfig = @{(__bridge NSString *)kSCPropNetProxiesHTTPSEnable:@(1),
587 (__bridge NSString *)kSCPropNetProxiesHTTPSPort:@(8080),
588 (__bridge NSString *)kSCPropNetProxiesHTTPSProxy:@"10.10.10.101",
589 (__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains:@[@TEST_DOMAIN],
591 anotherFakeServiceID = [NSUUID UUID].UUIDString;
592 anotherProxyKey = (__bridge_transfer NSString *)[self copyStateKeyWithServiceID:(__bridge CFStringRef)(anotherFakeServiceID) forEntity:kSCEntNetProxies];
594 [test publishToSCDynamicStore:anotherProxyKey value:duplicateProxyConfig];;
595 dict2 = [NSMutableDictionary dictionaryWithDictionary:duplicateProxyConfig];
596 [dict2 removeObjectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains];
597 test.testProxy = @[@[dict],@[dict2]];
599 // Wait for the path changes to quiesce
600 [test waitFor:PATH_QUIESCE_TIME];
602 // Use NSSet for unordered comparison
603 testProxySet = [NSSet setWithArray:test.testProxy];
604 pathProxySet = [NSSet setWithArray:test.pathProxy];
605 success = [testProxySet isEqualToSet:pathProxySet];
606 [test removeFromSCDynamicStore:anotherProxyKey];
608 SCTestLog("test proxy and applied proxy for duplicate domains do not match. Test: %@, Applied: %@", test.testDNS, test.pathDNS);
612 SCTestLog("Verified the configured proxy with Duplicate domains is the same as applied proxy");
614 [test removeFromSCDynamicStore:test.proxyKey];
615 test.testProxy = nil;
617 // Wait for the path changes to quiesce
618 [test waitFor:PATH_QUIESCE_TIME];
620 if (test.pathProxy != nil) {
621 SCTestLog("proxy applied when there is no test proxy");
629 [pathEvaluator removeObserver:test
637 [self removeFromSCDynamicStore:self.proxyKey];
638 [self removeFromSCDynamicStore:self.dnsKey];
642 - (void)observeValueForKeyPath:(NSString *)keyPath
644 change:(NSDictionary *)change
645 context:(void *)context
647 NWPathEvaluator *pathEvaluator = (NWPathEvaluator *)object;
648 if ([keyPath isEqualToString:@"path"]) {
649 self.pathProxy = pathEvaluator.path.proxySettings;
650 self.pathDNS = pathEvaluator.path.dnsServers;