SS-windows源码分析[3]-shadowsocks和privoxy的关系以及协作方式

楚天乐 2005 0 条

介绍

什么是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的

QQ图片20180924150938.png
搜索代码可以看到,所有对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细节请看上一篇)指定端口上。

打赏

微信打赏

支付宝打赏



与本文相关的文章

发表我的评论
昵称 (必填)
邮箱 (必填)
网址