动态修改iOS网络配置

动态修改iOS网络配置

失败的尝试

  系统网络配置默认存储在/Library/Preferences/SystemConfiguration/preferences.plist
尝试直接修改该文件,这样的效果是再次打开设置app,显示结果是更新后的配置, 然而并没有触发真正的逻辑,比如PAC更新
跟踪了一天发现系统使用SCPreferences API对配置进行增删改查,SCPreferences API通过受控方式存取XML配置并在必要时自动发送通知给其他监控配置改变的进程。使用时首先使用SCPreferencesCreate建立一个preferences的会话,在指定特定的preferences时需要传入prefsID参数,0代表访问系统默认配置;路径以/开头用于指定配置文件的绝对路径,否则是相对路径;最后使用CFRelease释放对象。SCPreferencesPath API可以以字典方式访问配置;

API

SCPreferencesRef SCPreferencesCreate(CFAllocatorRef allocator, 
    CFStringRef name,       // 进程标识
    CFStringRef prefsID)    // preferences组名
SCPreferencesRef SCPreferencesCreateWithAuthorization(CFAllocatorRef allocator,
    CFStringRef name,
    CFStringRef prefsID,
    AuthorizationRef authorization) // 提权访问
Boolean SCPreferencesLock(SCPreferencesRef prefs, Boolean wait) // exclusive方式锁定preferences
Boolean SCPreferencesCommitChanges(SCPreferencesRef prefs)      // commit对preferences的修改并持久化存储
Boolean SCPreferencesApplyChanges(SCPreferencesRef prefs)       // 将当前存储的preferences应用到活跃的配置中
Boolean SCPreferencesUnlock(SCPreferencesRef prefs)
CFArrayRef SCPreferencesCopyKeyList(SCPreferencesRef prefs)     // 获取当前定义的preferences键
CFPropertyListRef SCPreferencesGetValue(SCPreferencesRef prefs, CFStringRef key)
Boolean SCPreferencesAddValue(SCPreferencesRef prefs, CFStringRef key, CFPropertyListRef value)
Boolean SCPreferencesSetValue(SCPreferencesRef prefs, CFStringRef key, CFPropertyListRef value)
Boolean SCPreferencesRemoveValue(SCPreferencesRef prefs, CFStringRef key)
void SCPreferencesSynchronize(SCPreferencesRef prefs)           // 使用commit的preferences来访问
CFDictionaryRef SCPreferencesPathGetValue(SCPreferencesRef prefs, CFStringRef path)
CFStringRef SCPreferencesPathGetLink(SCPreferencesRef prefs, CFStringRef path)
Boolean SCPreferencesPathSetValue(SCPreferencesRef prefs, CFStringRef path, CFDictionaryRef value)
Boolean SCPreferencesPathSetLink(SCPreferencesRef prefs, CFStringRef path, CFStringRef link)
Boolean SCPreferencesPathRemoveValue(SCPreferencesRef prefs, CFStringRef path)                          

具体实现

#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#import <Foundation/Foundation.h>

typedef void* SCPreferencesRef;
typedef void* AuthorizationRef;
SCPreferencesRef (*_SCPreferencesCreate)(CFAllocatorRef allocator, NSString* name, NSString* prefsID);
SCPreferencesRef (*_SCPreferencesCreateWithAuthorization)(CFAllocatorRef allocator, NSString* name,
    NSString* prefsID, AuthorizationRef authorization);
Boolean (*_SCPreferencesLock)(SCPreferencesRef prefs, Boolean wait);
Boolean (*_SCPreferencesCommitChanges)(SCPreferencesRef prefs);
Boolean (*_SCPreferencesApplyChanges)(SCPreferencesRef prefs);
Boolean (*_SCPreferencesUnlock)(SCPreferencesRef prefs);
NSArray* (*_SCPreferencesCopyKeyList)(SCPreferencesRef prefs);
id (*_SCPreferencesGetValue)(SCPreferencesRef prefs, NSString* key);
Boolean (*_SCPreferencesAddValue)(SCPreferencesRef prefs, NSString* key, id value);
Boolean (*_SCPreferencesSetValue)(SCPreferencesRef prefs, NSString* key, id value);
Boolean (*_SCPreferencesRemoveValue)(SCPreferencesRef prefs, NSString* key);
void (*_SCPreferencesSynchronize)(SCPreferencesRef prefs);
NSDictionary* (*_SCPreferencesPathGetValue)(SCPreferencesRef prefs, NSString* path);
NSString* (*_SCPreferencesPathGetLink)(SCPreferencesRef prefs, NSString* path);
Boolean (*_SCPreferencesPathSetValue)(SCPreferencesRef prefs, NSString* path, NSDictionary* value);
Boolean (*_SCPreferencesPathSetLink)(SCPreferencesRef prefs, NSString* path, NSString* link);
Boolean (*_SCPreferencesPathRemoveValue)(SCPreferencesRef prefs, NSString* path);

void FLog(NSString* msg) {
	NSLog(@"%@", msg);
}

int main(int argc, char **argv, char **envp) {
	_SCPreferencesCreate = (typeof(_SCPreferencesCreate))dlsym(RTLD_DEFAULT, "SCPreferencesCreate");
	_SCPreferencesCreateWithAuthorization = (typeof(_SCPreferencesCreateWithAuthorization))dlsym(RTLD_DEFAULT, 
		"SCPreferencesCreateWithAuthorization");
	_SCPreferencesLock = (typeof(_SCPreferencesLock))dlsym(RTLD_DEFAULT, "SCPreferencesLock");
	_SCPreferencesCommitChanges = (typeof(_SCPreferencesCommitChanges))dlsym(RTLD_DEFAULT, "SCPreferencesCommitChanges");
	_SCPreferencesApplyChanges = (typeof(_SCPreferencesApplyChanges))dlsym(RTLD_DEFAULT, "SCPreferencesApplyChanges");
	_SCPreferencesUnlock = (typeof(_SCPreferencesUnlock))dlsym(RTLD_DEFAULT, "SCPreferencesUnlock");
	_SCPreferencesCopyKeyList = (typeof(_SCPreferencesCopyKeyList))dlsym(RTLD_DEFAULT, "SCPreferencesCopyKeyList");
	_SCPreferencesGetValue = (typeof(_SCPreferencesGetValue))dlsym(RTLD_DEFAULT, "SCPreferencesGetValue");
	_SCPreferencesAddValue = (typeof(_SCPreferencesAddValue))dlsym(RTLD_DEFAULT, "SCPreferencesAddValue");
	_SCPreferencesSetValue = (typeof(_SCPreferencesSetValue))dlsym(RTLD_DEFAULT, "SCPreferencesSetValue");
	_SCPreferencesRemoveValue = (typeof(_SCPreferencesRemoveValue))dlsym(RTLD_DEFAULT, "SCPreferencesRemoveValue");
	_SCPreferencesSynchronize = (typeof(_SCPreferencesSynchronize))dlsym(RTLD_DEFAULT, "SCPreferencesSynchronize");
	_SCPreferencesPathGetValue = (typeof(_SCPreferencesPathGetValue))dlsym(RTLD_DEFAULT, "SCPreferencesPathGetValue");
	_SCPreferencesPathGetLink = (typeof(_SCPreferencesPathGetLink))dlsym(RTLD_DEFAULT, "SCPreferencesPathGetLink");
	_SCPreferencesPathSetValue = (typeof(_SCPreferencesPathSetValue))dlsym(RTLD_DEFAULT, "SCPreferencesPathSetValue");
	_SCPreferencesPathSetLink = (typeof(_SCPreferencesPathSetLink))dlsym(RTLD_DEFAULT, "SCPreferencesPathSetLink");
	_SCPreferencesPathRemoveValue = (typeof(_SCPreferencesPathRemoveValue))dlsym(RTLD_DEFAULT, "SCPreferencesPathRemoveValue");

	if (argc < 2) {
		return 0;
	}
	NSMutableDictionary* proxyconf = [[NSMutableDictionary alloc] init];
	proxyconf[@"proxymode"] = [NSString stringWithUTF8String:argv[1]];
	if ([proxyconf[@"proxymode"] isEqualToString:@"close"]) {
	} else if ([proxyconf[@"proxymode"] isEqualToString:@"manual"]) {
		proxyconf[@"proxyhost"] = [NSString stringWithUTF8String:argv[2]];
		proxyconf[@"proxyport"] = @([[NSString stringWithUTF8String:argv[3]] integerValue]);
	} else if ([proxyconf[@"proxymode"] isEqualToString:@"auto"]) {
		proxyconf[@"proxyurl"] = [NSString stringWithUTF8String:argv[2]];
	}

	SCPreferencesRef prefs = _SCPreferencesCreate(nil, @"", nil);
	_SCPreferencesLock(prefs, TRUE);
	BOOL change_proxy_success = NO;
	do {
		NSString* CurrentSet = (NSString*)_SCPreferencesGetValue(prefs, @"CurrentSet");
		if (CurrentSet == nil) {
			FLog(@"unchroot CurrentSet nil");
			break;
		}
		NSString* IPv4Path = [NSString stringWithFormat:@"%@/Network/Global/IPv4", CurrentSet];
		NSDictionary* IPv4 = _SCPreferencesPathGetValue(prefs, IPv4Path);
		NSString* service_en0 = nil;
		if (IPv4 == nil || ![[IPv4 allKeys] containsObject:@"ServiceOrder"]) {
			FLog(@"unchroot IPv4 nil");
			break;
		}
		for (NSString* service in IPv4[@"ServiceOrder"]) {
			NSString* InterfacePath = [NSString stringWithFormat:@"/NetworkServices/%@/Interface", service];
			NSDictionary* Interface = _SCPreferencesPathGetValue(prefs, InterfacePath);
			if (Interface == nil) {
				continue;
			}
			if ([Interface[@"DeviceName"] isEqualToString:@"en0"]) {
				service_en0 = service;
			}
		}
		if (service_en0 == nil) {
			FLog(@"unchroot service.en0 not exist");
			break;
		}
		NSString* ServiceEn0Path = [NSString stringWithFormat:@"/NetworkServices/%@", service_en0];
		NSDictionary* ServiceEn0 = _SCPreferencesPathGetValue(prefs, ServiceEn0Path);
		if (ServiceEn0 == nil) {
			FLog(@"unchroot service.en0 nil");
			break;
		}
		NSMutableDictionary* serviceobj = [[ServiceEn0 mutableCopy] autorelease];
		if ([proxyconf[@"proxymode"] isEqualToString:@"close"]) {
			serviceobj[@"Proxies"] = @{
				@"FTPPassive": @1, 
				@"ExceptionsList": @[
					@"*.local", 
					@"169.254/16"
				]
			};
		} else if ([proxyconf[@"proxymode"] isEqualToString:@"manual"]) {
			if (![[proxyconf allKeys] containsObject:@"proxyhost"] || 
					![[proxyconf allKeys] containsObject:@"proxyport"]) {
				FLog(@"unchroot lack of param proxyhost or proxyport");
				break;
			}
			serviceobj[@"Proxies"] = @{
				@"HTTPProxy": proxyconf[@"proxyhost"], 
				@"HTTPPort": proxyconf[@"proxyport"], 
				@"HTTPSProxy": proxyconf[@"proxyhost"], 
				@"HTTPSPort": proxyconf[@"proxyport"],
				@"HTTPEnable": @1, 
				@"HTTPSEnable": @1, 
				@"FTPPassive": @1,
				@"ExceptionsList": @[
					@"*.local", 
					@"169.254/16"
				]
			};
		} else if ([proxyconf[@"proxymode"] isEqualToString:@"auto"]) {
			if (![[proxyconf allKeys] containsObject:@"proxyurl"]) {
				FLog(@"unchroot lack of param proxyurl");
				break;
			}
			serviceobj[@"Proxies"] = @{
				@"FTPPassive": @1, 
				@"ExceptionsList": @[
					@"*.local", 
					@"169.254/16"
				], 
				@"ProxyAutoConfigEnable": @1, 
				@"ProxyAutoConfigURLString": proxyconf[@"proxyurl"]
			};
		} else {
			FLog(@"unchroot unknown proxymode");
			break;
		}
		if (_SCPreferencesPathSetValue(prefs, ServiceEn0Path, serviceobj)) {
			change_proxy_success = YES;
		}
	} while (false);
	if (change_proxy_success) {
		_SCPreferencesCommitChanges(prefs);
		_SCPreferencesApplyChanges(prefs);
		_SCPreferencesSynchronize(prefs);
	}
	_SCPreferencesUnlock(prefs);
	CFRelease(prefs);

	if (change_proxy_success) {
		printf("change ok\n");
	} else {
		printf("change fail\n");
	}
	return 0;
}

最后要注意,使用SCPreferences API需要签名:

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>platform-application</key>
    <true/>
  </dict>
</plist>