C#实现Socks5服务端和客户端

一:基础协议流程参数

1、请求 client -> server,客户端socket连接服务器后立即发送此包

VERSIONMETHODS_COUNTMETHODS
1字节1字节1到255字节,长度由METHODS_COUNT值决定
0x050x030x00 0x01
0x02

各字段含义

  1. VERSION SOCKS协议版本,目前固定0x05
  2. METHODS_COUNT 客户端支持的认证方法数量
  3. METHODS 客户端支持的认证方法,每个方法占用1个字节

METHODS列表

  1. 0x00 不需要认证(常用)
  2. 0x01 GSSAPI认证
  3. 0x02 账号密码认证(常用)
  4. 0x03 - 0x7F IANA分配
  5. 0x80 - 0xFE 私有方法保留
  6. 0xFF 无支持的认证方法

2、认证过程

2.1、server -> client 无需认证,server返回无需认证,则直接进入第3步,命令过程
VERSIONMETHOD
1字节1字节
0x050x00
2.2、server -> client 密码认证,server返回需要密码认证,则进入密码认证过程,密码认证成功则进入第3步,命令过程。密码认证失败,则直接断开连接
VERSIONMETHOD
1字节1字节
0x050x02
2.2.1、client -> server 客户端发送账号密码
VERSIONUSERNAME_LENGTHUSERNAMEPASSWORD_LENGTHPASSWORD
1字节1字节1到255字节1字节1到255字节
0x010x010x0a0x010x0a

各字段含义

  1. VERSION 认证子协商版本(与SOCKS协议版本的0x05无关系)
  2. USERNAME_LENGTH 用户名长度
  3. USERNAME 用户名字节数组,长度为USERNAME_LENGTH
  4. PASSWORD_LENGTH 密码长度
  5. PASSWORD 密码字节数组,长度为PASSWORD_LENGTH
2.2.2、server -> client 返回认证结果
VERSIONSTATUS
1字节1字节
0x010x00

各字段含义

  1. VERSION 认证子协商版本
  2. STATUS 认证结果,0x00认证成功,大于0x00认证失败

3、命令过程

3.1 client -> server 发送连接请求
VERSIONCOMMANDRSVADDRESS_TYPEDST.ADDRDST.PORT
1字节1字节1字节1字节1-255字节2字节

各字段含义

  1. VERSION SOCKS协议版本,固定0x05
  2. COMMAND 命令
    1. 0x01 CONNECT 连接上游服务器
    2. 0x02 BIND 绑定,客户端会接收来自代理服务器的链接,著名的FTP被动模式
    3. 0x03 UDP ASSOCIATE UDP中继
  3. RSV 保留字段
  4. ADDRESS_TYPE 目标服务器地址类型
    1. 0x01 IP V4地址
    2. 0x03 域名地址(没有打错,就是没有0x02),域名地址的第1个字节为域名长度,剩下字节为域名名称字节数组
    3. 0x04 IP V6地址
  5. DST.ADDR 目标服务器地址
  6. DST.PORT 目标服务器端口
3.2 server -> client 服务端响应连接结果
VERSIONRESPONSERSVADDRESS_TYPEDST.ADDRDST.PORT
1字节1字节1字节1字节1-255字节2字节

各字段含义

  1. VERSION SOCKS协议版本,固定0x05
  2. RESPONSE 响应命令,除0x00外,其它响应都应该直接断开连接
    1. 0x00 代理服务器连接目标服务器成功
    2. 0x01 代理服务器故障
    3. 0x02 代理服务器规则集不允许连接
    4. 0x03 网络无法访问
    5. 0x04 目标服务器无法访问(主机名无效)
    6. 0x05 连接目标服务器被拒绝
    7. 0x06 TTL已过期
    8. 0x07 不支持的命令
    9. 0x08 不支持的目标服务器地址类型
    10. 0x09 - 0xFF 未分配
  3. RSV 保留字段
  4. BND.ADDR 代理服务器连接目标服务器成功后的代理服务器IP
  5. BND.PORT 代理服务器连接目标服务器成功后的代理服务器端口

4、数据转发

第3步成功后,进入数据转发阶段

  1. CONNECT:则将client过来的数据原样转发到目标,接着再将目标回来的数据原样返回给client
  2. BIND
  3. UDP ASSOCIATE:使用UDP转发

udp转发的数据包

  1. 收到客户端udp数据包后,解析出目标地址,数据,然后把数据发送过去
  2. 收到服务端回来的udp数据后,根据相同格式,打包,然后发回客户端
RSVFRAGADDRESS_TYPEDST.ADDRDST.PORTDATA
2字节1字节1字节可变长2字节可变长

各字段含义

  1. RSV 保留为
  2. FRAG 分片位
  3. ATYP 地址类型
    1. 0x01 IP V4地址
    2. 0x03 域名地址(没有打错,就是没有0x02),域名地址的第1个字节为域名长度,剩下字节为域名名称字节数组
    3. 0x04 IP V6地址
  4. DST.ADDR 目标地址
  5. DST.PORT 目标端口
  6. DATA 数据

二:示例代码

1、服务端代码,暂未实现UDP转发和认证

1.1、基础的封装类和枚举

/// <summary>
/// 客户端和目标服务器的连接
/// </summary>
internal class UserAndToken
{
    public TcpClient Client { get; set; }
    public NetworkStream  ClientStream { get; set; }

    public TcpClient TargetClient { get; set; }
    public NetworkStream TargetStream { get; set; }

}
/// <summary>
/// socks5地址类型
/// </summary>
public enum Socks5AddressType : byte
{
    /// <summary>
    /// IPV4
    /// </summary>
    IPV4 = 0x01,
    /// <summary>
    /// 域名
    /// </summary>
    Domain = 0x03,
    /// <summary>
    /// IPV6
    /// </summary>
    IPV6 = 0x04
}
/// <summary>
/// socks5命令类型
/// </summary>
public enum Socks5CommandType : byte
{
    /// <summary>
    /// 连接
    /// </summary>
    Connect = 0x01,
    /// <summary>
    /// 绑定
    /// </summary>
    Bind = 0x02,
    /// <summary>
    /// UDP转发
    /// </summary>
    Udp = 0x03
}

1.2、服务端完整代码:

using System.Buffers.Binary;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Server
{
    public class TcpClientSocks5Server
    {
        static ushort port = 8300;
        static async Task Main(string[] args)
        {
            try
            {
                TcpListener listener = new TcpListener(IPAddress.Any, port);
                listener.Start();
                Console.WriteLine($"Socks5服务已启动,监听端口:{port}");

                while (true)
                {
                    //接受连接创建客户端
                    TcpClient tcpClient = await listener.AcceptTcpClientAsync();
                    HandleClientAsync(tcpClient);
                }

            }
            catch (Exception e)
            {
                Console.WriteLine($"Main的异常:{e.Message}");

            }
        }

        /// <summary>
        /// 接收处理客户端的数据
        /// </summary>
        /// <param name="tcpClient"></param>
        /// <returns></returns>
        private static async Task HandleClientAsync(TcpClient tcpClient)
        {
            try
            {
                UserAndToken token = new UserAndToken()
                {
                    Client = tcpClient,
                    ClientStream = tcpClient.GetStream(),
                };


                //握手阶段,读取到客户端发送的协议,可以根据协议进行指定的连接
                byte[] buffer = new byte[10];
                await token.ClientStream.ReadAsync(buffer, 0, buffer.Length);

                //判断协议
                if (buffer[0] == 5)
                {
                    int AuthMethod = 0;
                    //设置不需要认证
                    await token.ClientStream.WriteAsync(new byte[] { 0x05, 0x00 });
                    //不需要认证,直接到第三步
                    if (AuthMethod != 0)
                    {
                        //处理认证
                    }

                    #region 处理命令阶段
                    byte[] Commandbuffer = new byte[1024];
                    await token.ClientStream.ReadAsync(Commandbuffer, 0, Commandbuffer.Length);

                    Socks5CommandType socks5CommandType = (Socks5CommandType)Commandbuffer[1];
                    Socks5AddressType addressType = (Socks5AddressType)Commandbuffer[3];

                    //获取目标服务器IP和端口
                    string targetHost = null;
                    ushort TargetPort = 0;
                    switch (addressType)
                    {
                        case Socks5AddressType.IPV4:
                            {
                                targetHost = new IPAddress(Commandbuffer.AsSpan(4, 4)).ToString();
                                TargetPort = BinaryPrimitives.ReadUInt16BigEndian(Commandbuffer.AsSpan(8, 2));
                            }
                            break;
                        case Socks5AddressType.Domain:
                            {
                                byte length = Commandbuffer[4];
                                targetHost = Encoding.UTF8.GetString(Commandbuffer.AsSpan(5, length));
                                TargetPort = BinaryPrimitives.ReadUInt16BigEndian(Commandbuffer.AsSpan(5 + length, 2));
                            }
                            break;
                        case Socks5AddressType.IPV6:
                            {
                                targetHost = new IPAddress(Commandbuffer.AsSpan(4, 16)).ToString();
                                TargetPort = BinaryPrimitives.ReadUInt16BigEndian(Commandbuffer.AsSpan(20, 2));
                            }
                            break;
                    }

                    byte[] portArray = BitConverter.GetBytes(port);
                    // 如果您需要确保使用特定的字节顺序(例如,大端或小端),可以使用以下方式进行转换:
                    if (BitConverter.IsLittleEndian)
                    {
                        // 如果当前系统是小端字节序,反转字节数组以得到大端字节序
                        Array.Reverse(portArray);
                    }

                    if (socks5CommandType == Socks5CommandType.Connect)
                    {
                        try
                        {
                            token.TargetClient = new TcpClient(targetHost, TargetPort);
                            token.TargetStream = token.TargetClient.GetStream();

                            // 返回客户端连接成功
                            await token.ClientStream.WriteAsync(new byte[] { 0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, portArray[0], portArray[1] }, 0, 10);

                        }
                        catch (SocketException ex)
                        {
                            //访问目标服务器失败则不断开客户端和代理服务器的连接,仅返回失败原因
                            if (SocketError.HostNotFound == ex.SocketErrorCode)
                            {
                                // 返回客户端连接失败原因:目标服务器无法访问(主机名无效)
                                await token.ClientStream.WriteAsync(new byte[] { 0x05, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, portArray[0], portArray[1] }, 0, 10);
                            }
                            else
                            {
                                // 返回客户端连接失败原因:连接目标服务器被拒绝
                                await token.ClientStream.WriteAsync(new byte[] { 0x05, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, portArray[0], portArray[1] }, 0, 10);
                            }
                        }
                    }
                    else
                    {
                        // 返回客户端连接失败原因:代理服务器规则集不允许连接
                        await token.ClientStream.WriteAsync(new byte[] { 0x05, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, portArray[0], portArray[1] }, 0, 10);
                        //关闭和客户端的连接通道
                        token.Client.Close();
                        token.Client.Dispose();
                    }
                    #endregion

                    #region 处理转发
                    if (token.Client.Connected && token.TargetClient.Connected)
                    {
                        //两个Task不停地互相转发数据
                        await Task.WhenAny(
                            PipeAsync(token.ClientStream, token.TargetStream),
                            PipeAsync(token.TargetStream, token.ClientStream)
                        );
                    }
                    #endregion
                }
                else
                {
                    token.Client.Close();
                    token.Client.Dispose();
                }
            }
            catch (Exception e)
            {

                Console.WriteLine($"HandleClientAsync的异常:{e.Message}");
            }
        }


        /// <summary>
        /// 转发数据
        /// </summary>
        /// <param name="source">源Stream</param>
        /// <param name="target">目标Stream</param>
        /// <returns></returns>
        static async Task PipeAsync(NetworkStream source, NetworkStream target)
        {
            byte[] buffer = new byte[1024];
            int bytesRead;
            try
            {
                //一直循环,在没有stream传输的时候等待
                while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length)) > 0)
                {
                    await target.WriteAsync(buffer, 0, bytesRead);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine($"PipeAsync的异常:{e.Message}");
            }
        }
    }
}

1.3、服务端的个人理解:

SOCKS5代理连接到目标网站的过程通常包括以下四个阶段:

  1. 建立连接: 在这个阶段,客户端应用程序首先会连接到SOCKS5代理服务器。这是最初的TCP连接,它建立了代理和客户端之间的通信通道,这里只需要客户端和服务端建立一次口可以了,会保持长连接(类似于服务端和客户端之间挖了一条隧道,后续所有的请求都在这个隧道里面进行传输)
  2. 协商认证: 一旦建立连接,客户端和代理服务器之间会进行协商认证。在SOCKS5中,有两种认证方法可供选择:无认证方法(0x00)和用户名/密码认证方法(0x02)。客户端会发送认证方法的请求,代理服务器会选择一个支持的方法进行认证。如果需要认证,客户端会发送用户名和密码。
  3. 请求建立连接: 在协商认证成功后,客户端会发送一个连接请求,其中包含目标服务器的IP地址或域名以及端口号。代理服务器会解析该请求并尝试连接到目标服务器。(这里服务器和目标服务器也会搭建一条类似的通道,这里目标服务器和客户端没有直接的关系,代理服务器和两者分别有直接的关系)
  4. 数据转发: 一旦代理服务器成功连接到目标服务器,它会充当中间人,将来自客户端的数据传输到目标服务器,并将来自目标服务器的响应传输回客户端。数据转发阶段持续到客户端或目标服务器中的一方关闭连接。(短连接的数据转发,可以将目标服务器的连接在使用完后进行断开;长连接请使用异步方法一直监听两端的数据流,一旦有数据传输就开始进行转发,没有则等待,存在循环中一直运行,短连接则不用)

1、客户端端代码,敬请期待。。。。。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
老鱼socks5服务是一个网络代理服务器软件,可以让用户通过该服务器连接互联网。它采用socks5协议,这是一种支持TCP和UDP传输的代理协议。 老鱼socks5服务的工作原理如下:当用户请求访问某个网站或服务时,首先用户的浏览器或应用程序会将请求发送给老鱼socks5服务服务会解析请求,并根据用户的配置将请求转发给目标服务器。目标服务器将响应数据返回到老鱼socks5服务,再由服务转发给用户。这样用户和目标服务器之间的通信就通过了中间的代理服务器。 老鱼socks5服务的优点在于它可以实现细粒度的权限控制和灵活的配置。管理员可以根据需求设置不同用户的访问权限,比如限制某些网站的访问或限制某些口的访问。此外,老鱼socks5服务支持本地DNS解析,可以避免因网络问题导致无法正常访问某些网站的情况。 老鱼socks5服务还提供了一些安全性的功能。它支持用户认证,只有经过认证的用户才能连接到服务。此外,它还支持加密传输,可以通过配置使用TLS/SSL等加密协议来保护数据的安全性。 综上所述,老鱼socks5服务是一款功能强大、安全可靠的网络代理服务器软件,可以提供方便的访问互联网和灵活的权限控制。无论是个人用户还是企业用户,都可以选择老鱼socks5服务来搭建自己的代理服务器,以满足不同的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

百度CV程序员

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

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

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

打赏作者

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

抵扣说明:

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

余额充值