如果不关心原理细节,可直接下载本人编译好的客户端修复版:Shadowsocks/SS修复版 ShadowsocksR/SSR修复版
今天打开任务管理器,发现有大量“HTTP Auto Proxy Detection Worker Process”进程,画风如下:
根据进程名称,应该和代理有关,因为电脑上开着Shadowsocks。但是这么多什么鬼?打群架吗?
退出SS程序,发现这些进程不会随之退出。意识到这超出了我的知识范围,于是马上打开Google学学。根据有限的搜索结果,基本上确认和SS有关,并且仅会出现在1809版的Windows 10上。于是打开电脑属性,确认了系统是1809版的bug10:
花了一个多小时理解WPAD、PAC和pacjsworker.exe关系,并加上自己反复测验,得到一些结论:
- HTTP Auto Proxy Detection Worker Process进程的程序文件是”C:/Windows/System32/pacjsworker.exe”,由WPAD服务启动;
- 这些进程不能手工杀死,重启系统才能解决,用户退出(logout)再登录也不行;
- WPAD服务(Win HTTP Web Proxy Auto Discovery Service)的属性不能修改,通过重启WPAD服务杀死进程的路也不通;
- 1809版本才会出现,在这之前版本的系统上运行正常;根据Twitter链接https://twitter.com/epakskape/status/1007316208087994368,应该是build 17692引入新功能导致;
- 情况复现:打开SS客户端,选择PAC模式,勾选启用系统代理;然后按电源键(或其他方式)让电脑进入休眠;接着唤醒电脑进入桌面,任务管理器中就多了一个HTTP Auto Proxy Detection Worker Process进程;如此反复进入休眠又启动,每次都会出现一个新进程;
- 后来发现不需要休眠也能复现情况:打开SS客户端,选择PAC模式,打开任务管理器;然后不断禁用/启用系统代理,在任务管理器窗口中可实时看到每次启用系统代理后都会多一个进程;
- 最新版的SS客户端依然有这个问题;
- 每次SS启动系统代理后的PAC地址均不同;
- PAC地址中的secret参数仅在勾选“保护本地PAC(secure local PAC)”选项时才会出现,但t参数一直都在。
根据网上知识,加上个人实验和思考,对问题原因的理解为:WPAD是系统关键服务,用户不能更改;以PAC模式启动SS时,SS会在LAN配置中设置一个新的PAC脚本地址;WPAD检测到有新的PAC脚本地址,唤醒一个新的pacjsworker.exe进程监听该地址;PAC地址失效后,监听进程不退出,于是进程堆积,慢慢出现几十上百个进程的壮观场景。
总结问题根源:1. Windows 10的进程不自动退出;2. 每次从休眠过来后,SS客户端生成不同的PAC地址并将其配置到LAN设置中。
把问题搞清楚了,解决方案基本上章口就莱:
- 使用全局模式;全局模式不会设置PAC配置脚本,WPAD服务也就不会拉起新的进程;
- 等待巨硬修复问题;去年下半年就爆出问题,到现在问题依旧,应该有得等(本人最近才更新1809版本,故而发现得迟);另外我不认为这是巨硬的锅,他们未必会修复;
- 重新编译SS客户端,生成相同的PAC(至少未重启SS客户端前PAC地址应一致)。
我的解决方案是重新编译SS客户端,根据 @Joelism 的提示及自己理解,做了两个方案的客户端:
- 方案一:总是生成相同的PAC地址,除非人工编辑”pac-secret.txt”文件。改动代码如下:
// 文件: shadowsocks-csharp/Controller/Service/PACServer.cs public void UpdateConfiguration(Configuration config) { this._config = config; if (config.secureLocalPac) { // 注释掉部分 // var rd = new byte[32]; // RNG.GetBytes(rd); // PacSecret = $"&secret={Convert.ToBase64String(rd)}"; if (!File.Exists(PAC_SECRET_FILE)) { var rd = new byte[32]; RNG.GetBytes(rd); string secret = Convert.ToBase64String(rd); PacSecret = $"secret={secret}"; File.WriteAllText(PAC_SECRET_FILE, secret); } else { PacSecret = $"secret={File.ReadAllText(PAC_SECRET_FILE)}"; } } else { PacSecret = ""; } // 注释掉部分 // PacUrl = $"http://127.0.0.1:{config.localPort}/pac?t={GetTimestamp(DateTime.Now)}{PacSecret}"; PacUrl = $"http://127.0.0.1:{config.localPort}/pac?{PacSecret}"; }
这个方案保证不管系统休眠重启,还是退出SS客户端再打开,都只会有一个HTTP Auto Proxy Detection Worker Process进程。
- 方案二: 仅当系统中无PAC进程运行时才生成新的PAC地址并设置到LAN中。代码如下
// 文件: shadowsocks-csharp/Controller/ShadowsocksController.cs protected void Reload() { Encryption.RNG.Reload(); // some logic in configuration updated the config when saving, we need to read it again _config = Configuration.Load(); StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); if (privoxyRunner == null) { privoxyRunner = new PrivoxyRunner(); } if (_pacServer == null) { _pacServer = new PACServer(); _pacServer.PACFileChanged += pacServer_PACFileChanged; _pacServer.UserRuleFileChanged += pacServer_UserRuleFileChanged; // 这一行代码从外部移入 _pacServer.UpdateConfiguration(_config); } // 每次唤醒都更新的代码删除 // _pacServer.UpdateConfiguration(_config); if (gfwListUpdater == null) { gfwListUpdater = new GFWListUpdater(); gfwListUpdater.UpdateCompleted += pacServer_PACUpdateCompleted; gfwListUpdater.Error += pacServer_PACUpdateError; } ... }
这个方案保证不退出SS客户端情况下只有一个pacjsworker.exe进程。缺点是如果频繁退出并重启SS客户端,同样会出现有大量进程的现象。
根据源代码思路,我认为第二种方案更合理,是原作者想要的。当然你应该想得到,我是先根据网上提示实现方案一,测试达到效果后继续思考才做出的方案二。从这个角度也是方案二更合理。
两个方案的exe文件我都编译好了,需要请自取:方案一 方案二
如果你用的SSR,请到这个页面下载:ShadowsocksR/SSR修复版
稍后我会发一个pull request到官方库,使用方案二修复该问题。
问题相关网页
- https://github.com/shadowsocks/shadowsocks-windows/issues/1979
- https://github.com/XX-net/XX-Net/issues/11709
- https://github.com/shadowsocks/shadowsocks-windows/issues/2092
- https://github.com/shadowsocksrr/shadowsocksr-csharp/issues/149
- https://steamcn.com/t430331-1-1
- http://bbs.pcbeta.com/forum.php?mod=viewthread&tid=1797995