介绍
什么是privoxy
项目地址:https://github.com/unisx/privoxy
文档地址:http://www.privoxy.org/user-manual/quickstart.html
官网上是这么介绍Privoxy的,
Privoxy is a non-caching web proxy with advanced filtering capabilities for enhancing privacy, modifying web page data and HTTP headers, controlling access, and removing ads and other obnoxious Internet junk. Privoxy has a flexible configuration and can be customized to suit individual needs and tastes. It has application for both stand-alone systems and multi-user networks.
privoxy是一个无缓存的浏览器代理,带有高级的过滤功能,可用于提升隐私安全性,修改网页数据和HTTP头,控制访问,去除广告和其他令人恶心的内容。
读到这里发现shadowsocks自己没有去实现系统代理功能,而是委托privoxy作为浏览器HTTP代理。浏览器http代理发送给ss_privoxy, ss_privoxy通过sock5协议转发shadowsock。如此这般,Shadowsock就可以支持http代理和sock5代理两种方式了。
启动参数
PrivoxyRunner.cs如何和privoxy.exe配合的
调用关系
文件Controller/Service/PrivoxyRunner.cs负责控制privoxy.exe,文件Controller/ShadowsocksController.cs通过调用PrivoxyRunner.cs来间接控制privoxy.exe启停。
那么很明显,作者在这里使用了一个代理设计模式(Proxy Pattern)。PrivoxyRunner.cs在这里以一个代理的身份,把具体执行代理任务的组件Privoxy.exe和当前系统组件隔离开。如果将来启用privoxy.exe,改用其他组件,只需要修改PrivoxyRunner.cs即可。
Privoxy.cs代码也非常简单,可以贴在这里
class PrivoxyRunner
{
private static int _uid;
private static string _uniqueConfigFile;
private static Job _privoxyJob;
private Process _process;
private int _runningPort;
// 静态构造函数,解压需要的依赖资源ss_privoxy.exe,mgwz.dll,初始化配置文件名_uniqueConfigFile
static PrivoxyRunner()
{
try
{
_uid = Application.StartupPath.GetHashCode(); // Currently we use ss's StartupPath to identify different Privoxy instance.
_uniqueConfigFile = $"privoxy_{_uid}.conf";
_privoxyJob = new Job();
FileManager.UncompressFile(Utils.GetTempPath("ss_privoxy.exe"), Resources.privoxy_exe);
FileManager.UncompressFile(Utils.GetTempPath("mgwz.dll"), Resources.mgwz_dll);
}
catch (IOException e)
{
Logging.LogUsefulException(e);
}
}
public int RunningPort => _runningPort;
public void Start(Configuration configuration)
{
if (_process == null)
{
Process[] existingPrivoxy = Process.GetProcessesByName("ss_privoxy");
foreach (Process p in existingPrivoxy.Where(IsChildProcess))
{
KillProcess(p);
}
string privoxyConfig = Resources.privoxy_conf;
_runningPort = GetFreePort();
privoxyConfig = privoxyConfig.Replace("__SOCKS_PORT__", configuration.localPort.ToString());
privoxyConfig = privoxyConfig.Replace("__PRIVOXY_BIND_PORT__", _runningPort.ToString());
privoxyConfig = privoxyConfig.Replace("__PRIVOXY_BIND_IP__", configuration.shareOverLan ? "0.0.0.0" : "127.0.0.1");
FileManager.ByteArrayToFile(Utils.GetTempPath(_uniqueConfigFile), Encoding.UTF8.GetBytes(privoxyConfig));
_process = new Process
{
// Configure the process using the StartInfo properties.
StartInfo =
{
FileName = "ss_privoxy.exe",
Arguments = _uniqueConfigFile,
WorkingDirectory = Utils.GetTempPath(),
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = true,
CreateNoWindow = true
}
};
_process.Start();
/*
* Add this process to job obj associated with this ss process, so that
* when ss exit unexpectedly, this process will be forced killed by system.
*/
_privoxyJob.AddProcess(_process.Handle);
}
}
public void Stop()
{
if (_process != null)
{
KillProcess(_process);
_process.Dispose();
_process = null;
}
}
private static void KillProcess(Process p)
{
try
{
p.CloseMainWindow();
p.WaitForExit(100);
if (!p.HasExited)
{
p.Kill();
p.WaitForExit();
}
}
catch (Exception e)
{
Logging.LogUsefulException(e);
}
}
/*
* We won't like to kill other ss instances' ss_privoxy.exe.
* This function will check whether the given process is created
* by this process by checking the module path or command line.
*
* Since it's required to put ss in different dirs to run muti instances,
* different instance will create their unique "privoxy_UID.conf" where
* UID is hash of ss's location.
*/
private static bool IsChildProcess(Process process)
{
try
{
/*
* Under PortableMode, we could identify it by the path of ss_privoxy.exe.
*/
var path = process.MainModule.FileName;
return Utils.GetTempPath("ss_privoxy.exe").Equals(path);
}
catch (Exception ex)
{
/*
* Sometimes Process.GetProcessesByName will return some processes that
* are already dead, and that will cause exceptions here.
* We could simply ignore those exceptions.
*/
Logging.LogUsefulException(ex);
return false;
}
}
private int GetFreePort()
{
int defaultPort = 8123;
try
{
// TCP stack please do me a favor
TcpListener l = new TcpListener(IPAddress.Loopback, 0);
l.Start();
var port = ((IPEndPoint)l.LocalEndpoint).Port;
l.Stop();
return port;
}
catch (Exception e)
{
// in case access denied
Logging.LogUsefulException(e);
return defaultPort;
}
}
}
这个类有俩public方法:Start()和Stop()。
- 先来看Start()函数
如果_process != null, 进程已经启动,不做任何处理。否则:
第一步:找出所有ss_privoxy进程,挨个杀掉
Process[] existingPrivoxy = Process.GetProcessesByName("ss_privoxy");
foreach (Process p in existingPrivoxy.Where(IsChildProcess))
{
KillProcess(p);
}
第二步:为privoxy准备配置文件内容,自动分配一个可用的网络端口,写入_uniqueConfigFile制定的文件。
运行参数类似这样子:
__SOCKS_PORT__: shadowsocks端口
__PRIVOXY_BIND_PORT__: privoxy运行端口
__PRIVOXY_BIND_IP__: privoxy监听ip
privoxy listen-address 127.0.0.1:61677
toggle 0
logfile ss_privoxy.log
show-on-task-bar 0
activity-animation 0
forward-socks5 / 127.0.0.1:1400
string privoxyConfig = Resources.privoxy_conf;
_runningPort = GetFreePort();
privoxyConfig = privoxyConfig.Replace("__SOCKS_PORT__", configuration.localPort.ToString());
privoxyConfig = privoxyConfig.Replace("__PRIVOXY_BIND_PORT__", _runningPort.ToString());
privoxyConfig = privoxyConfig.Replace("__PRIVOXY_BIND_IP__", configuration.shareOverLan ? "0.0.0.0" : "127.0.0.1");
FileManager.ByteArrayToFile(Utils.GetTempPath(_uniqueConfigFile), Encoding.UTF8.GetBytes(privoxyConfig));
第三步:启动privoxy进程(不得不说c#启动进程好方便),将privoxy加入job,如果ss进程挂了,privoxy会跟着退出。
process = new Process
{
// Configure the process using the StartInfo properties.
StartInfo =
{
FileName = "ss_privoxy.exe",
Arguments = _uniqueConfigFile,
WorkingDirectory = Utils.GetTempPath(),
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = true,
CreateNoWindow = true
}
};
_process.Start();
_privoxyJob.AddProcess(_process.Handle);
- 再看Stop()函数
简单粗暴,如果process!=null, 直接杀进程
public void Stop()
{
if (_process != null)
{
KillProcess(_process);
_process.Dispose();
_process = null;
}
}
ShadowSocks是如何使用PrivoxyRunner.cs的
搜索代码可以看到,所有对PrivoxyRunner类操作都集中在Controller/ShadowsocksController.cs文件中。总共9处,以我们就来看看相关代码都做了什么。
- line 295:stop
- line 499:创建PrivoxyRunner对象
- line 528:stop
- line 538:start
- line 546:privoxy运行端口作为参数创建PortForwarder对象,然后将PortForwarder对象加入servers对象中
所有这些中,只有PortForwarder工作方式还不清楚。下一段去看看PortForwarder做了什么吧。
到这里,目测privoxy会将数据请求发送到config.localPort(config细节请看上一篇)指定端口上。
github:shadowsocks-windows-master设置
学习了。重中之重