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

一:基础协议流程参数

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

VERSION METHODS_COUNT METHODS
1字节 1字节 1到255字节,长度由METHODS_COUNT值决定
0x05 0x03 0x00 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步,命令过程
VERSION METHOD
1字节 1字节
0x05 0x00
2.2、server -> client 密码认证,server返回需要密码认证,则进入密码认证过程,密码认证成功则进入第3步,命令过程。密码认证失败,则直接断开连接
VERSION METHOD
1字节 1字节
0x05 0x02
2.2.1、client -> server 客户端发送账号密码
VERSION USERNAME_LENGTH USERNAME PASSWORD_LENGTH PASSWORD
1字节 1字节 1到255字节 1字节 1到255字节
0x01 0x01 0x0a 0x01 0x0a

各字段含义

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

各字段含义

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

3、命令过程

3.1 client -> server 发送连接请求
VERSION COMMAND RSV ADDRESS_TYPE DST.ADDR DST.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 服务端响应连接结果
VERSION RESPONSE RSV ADDRESS_TYPE DST.ADDR DST.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数据后,根据相同格式,打包,然后发回客户端
RSV FRAG ADDRESS_TYPE DST.ADDR DST.PORT DATA
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、基础的封装类和枚举

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

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

}
/// 
/// socks5地址类型
/// 
public enum Socks5AddressType : byte
{
    /// 
    /// IPV4
    /// 
    IPV4 = 0x01,
    /// 
    /// 域名
    /// 
    Domain = 0x03,
    /// 
    /// IPV6
    /// 
    IPV6 = 0x04
}
/// 
/// socks5命令类型
/// 
public enum Socks5CommandType : byte
{
    /// 
    /// 连接
    /// 
    Connect = 0x01,
    /// 
    /// 绑定
    /// 
    Bind = 0x02,
    /// 
    /// UDP转发
    /// 
    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}");

            }
        }

        /// 
        /// 接收处理客户端的数据
        /// 
        /// 
        /// 
        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}");
            }
        }


        /// 
        /// 转发数据
        /// 
        /// 源Stream
        /// 目标Stream
        /// 
        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、客户端端代码,敬请期待。。。。。

你可能感兴趣的:(c#,服务器,网络协议)