C#实现Socks5服务器:让Socks5服务器同时提供PAC服务

项目托管地址:https://github.com/hooow-does-it-work/socks5
在前面文章,我们实现了Socks5服务器:C#实现Socks5服务器
现在我们让服务器提供Socks5服务的同时提供PAC服务。

0、PAC文件

包含类似于如下javascript代码,核心是FindProxyForURL方法,执行完后,告诉浏览器该怎么处理用户发起的请求。
如果是DIRECT,直连服务器;如果是SOCKS5,则向指定的SOCKS5服务器发起代理请求(协议的交换在前面文章有实现)。

var domains = {
  "google.com" : 1, 
  "packagist.org" : 1, 
  "gmail.com" : 1, 
  "github.com" : 1
};
var proxy = 'SOCKS5 127.0.0.1:4088;';
var direct = 'DIRECT;';
var hasOwnProperty = Object.hasOwnProperty;

function FindProxyForURL(url, host) {
    var suffix;
    var pos = host.lastIndexOf('.');
    pos = host.lastIndexOf('.', pos - 1);
    while(1) {
        if (pos <= 0) {
            if (hasOwnProperty.call(domains, host)) {
                return proxy;
            } else {
                return direct;
            }
        }
        suffix = host.substring(pos + 1);
        if (hasOwnProperty.call(domains, suffix)) {
            return proxy;
        }
        pos = host.lastIndexOf('.', pos - 1);
    }
}

1、可控制数据消费的流

我们目的就是在同一个端口,即提供Socks5服务,又提供PAC服务。
因为默认的NetworkStream是单向、不可逆的,所以在流BufferedNetworkStream中,我们通过Consume属性来控制流是否消费缓冲区的数据。
主要在下面两个方法控制缓冲区的消费。

public override int ReadByte()
{
    if (_length > 0)
    {
        if (!_consume) return _buffer[_offset];

        _length--;
        return _buffer[_offset++];
    }
    return base.ReadByte();
}

private int CopyFromBuffer(byte[] buffer, int offset, int size)
{
    //如果要求读的数据超过了缓冲区内数据的大小,则只返回缓冲区内的可用数据
    if (size > _length) size = _length;

    //从缓冲区拷贝数据到应用
    Array.Copy(_buffer, _offset, buffer, offset, size);

    if (!_consume) return size;

    //数据拷贝完成,移动偏移,修改缓冲区的可能数据长度
    _offset += size;
    _length -= size;

    return size;
}

2、PAC服务实现

实现上面的流后,我们就可以从缓冲区中读取数据,通过判断流的起始数据来决定提供Socks5服务还是PAC服务。
这里判断的核心就是读取第一个字节,如果是0x05,代表是Socks5服务,否则我们全部当成PAC服务来处理。
我们把上篇文章使用的NetworkStream替换为BufferedNetworkStream
通过Consume属性告诉流什么时候开始消费数据。

/// <summary>
/// 实现NewClient方法,处理Socks5请求
/// </summary>
/// <param name="client"></param>
protected override void NewClient(Socket client)
{
    if (string.IsNullOrEmpty(_serveAt))
    {
        _serveAt = $"127.0.0.1:{LocalEndPoint.Port}";
    }

    //实例化的是BufferedNetworkStream,可控制数据的消费和缓冲区
    BufferedNetworkStream stream = new BufferedNetworkStream(client, true);
    try
    {
        //先设置基础流不消费读取到的缓冲区
        stream.Consume = false;
        int firstByte = stream.ReadByte();

        //开始消费缓冲区
        stream.Consume = true;

        //第一个字节不为0x05,代表不是Socks5协议,我们全部作为PAC服务器处理
        if (firstByte != 0x05)
        {
            Pac.Process(stream, _hostListFile, _serveAt);
            return;
        }

        ProtocolExchanger exchanger = new ProtocolExchanger();
        try
        {
            //实例化NetWorkStream,让实例化NetWorkStream拥有基础Socket的处理权限
            exchanger.Start(stream);
        }
        catch
        {
            //异常,销毁
            exchanger.Dispose();
        }
    }
    catch
    {
        stream.Close();
    }
}

PAC本质就是接收一个HTTP请求,发送一个HTTP响应到客户端。
https://github.com/hooow-does-it-work/socks5/blob/main/src/Pac.cs这里实现HTTP的简单读取和响应,就不把代码拷贝过来占用文章篇幅了。

3、测试

本地的pac.lst文件把baidu.com加上(https://github.com/hooow-does-it-work/socks5/blob/main/bin/Release/pac.lst)。
启动服务,浏览器访问:http://127.0.0.1:4088/pac
在这里插入图片描述
可以看到浏览器显示了正确的PAC文件内容。
然后设置浏览器代理,脚本地址填写我们的PAC文件地址。
在这里插入图片描述
保存,浏览器访问:https://www.baidu.com
在这里插入图片描述
可以看到,baidu页面正常打开,开发工具中显示的Remote Address也是我们的Socks服务器地址,代理成功。

4、总结

1、PAC和Socks5完全可以分开,只要PAC中指定了正确的Socks服务器即可,我们为了少占用一个端口,放在一起方便处理。
2、同时提供两种服务的关键就是保留从基础流读取的数据,继续提供给下游应用使用,我们这里通过缓冲区和流是否消费缓冲区数据实现。
3、能够实现同时提供两种服务必须要求两种服务的协议有明显区别。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Anlige

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值