SCTestConfigAgents.m   [plain text]


/*
 * Copyright (c) 2016, 2017 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

#import "SCTest.h"
#import "SCTestUtils.h"
#import <Network/Network_Private.h>
#import <CoreFoundation/CFXPCBridge.h>
#import <config_agent_info.h>

#define TEST_DOMAIN	"filemaker.com"
#define PATH_QUIESCE_TIME 1.5 // seconds

@interface SCTestConfigAgent : SCTest
@property NSString *serviceID;
@property NSString *proxyKey;
@property NSString *dnsKey;
@property NSArray<NSArray<NSDictionary *> *> *testProxy;
@property NSArray<NWEndpoint *> *testDNS;
@property SCDynamicStoreRef store;
@property (copy) NSArray<NSArray<NSDictionary *> *> *pathProxy;
@property (copy) NSArray<NWEndpoint *> *pathDNS;
@end

@implementation SCTestConfigAgent

- (instancetype)initWithOptions:(NSDictionary *)options
{
	self = [super initWithOptions:options];
	if (self) {
		_serviceID = @"8F66B505-EAEF-4611-BD4D-C523FD9451F0";
		_store = SCDynamicStoreCreate(kCFAllocatorDefault,
						CFSTR("SCTest"),
						NULL,
						NULL);
		_proxyKey = (__bridge_transfer NSString *)[self copyStateKeyWithServiceID:(__bridge CFStringRef)(self.serviceID) forEntity:kSCEntNetProxies];
		_dnsKey = (__bridge_transfer NSString *)[self copyStateKeyWithServiceID:(__bridge CFStringRef)(self.serviceID) forEntity:kSCEntNetDNS];
	}
	return self;
}

- (void)dealloc
{
	if (self.store != NULL) {
		CFRelease(self.store);
		self.store = NULL;
	}
}

+ (NSString *)command
{
	return @"config_agent";
}

+ (NSString *)commandDescription
{
	return @"Tests the Config Agent code path";
}

- (void)start
{
	if (self.options[kSCTestConfigAgentRemoveProxy]) {
		[self removeFromSCDynamicStore:self.proxyKey];
	}

	if (self.options[kSCTestConfigAgentRemoveDNS]) {
		[self removeFromSCDynamicStore:self.dnsKey];
	}

	NSDictionary *proxyConfig = [self parseProxyAgentOptions];
	if (proxyConfig != nil) {
		[self publishToSCDynamicStore:self.proxyKey value:proxyConfig];
		self.testProxy = @[@[proxyConfig]];
	}

	NSDictionary *dnsConfig = [self parseDNSAgentOptions];
	if (dnsConfig != nil) {
		[self publishToSCDynamicStore:self.dnsKey value:dnsConfig];
		self.testDNS = [self createDNSArray:dnsConfig];
	}

	[self cleanupAndExitWithErrorCode:0];
}

- (CFStringRef)copyStateKeyWithServiceID:(CFStringRef)serviceID
				 forEntity:(CFStringRef)entity
{
	return SCDynamicStoreKeyCreateNetworkServiceEntity(kCFAllocatorDefault,
							   kSCDynamicStoreDomainState,
							   serviceID,
							   entity);
}

- (NSArray<NWEndpoint *> *)createDNSArray:(NSDictionary *)dnsConfig
{
	NSArray<NSString *> *dnsServers;
	NSMutableArray<NWEndpoint *> *dnsArray;

	dnsServers = [dnsConfig objectForKey:(__bridge NSString *)kSCPropNetDNSServerAddresses];
	if (dnsServers == nil || [dnsServers count] == 0) {
		return nil;
	}

	dnsArray = [[NSMutableArray alloc] init];
	for (NSString *server in dnsServers) {
		NWEndpoint *endpoint = (NWEndpoint *)[NWAddressEndpoint endpointWithHostname:server port:@"0"];
		[dnsArray addObject:endpoint];
	}

	return dnsArray;
}

- (void)publishToSCDynamicStore:(NSString *)key
			  value:(NSDictionary *)value
{

	BOOL ok = SCDynamicStoreSetValue(self.store, (__bridge CFStringRef)key, (__bridge CFPropertyListRef _Nonnull)(value));
	if (!ok) {
		int error = SCError();
		if (error == kSCStatusNoKey) {
			return;
		}
		SCTestLog("Could not set in SCDynamicStore: Error: %s", SCErrorString(error));
		return;
	}
}

- (void)removeFromSCDynamicStore:(NSString *)key
{
	BOOL ok = SCDynamicStoreRemoveValue(self.store, (__bridge CFStringRef)key);
	if (!ok) {
		int error = SCError();
		if (error == kSCStatusNoKey) {
			return;
		}
		SCTestLog("Could not remove key: %@, Error: %s", key, SCErrorString(error));
		return;
	}
}

- (NSDictionary *)parseProxyAgentOptions
{
	NSMutableDictionary *proxyConfig = [[NSMutableDictionary alloc] init];

#define NS_NUMBER(x) [NSNumber numberWithInt:x]

#define SET_PROXY_CONFIG(proxyType)														\
	do {																	\
		if (self.options[kSCTestConfigAgent ## proxyType ## Proxy] != nil) {								\
			NSString *serverAndPortString = self.options[kSCTestConfigAgent ## proxyType ## Proxy];					\
			NSArray<NSString *> *serverAndPortArray = [serverAndPortString componentsSeparatedByString:@":"];			\
			if ([serverAndPortArray count] != 2) {											\
				SCTestLog("server address or port missing");									\
				ERR_EXIT;													\
			}															\
			NSString *server = [serverAndPortArray objectAtIndex:0];								\
			NSString *port = [serverAndPortArray objectAtIndex:1];									\
			[proxyConfig setObject:server forKey:(__bridge NSString *)kSCPropNetProxies ## proxyType ## Proxy];			\
			[proxyConfig setObject:NS_NUMBER(port.intValue) forKey:(__bridge NSString *)kSCPropNetProxies ## proxyType ## Port];	\
			[proxyConfig setObject:NS_NUMBER(1) forKey:(__bridge NSString *)kSCPropNetProxies ## proxyType ## Enable];		\
		}																\
	} while(0);

	SET_PROXY_CONFIG(HTTP);
	SET_PROXY_CONFIG(HTTPS);
	SET_PROXY_CONFIG(FTP);
	SET_PROXY_CONFIG(Gopher);
	SET_PROXY_CONFIG(SOCKS);

	if ([proxyConfig count] > 0) {

		NSArray<NSString *> *domains = nil;
		NSString *matchDomains = self.options[kSCTestConfigAgentProxyMatchDomain];
		if (matchDomains == nil) {
			domains = @[@TEST_DOMAIN];
		} else {
			domains = [matchDomains componentsSeparatedByString:@","];
		}

		[proxyConfig setObject:domains forKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains];
	} else {
		proxyConfig = nil;
	}

	return proxyConfig;
#undef SET_PROXY_CONFIG
}

- (NSDictionary *)parseDNSAgentOptions
{
	NSMutableDictionary *dnsConfig;
	NSString *dnsServerString;
	NSString *dnsDomainString;
	NSArray<NSString *> *dnsServers;
	NSArray<NSString *> *dnsDomains;

	dnsConfig = [[NSMutableDictionary alloc] init];
	dnsServerString = self.options[kSCTestConfigAgentDNSServers];
	if (dnsServerString == nil) {
		return nil;
	}

	dnsDomainString = self.options[kSCTestConfigAgentDNSDomains];
	if (dnsDomainString == nil) {
		dnsDomainString = @TEST_DOMAIN;
	}

	dnsServers = [dnsServerString componentsSeparatedByString:@","];
	[dnsConfig setObject:dnsServers forKey:(__bridge NSString *)kSCPropNetDNSServerAddresses];

	dnsDomains = [dnsDomainString componentsSeparatedByString:@","];
	[dnsConfig setObject:dnsDomains forKey:(__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains];

	return dnsConfig;
}

- (void)cleanupAndExitWithErrorCode:(int)error
{
	CFRelease(self.store);
	[super cleanupAndExitWithErrorCode:error];
}

- (BOOL)setup
{
	return YES;
}

- (BOOL)unitTest
{
	if(![self setup]) {
		return NO;
	}

	BOOL allUnitTestsPassed = YES;
	allUnitTestsPassed &= [self unitTestInstallProxy];
	allUnitTestsPassed &= [self unitTestInstallProxyWithLargeConfig];
	allUnitTestsPassed &= [self unitTestInstallProxyWithConflictingDomain];
	allUnitTestsPassed &= [self unitTestInstallDNS];
	allUnitTestsPassed &= [self unitTestInstallDNSWithConflictingDomain];

	if(![self tearDown]) {
		return NO;
	}

	return allUnitTestsPassed;
}

- (BOOL)unitTestInstallProxy
{
	BOOL success = NO;
	SCTestConfigAgent *test;
	NSDictionary *proxyConfig;
	NSString *hostname;
	NSNumber *port;
	NWHostEndpoint *hostEndpoint;
	NWPathEvaluator *pathEvaluator;
	NSMutableDictionary *dict;

	test = [[SCTestConfigAgent alloc] initWithOptions:self.options];
	proxyConfig = [test parseProxyAgentOptions];
	if (proxyConfig == nil) {
		// Use default options
		proxyConfig = @{(__bridge NSString *)kSCPropNetProxiesHTTPEnable:@(1),
				(__bridge NSString *)kSCPropNetProxiesHTTPPort:@(80),
				(__bridge NSString *)kSCPropNetProxiesHTTPProxy:@"10.10.10.100",
				(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains:@[@TEST_DOMAIN],
				};
	}

	hostname = [[proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains] objectAtIndex:0];
	port = [proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesHTTPPort];
	hostEndpoint = [NWHostEndpoint endpointWithHostname:hostname port:port.stringValue];
	pathEvaluator = [[NWPathEvaluator alloc] initWithEndpoint:hostEndpoint parameters:NULL];
	[pathEvaluator addObserver:test
			forKeyPath:@"path"
			   options:NSKeyValueObservingOptionNew
			   context:nil];

	do {
		[test publishToSCDynamicStore:test.proxyKey value:proxyConfig];
		dict = [NSMutableDictionary dictionaryWithDictionary:proxyConfig];
		[dict removeObjectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains];
		test.testProxy = @[@[dict]];

		// Wait for the path changes to quiesce
		[test waitFor:PATH_QUIESCE_TIME];
		if (![test.testProxy isEqualToArray:test.pathProxy]) {
			SCTestLog("test proxy and applied proxy do not match. Test: %@, Applied: %@", test.testProxy, test.pathProxy);
			break;
		}

		SCTestLog("Verified the configured proxy is the same as applied proxy");
		[test removeFromSCDynamicStore:test.proxyKey];
		test.testProxy = nil;
		// Wait for the path changes to quiesce
		[test waitFor:PATH_QUIESCE_TIME];
		if (test.pathProxy != nil) {
			SCTestLog("proxy applied when there is no test proxy");
			break;
		}

		success = YES;
	} while(0);

	[pathEvaluator removeObserver:test
			   forKeyPath:@"path"];

	return success;
}

- (BOOL)unitTestInstallDNS
{
	BOOL success = NO;
	SCTestConfigAgent *test;
	NSDictionary *dnsConfig;
	NSString *hostname;
	NWHostEndpoint *hostEndpoint;
	NWPathEvaluator *pathEvaluator;

	test = [[SCTestConfigAgent alloc] initWithOptions:self.options];
	dnsConfig = [test parseDNSAgentOptions];
	if (dnsConfig == nil) {
		dnsConfig = @{	(__bridge NSString *)kSCPropNetDNSServerAddresses:@[@"10.10.10.101", @"10.10.10.102", @"10.10.10.103"],
				(__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains:@[@TEST_DOMAIN],
				};
	}

	hostname = [[dnsConfig objectForKey:(__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains] objectAtIndex:0];
	hostEndpoint = [NWHostEndpoint endpointWithHostname:hostname port:@"80"];
	pathEvaluator = [[NWPathEvaluator alloc] initWithEndpoint:hostEndpoint parameters:NULL];
	[pathEvaluator addObserver:test
			forKeyPath:@"path"
			   options:NSKeyValueObservingOptionNew
			   context:nil];

	do {
		[test publishToSCDynamicStore:test.dnsKey value:dnsConfig];
		test.testDNS = [test createDNSArray:dnsConfig];

		// Wait for the path changes to quiesce
		[test waitFor:PATH_QUIESCE_TIME];
		if (![test.testDNS isEqualToArray:test.pathDNS]) {
			SCTestLog("test DNS and applied DNS do not match. Test: %@, Applied: %@", test.testDNS, test.pathDNS);
			break;
		}

		[test removeFromSCDynamicStore:test.dnsKey];
		test.testDNS = nil;
		[test waitFor:PATH_QUIESCE_TIME];

		SCTestLog("Verified that the configured DNS is same as applied DNS for a domain");
		success = YES;
	} while (0);

	[pathEvaluator removeObserver:test
			   forKeyPath:@"path"];

	return success;
}

- (BOOL)unitTestInstallProxyWithLargeConfig
{
	BOOL success = NO;
	SCTestConfigAgent *test;
	NSString *str = @"0123456789";
	NSMutableString *largeStr;
	NSDictionary *proxyConfig;
	NSString *hostname;
	NSNumber *port;
	NWHostEndpoint *hostEndpoint;
	NWPathEvaluator *pathEvaluator;
	NSMutableDictionary *dict;

	test = [[SCTestConfigAgent alloc] initWithOptions:self.options];
	largeStr = [[NSMutableString alloc] init];
	for (int i = 0; i < 200; i++) {
		[largeStr appendString:str];
	}

	// We imitate a proxy config worth 2K bytes.
	proxyConfig = @{(__bridge NSString *)kSCPropNetProxiesHTTPEnable:@(1),
			(__bridge NSString *)kSCPropNetProxiesHTTPPort:@(80),
			(__bridge NSString *)kSCPropNetProxiesHTTPProxy:@"10.10.10.100",
			(__bridge NSString *)kSCPropNetProxiesProxyAutoConfigJavaScript:largeStr,
			(__bridge NSString *)kSCPropNetProxiesProxyAutoConfigEnable:@(1),
			(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains:@[@TEST_DOMAIN],
			};

	hostname = [[proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains] objectAtIndex:0];
	port = [proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesHTTPPort];
	hostEndpoint = [NWHostEndpoint endpointWithHostname:hostname port:port.stringValue];
	pathEvaluator = [[NWPathEvaluator alloc] initWithEndpoint:hostEndpoint parameters:NULL];
	[pathEvaluator addObserver:test
			forKeyPath:@"path"
			   options:NSKeyValueObservingOptionNew
			   context:nil];

	do {
		[test publishToSCDynamicStore:test.proxyKey value:proxyConfig];
		dict = [NSMutableDictionary dictionaryWithDictionary:proxyConfig];
		[dict removeObjectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains];
		test.testProxy = @[@[dict]];

		// Wait for the path changes to quiesce
		[test waitFor:PATH_QUIESCE_TIME];
		if ([test.testProxy isEqualToArray:test.pathProxy]) {
			SCTestLog("applied proxy does not contain Out of Band Agent UUID");
			break;
		}

		// Now we verify that we are able to fetch the proxy configuration from configd
		for (NSArray<NSDictionary *> *config in test.pathProxy) {
			xpc_object_t xpcConfig = _CFXPCCreateXPCObjectFromCFObject((__bridge CFArrayRef)config);
			xpc_object_t fetchedConfig = config_agent_update_proxy_information(xpcConfig);
			if (fetchedConfig != nil) {
				NSArray *nsConfig = (__bridge_transfer NSArray *)(_CFXPCCreateCFObjectFromXPCObject(fetchedConfig));
				test.pathProxy = @[nsConfig];
				break;
			}
		}

		if (![test.testProxy isEqualToArray:test.pathProxy]) {
			SCTestLog("Could not fetch proxy configuration from configd. Test: %@, Applied: %@", test.testProxy, test.pathProxy);
			break;
		}

		SCTestLog("Verified that the proxy configuration is successfully fetched from configd");
		test.testProxy = nil;
		[test removeFromSCDynamicStore:test.proxyKey];

		// Wait for the path changes to quiesce
		[test waitFor:PATH_QUIESCE_TIME];
		if (test.pathProxy != nil) {
			SCTestLog("proxy applied when there is no test proxy");
			break;
		}

		success = YES;
	} while (0);

	[pathEvaluator removeObserver:test
			   forKeyPath:@"path"];

	return success;
}

- (BOOL)unitTestInstallDNSWithConflictingDomain
{
	BOOL success = NO;
	SCTestConfigAgent *test;
	NSDictionary *dnsConfig;
	NSString *hostname;
	NWHostEndpoint *hostEndpoint;
	NWPathEvaluator *pathEvaluator;

	test = [[SCTestConfigAgent alloc] initWithOptions:self.options];
	dnsConfig = @{	(__bridge NSString *)kSCPropNetDNSServerAddresses:@[@"10.10.10.101", @"10.10.10.102", @"10.10.10.103"],
			(__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains:@[@TEST_DOMAIN],
			};

	hostname = [[dnsConfig objectForKey:(__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains] objectAtIndex:0];
	hostEndpoint = [NWHostEndpoint endpointWithHostname:hostname port:@"80"];
	pathEvaluator = [[NWPathEvaluator alloc] initWithEndpoint:hostEndpoint parameters:NULL];
	[pathEvaluator addObserver:test
			forKeyPath:@"path"
			   options:NSKeyValueObservingOptionNew
			   context:nil];

	do {
		NSDictionary *duplicateDnsConfig;
		NSString *anotherFakeServiceID;
		NSString *anotherDNSKey;
		NSArray *array;
		NSSet *testDNSSet;
		NSSet *pathDNSSet;

		[test publishToSCDynamicStore:test.dnsKey value:dnsConfig];
		test.testDNS = [test createDNSArray:dnsConfig];

		// Wait for the path changes to quiesce
		[test waitFor:PATH_QUIESCE_TIME];
		if (![test.testDNS isEqualToArray:test.pathDNS]) {
			SCTestLog("test DNS and applied DNS do not match. Test: %@, Applied: %@", test.testDNS, test.pathDNS);
			break;
		}

		// Now install the conflicting DNS configuration
		duplicateDnsConfig = @{	(__bridge NSString *)kSCPropNetDNSServerAddresses:@[@"10.10.10.104", @"10.10.10.105", @"10.10.10.106"],
					(__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains:@[@TEST_DOMAIN],
					};

		anotherFakeServiceID = [NSUUID UUID].UUIDString;
		anotherDNSKey = (__bridge_transfer NSString *)[self copyStateKeyWithServiceID:(__bridge CFStringRef)(anotherFakeServiceID) forEntity:kSCEntNetDNS];

		[test publishToSCDynamicStore:anotherDNSKey value:duplicateDnsConfig];
		array = [test.testDNS arrayByAddingObjectsFromArray:[test createDNSArray:duplicateDnsConfig]];
		test.testDNS = array;

		// Wait for the path changes to quiesce
		[test waitFor:PATH_QUIESCE_TIME];

		// Use NSSet for unordered comparison
		testDNSSet = [NSSet setWithArray:test.testDNS];
		pathDNSSet = [NSSet setWithArray:test.pathDNS];
		success = [testDNSSet isEqualToSet:pathDNSSet];
		[test removeFromSCDynamicStore:anotherDNSKey];
		if (!success) {
			SCTestLog("test DNS and applied DNS for duplicate domains do not match. Test: %@, Applied: %@", test.testDNS, test.pathDNS);
			break;
		}

		[test removeFromSCDynamicStore:test.dnsKey];
		test.testDNS = nil;
		[test waitFor:PATH_QUIESCE_TIME];

		SCTestLog("Verified that the configured DNS with duplicate domains is same as applied DNS for a domain");
		success = YES;

	} while (0);

	[pathEvaluator removeObserver:test
			   forKeyPath:@"path"];

	return success;
}

- (BOOL)unitTestInstallProxyWithConflictingDomain
{
	BOOL success = NO;
	SCTestConfigAgent *test;
	NSDictionary *proxyConfig;
	NSString *hostname;
	NSNumber *port;
	NWHostEndpoint *hostEndpoint;
	NWPathEvaluator *pathEvaluator;

	test = [[SCTestConfigAgent alloc] initWithOptions:self.options];
	proxyConfig = @{(__bridge NSString *)kSCPropNetProxiesHTTPEnable:@(1),
			(__bridge NSString *)kSCPropNetProxiesHTTPPort:@(80),
			(__bridge NSString *)kSCPropNetProxiesHTTPProxy:@"10.10.10.100",
			(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains:@[@TEST_DOMAIN],
			};

	hostname = [[proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains] objectAtIndex:0];
	port = [proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesHTTPPort];
	hostEndpoint = [NWHostEndpoint endpointWithHostname:hostname port:port.stringValue];
	pathEvaluator = [[NWPathEvaluator alloc] initWithEndpoint:hostEndpoint parameters:NULL];
	[pathEvaluator addObserver:test
			forKeyPath:@"path"
			   options:NSKeyValueObservingOptionNew
			   context:nil];

	do {
		NSMutableDictionary *dict;
		NSMutableDictionary *dict2;
		NSDictionary *duplicateProxyConfig;
		NSString *anotherFakeServiceID;
		NSString *anotherProxyKey;
		NSSet *testProxySet;
		NSSet *pathProxySet;

		[test publishToSCDynamicStore:test.proxyKey value:proxyConfig];
		dict = [NSMutableDictionary dictionaryWithDictionary:proxyConfig];
		[dict removeObjectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains];
		test.testProxy = @[@[dict]];

		// Wait for the path changes to quiesce
		[test waitFor:PATH_QUIESCE_TIME];
		if (![test.testProxy isEqualToArray:test.pathProxy]) {
			SCTestLog("test proxy and applied proxy do not match. Test: %@, Applied: %@", test.testProxy, test.pathProxy);
			break;
		}

		// Now install the conflicting Proxy configuration
		duplicateProxyConfig = @{(__bridge NSString *)kSCPropNetProxiesHTTPSEnable:@(1),
							(__bridge NSString *)kSCPropNetProxiesHTTPSPort:@(8080),
							(__bridge NSString *)kSCPropNetProxiesHTTPSProxy:@"10.10.10.101",
							(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains:@[@TEST_DOMAIN],
							};
		anotherFakeServiceID = [NSUUID UUID].UUIDString;
		anotherProxyKey = (__bridge_transfer NSString *)[self copyStateKeyWithServiceID:(__bridge CFStringRef)(anotherFakeServiceID) forEntity:kSCEntNetProxies];

		[test publishToSCDynamicStore:anotherProxyKey value:duplicateProxyConfig];;
		dict2 = [NSMutableDictionary dictionaryWithDictionary:duplicateProxyConfig];
		[dict2 removeObjectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains];
		test.testProxy = @[@[dict],@[dict2]];

		// Wait for the path changes to quiesce
		[test waitFor:PATH_QUIESCE_TIME];

		// Use NSSet for unordered comparison
		testProxySet = [NSSet setWithArray:test.testProxy];
		pathProxySet = [NSSet setWithArray:test.pathProxy];
		success = [testProxySet isEqualToSet:pathProxySet];
		[test removeFromSCDynamicStore:anotherProxyKey];
		if (!success) {
			SCTestLog("test proxy and applied proxy for duplicate domains do not match. Test: %@, Applied: %@", test.testDNS, test.pathDNS);
			break;
		}

		SCTestLog("Verified the configured proxy with Duplicate domains is the same as applied proxy");

		[test removeFromSCDynamicStore:test.proxyKey];
		test.testProxy = nil;

		// Wait for the path changes to quiesce
		[test waitFor:PATH_QUIESCE_TIME];

		if (test.pathProxy != nil) {
			SCTestLog("proxy applied when there is no test proxy");
			break;
		}

		success = YES;

	} while(0);

	[pathEvaluator removeObserver:test
			   forKeyPath:@"path"];

	return success;
}

- (BOOL)tearDown
{
	[self removeFromSCDynamicStore:self.proxyKey];
	[self removeFromSCDynamicStore:self.dnsKey];
	return YES;
}

- (void)observeValueForKeyPath:(NSString *)keyPath
			ofObject:(id)object
			change:(NSDictionary *)change
		context:(void *)context
{
#pragma unused(change)
#pragma unused(context)
	NWPathEvaluator *pathEvaluator = (NWPathEvaluator *)object;
	if ([keyPath isEqualToString:@"path"]) {
		self.pathProxy = pathEvaluator.path.proxySettings;
		self.pathDNS = pathEvaluator.path.dnsServers;
	}
}

@end