c#构建具有用户认证与管理的socks5代理服务端

这篇具有很好参考价值的文章主要介绍了c#构建具有用户认证与管理的socks5代理服务端。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Socks 协议是一种代理 (Proxy) 协议, 例如我们所熟知的 Shdowsocks 便是 Socks 协议的一个典型应用程序, Socks 协议有多个版本, 目前最新的版本为 5, 其协议标准文档为 RFC 1928。
我们一起来使用.net 7 构建一个支持用户管理的高性能socks5代理服务端

目录
  • 协议流程
    • 1 client -> server 客户端与服务端握手
    • 2.1 server -> client 无需认证,直接进入第3步,命令过程
    • 2.2、server -> client 密码认证
      • 2.2.1、client -> server 客户端发送账号密码
      • 2.2.2、server -> client 返回认证结果
    • 3.1 client -> server 发送连接请求
    • 3.2 server -> client 服务端响应连接结果
    • 4、数据转发
    • udp转发的数据包
  • 状态机控制每个连接状态
  • 连接与用户管理
  • 持久化
  • 效果示例
  • 源码以及如何使用

协议流程

1 client -> server 客户端与服务端握手
VERSION METHODS_COUNT METHODS
1字节 1字节 1到255字节,长度zMETHODS_COUNT
0x05 0x03 0x00 0x01
0x02
  1. VERSION SOCKS协议版本,目前固定0x05
  2. METHODS_COUNT 客户端支持的认证方法数量
  3. METHODS 客户端支持的认证方法,每个方法占用1个字节

METHODS列表(其他的认证方法可以自行上网了解)

  1. 0x00 不需要认证(常用)
  2. 0x02 账号密码认证(常用)
2.1 server -> client 无需认证,直接进入第3步,命令过程
VERSION METHOD
1字节 1字节
0x05 0x00
2.2、server -> client 密码认证
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.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 目标服务器地址(如果COMMAND是0x03,即UDP模式,此处为客户端启动UDP发送消息的主机地址)
  6. DST.PORT 目标服务器端口(如果COMMAND是0x03,即UDP模式,此处为客户端启动UDP发送消息的端口)
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转发的数据包
  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 数据

状态机控制每个连接状态

从协议中我们可以看出,一个Socks5协议的连接需要经过握手,认证(可选),建立连接三个流程。那么这是典型的符合状态机模型的业务流程。

创建状态和事件枚举

public enum ClientState
    {
        Normal,
        ToBeCertified,
        Certified,
        Connected,
        Death
    }

    public enum ClientStateEvents
    {
        OnRevAuthenticationNegotiation, //当收到客户端认证协商
        OnRevClientProfile, //收到客户端的认证信息
        OnRevRequestProxy, //收到客户端的命令请求请求代理
        OnException,
        OnDeath
    }

根据服务器是否配置需要用户名密码登录,从而建立正确的状态流程。

if (clientStatehandler.NeedAuth)
            {
                builder.In(ClientState.Normal)
                    .On(ClientStateEvents.OnRevAuthenticationNegotiation)
                    .Goto(ClientState.ToBeCertified)
                    .Execute<UserToken>(clientStatehandler.HandleAuthenticationNegotiationRequestAsync)
                    .On(ClientStateEvents.OnException)
                    .Goto(ClientState.Death);
            }
            else 
            {
                builder.In(ClientState.Normal)
                        .On(ClientStateEvents.OnRevAuthenticationNegotiation)
                        .Goto(ClientState.Certified)
                        .Execute<UserToken>(clientStatehandler.HandleAuthenticationNegotiationRequestAsync)
                        .On(ClientStateEvents.OnException)
                        .Goto(ClientState.Death);
            }

            builder.In(ClientState.ToBeCertified)
                .On(ClientStateEvents.OnRevClientProfile)
                .Goto(ClientState.Certified)
                .Execute<UserToken>(clientStatehandler.HandleClientProfileAsync)
                .On(ClientStateEvents.OnException)
                .Goto(ClientState.Death); ;

            builder.In(ClientState.Certified)
                .On(ClientStateEvents.OnRevRequestProxy)
                .Goto(ClientState.Connected)
                .Execute<UserToken>(clientStatehandler.HandleRequestProxyAsync)
                .On(ClientStateEvents.OnException)
                .Goto(ClientState.Death);

            builder.In(ClientState.Connected).On(ClientStateEvents.OnException).Goto(ClientState.Death);

在状态扭转中如果出现异常,则直接跳转状态到“Death”,

_machine.TransitionExceptionThrown += async (obj, e) =>
            {
                _logger.LogError(e.Exception.ToString());
                await _machine.Fire(ClientStateEvents.OnException);
            };

对应状态扭转创建相应的处理方法, 基本都是解析客户端发来的数据包,判断是否合理,最后返回一个响应。

/// <summary>
        /// 处理认证协商
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="InvalidOperationException"></exception>
        public async Task HandleAuthenticationNegotiationRequestAsync(UserToken token)
        {
            if (token.ClientData.Length < 3)
            {
                await token.ClientSocket.SendAsync(new byte[] { 0x05, _exceptionCode });
                throw new ArgumentException("Error request format from client.");
            }
            if (token.ClientData.Span[0] != 0x05) //socks5默认头为5
            {
                await token.ClientSocket.SendAsync(new byte[] { 0x05, _exceptionCode });
                throw new ArgumentException("Error request format from client.");
            }
            int methodCount = token.ClientData.Span[1];
            if (token.ClientData.Length < 2 + methodCount) //校验报文
            {
                await token.ClientSocket.SendAsync(new byte[] { 0x05, _exceptionCode });
                throw new ArgumentException("Error request format from client.");
            }
            bool supprtAuth = false;
            for (int i = 0; i < methodCount; i++)
            {
                if (token.ClientData.Span[2 + i] == 0x02)
                {
                    supprtAuth = true;
                    break;
                }
            }

            if (_serverConfiguration.NeedAuth && !supprtAuth) //是否支持账号密码认证
            {
                await token.ClientSocket.SendAsync(new byte[] { 0x05, _exceptionCode });
                throw new InvalidOperationException("Can't support password authentication!");
            }

            await token.ClientSocket.SendAsync(new byte[] { 0x05, (byte)(_serverConfiguration.NeedAuth ? 0x02 : 0x00) });
        }

        /// <summary>
        /// 接收到客户端认证
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public async Task HandleClientProfileAsync(UserToken token)
        {
            var version = token.ClientData.Span[0];
            //if (version != _serverConfiguration.AuthVersion)
            //{
            //    await token.ClientSocket.SendAsync(new byte[] { 0x05, _exceptionCode });
            //    throw new ArgumentException("The certification version is inconsistent");
            //}

            var userNameLength = token.ClientData.Span[1];
            var passwordLength = token.ClientData.Span[2 + userNameLength];
            if (token.ClientData.Length < 3 + userNameLength + passwordLength)
            {
                await token.ClientSocket.SendAsync(new byte[] { 0x05, _exceptionCode });
                throw new ArgumentException("Error authentication format from client.");
            }

            var userName = Encoding.UTF8.GetString(token.ClientData.Span.Slice(2, userNameLength));
            var password = Encoding.UTF8.GetString(token.ClientData.Span.Slice(3 + userNameLength, passwordLength));
            var user = await _userService.FindSingleUserByUserNameAndPasswordAsync(userName, password);
            if (user == null || user.ExpireTime < DateTime.Now) 
            {
                await token.ClientSocket.SendAsync(new byte[] { version, _exceptionCode });
                throw new ArgumentException($"User{userName}尝试非法登录");
            }

            token.UserName = user.UserName;
            token.Password = user.Password;
            token.ExpireTime = user.ExpireTime;
            await token.ClientSocket.SendAsync(new byte[] { version, 0x00 });
        }

        /// <summary>
        /// 客户端请求连接
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public async Task HandleRequestProxyAsync(UserToken token)
        {
            var data = token.ClientData.Slice(3);
            Socks5CommandType socks5CommandType = (Socks5CommandType)token.ClientData.Span[1];
            var proxyInfo = _byteUtil.GetProxyInfo(data);
            var serverPort = BitConverter.GetBytes(_serverConfiguration.Port);
            if (socks5CommandType == Socks5CommandType.Connect) //tcp
            {
                //返回连接成功
                IPEndPoint targetEP = new IPEndPoint(proxyInfo.Item2, proxyInfo.Item3);//目标服务器的终结点
                token.ServerSocket = new Socket(targetEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                token.ServerSocket.Bind(new IPEndPoint(IPAddress.Any, 0));
                var e = new SocketAsyncEventArgs
                {
                    RemoteEndPoint = new IPEndPoint(targetEP.Address, targetEP.Port)
                };
                token.ServerSocket.ConnectAsync(e);
                e.Completed += async (e, a) =>
                {
                    try
                    {
                        token.ServerBuffer = new byte[800 * 1024];//800kb
                        token.StartTcpProxy();
                        var datas = new List<byte> { 0x05, 0x0, 0, (byte)Socks5AddressType.IPV4 };
                        foreach (var add in (token.ServerSocket.LocalEndPoint as IPEndPoint).Address.GetAddressBytes())
                        {
                            datas.Add(add);
                        }
                        //代理端启动的端口信息回复给客户端
                        datas.AddRange(BitConverter.GetBytes((token.ServerSocket.LocalEndPoint as IPEndPoint).Port).Take(2).Reverse());

                        await token.ClientSocket.SendAsync(datas.ToArray());
                    }
                    catch (Exception) 
                    {
                        token.Dispose();
                    }
                };
            }
            else if (socks5CommandType == Socks5CommandType.Udp)//udp
            {
                token.ClientUdpEndPoint = new IPEndPoint(proxyInfo.Item2, proxyInfo.Item3);//客户端发起代理的udp终结点
                token.IsSupportUdp = true;
                token.ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                token.ServerSocket.Bind(new IPEndPoint(IPAddress.Any, 0));
                token.ServerBuffer = new byte[800 * 1024];//800kb
                token.StartUdpProxy(_byteUtil);
                var addressBytes = (token.ServerSocket.LocalEndPoint as IPEndPoint).Address.GetAddressBytes();
                var portBytes = BitConverter.GetBytes((token.ServerSocket.LocalEndPoint as IPEndPoint).Port).Take(2).Reverse().ToArray();
                await token.ClientSocket.SendAsync(new byte[] { 0x05, 0x0, 0, (byte)Socks5AddressType.IPV4, addressBytes[0], addressBytes[1], addressBytes[2], addressBytes[3], portBytes[0], portBytes[1] });
            }
            else
            {
                await token.ClientSocket.SendAsync(new byte[] { 0x05, 0x1, 0, (byte)Socks5AddressType.IPV4, 0, 0, 0, 0, 0, 0 });
                throw new Exception("Unsupport proxy type.");
            }
        }

连接与用户管理

当服务器采用需要认证的配置时,我们会返回给客户端0x02的认证方式,此时,客户端需要上传用户名和密码,如果认证成功我们就可以将用户信息与连接对象做绑定,方便后续管理。

在客户端通过tcp或者udp上传数据包,需要代理服务器转发时,我们记录数据包的大小作为上传数据包流量记录下来,反之亦然。
示例:记录tcp代理客户端的下载流量

public void StartTcpProxy()
        {
            Task.Run(async () =>
            {
                while (true)
                {
                    var data = await ServerSocket.ReceiveAsync(ServerBuffer);
                    if (data == 0)
                    {
                        Dispose();
                    }

                    await ClientSocket.SendAsync(ServerBuffer.AsMemory(0, data));
                    if (!string.IsNullOrEmpty(UserName))
                        ExcuteAfterDownloadBytes?.Invoke(UserName, data);
                }
            }, CancellationTokenSource.Token);
        }

当管理界面修改某用户的密码或者过期时间的时候
1.修改密码,强制目前所有使用该用户名密码的连接断开
2.我们每个连接会有一个定时服务,判断是否过期
从而实现用户下线。

//更新密码或者过期时间后
public void UpdateUserPasswordAndExpireTime(string password, DateTime dateTime)
        {
            if (password != Password)
            {
                Dispose();
            }

            if (DateTime.Now > ExpireTime)
            {
                Dispose();
            }
        }

/// <summary>
        /// 过期自动下线
        /// </summary>
        public void WhenExpireAutoOffline()
        {
            Task.Run(async () =>
            {
                while (true)
                {
                    if (DateTime.Now > ExpireTime)
                    {
                        Dispose();
                    }

                    await Task.Delay(1000);
                }
            }, CancellationTokenSource.Token);
        }

持久化

用户数据包括,用户名密码,使用流量,过期时间等存储在server端的sqlite数据库中。通过EFcore来增删改查。
如下定期更新用户流量到数据库

private void LoopUpdateUserFlowrate()
        {
            Task.Run(async () =>
            {
                while (true)
                {

                    var datas = _uploadBytes.Select(x =>
                    {
                        return new
                        {
                            UserName = x.Key,
                            AddUploadBytes = x.Value,
                            AddDownloadBytes = _downloadBytes.ContainsKey(x.Key) ? _downloadBytes[x.Key] : 0
                        };
                    });

                    if (datas.Count() <= 0
                        || (datas.All(x => x.AddUploadBytes == 0)
                        && datas.All(x => x.AddDownloadBytes == 0)))
                    {
                        await Task.Delay(5000);
                        continue;
                    }
                    var users = await _userService.Value.GetUsersInNamesAsync(datas.Select(x => x.UserName));

                    foreach (var item in datas)
                    {
                        users.FirstOrDefault(x => x.UserName == item.UserName).UploadBytes += item.AddUploadBytes;
                        users.FirstOrDefault(x => x.UserName == item.UserName).DownloadBytes += item.AddDownloadBytes;
                    }

                    await _userService.Value.BatchUpdateUserAsync(users);
                    _uploadBytes.Clear();
                    _downloadBytes.Clear();
                    await Task.Delay(5000);
                }
            });
        }

//批量更新用户信息到sqlite
        public async Task BatchUpdateUserFlowrateAsync(IEnumerable<User> users)
        {
            using (var context = _dbContextFactory.CreateDbContext())
            {
                context.Users.UpdateRange(users);
                await context.SaveChangesAsync();
            }
        }

效果示例

打开服务
c#构建具有用户认证与管理的socks5代理服务端

打开Proxifier配置到我们的服务
c#构建具有用户认证与管理的socks5代理服务端

查看Proxifier已经流量走到我们的服务
c#构建具有用户认证与管理的socks5代理服务端

服务端管理器
c#构建具有用户认证与管理的socks5代理服务端

源码以及如何使用

https://github.com/BruceQiu1996/Socks5Server文章来源地址https://www.toymoban.com/news/detail-448216.html

到了这里,关于c#构建具有用户认证与管理的socks5代理服务端的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • fatal: ServicePointManager 不支持具有 socks5 方案的代理。

    最近尝试自己之前的解决方法发现不行了,然后在这里找到了答案 github - 右上角自己头像 - settings - (拉到最下面)developer settings - Personal access tokens - Tokens(classic) 复制生成的token (当作密码) Username: your_github_name Password:this_copyied_token 以下是http和socks5的github代理更改方式,针对

    2024年02月10日
    浏览(44)
  • Socks5与代理IP技术探析:构建安全高效的网络通信

    1.1 握手与身份验证 Socks5协议的握手阶段通过版本协商和灵活的身份验证方式建立安全连接。这确保了通信的可靠性和用户身份的安全。 1.2 数据传输机制 Socks5通过代理实现数据传输,支持TCP和UDP协议,为用户提供了高度灵活的网络通信机制。连接一旦建立,数据可以通过代

    2024年02月04日
    浏览(44)
  • Java使用Netty实现端口转发&Http代理&Sock5代理服务器

    这里总结整理了之前使用Java写的端口转发、Http代理、Sock5代理程序,放在同一个工程中,方便使用。 开发语言:Java 开发框架:Netty 端口转发: HTTP代理服务器,支持账号密码认证 Sock5代理服务器,支持账号密码认证 支持连接后端时直接连接或采用代理连接,也后端代理连接认

    2024年01月25日
    浏览(53)
  • go入门实践四-go实现一个简单的tcp-socks5代理服务

    SOCKS是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递。协议在应用层和传输层之间。 本文使用先了解 socks 协议。然后实现一个socks5的tcp代理服务端。最后,进行抓包验证。 本文完整代码见仓库:laboratory/16-go-socks5 socks 协议相对http和tcp协议,还是比较

    2024年02月04日
    浏览(40)
  • 优化SOCKS5的方法

    在今天的互联网世界中,保护个人隐私和提升网络速度至关重要。作为一种常用的代理协议,SOCKS5代理服务器不仅可以保护您的隐私,还可以实现更快速的网络访问。本文将为您介绍一些优化SOCKS5代理服务器的方法,以提高网络速度和安全性。 1. 使用高性能服务器:选择强大

    2024年02月09日
    浏览(35)
  • 具有公网IP的服务器作为代理服务器,并使用Nginx将内网服务器反向代理

    在代理服务器上安装Nginx。如果您正在使用Linux操作系统,则可以使用包管理器来安装Nginx。例如,如果您使用的是Ubuntu,可以使用以下命令安装: 配置Nginx以将HTTP和HTTPS请求转发到内部服务器。打开Nginx的主配置文件/etc/nginx/nginx.conf,并在http块中添加以下代码: 注意: 将

    2024年01月25日
    浏览(48)
  • ExtJS 确实提供了大量的 UI 组件,用于构建具有丰富交互性的用户界面

    ExtJS 确实提供了大量的 UI 组件,用于构建具有丰富交互性的用户界面。ExtJS 是一个功能强大的 JavaScript 框架,主要用于构建富互联网应用程序(RIA)。以下是 ExtJS 提供的一些主要 UI 组件: 按钮(Buttons) :用于触发各种动作或事件的组件。 表单(Forms) :用于收集和提交用

    2024年02月03日
    浏览(47)
  • Socks5代理和IP代理

        Socks5代理和IP代理是常用的网络代理服务,它们为用户提供了匿名访问和保护隐私的功能。在本文中,我们将介绍这两种代理的基本概念和工作原理,并展示如何编写一个简单的代理服务器。 一、什么是Socks5代理和IP代理?      Socks5代理是一种网络代理协议,它允许用

    2024年02月16日
    浏览(45)
  • socks5代理 vs. HTTP代理

    如何使用代理IP保护网络安全 探讨代理IP在网络安全中的作用和重要性。 分析如何选择和配置合适的代理IP来提高网络的安全性。 提供使用代理IP保护隐私、防御DDoS攻击和绕过封锁的实用技巧和案例。 socks5代理与HTTP代理的比较与选择 介绍socks5代理和HTTP代理的基本原理和特点

    2024年02月15日
    浏览(68)
  • HTTP与SOCKS5的区别对比

    在互联网世界中,服务器是一种重要的工具,可以帮助我们提高网络安全性等。今天,我们将重点关注两种常见的技术:HTTP和SOCKS5。让我们深入了解它们的工作原理、用途和优缺点,并通过Python代码示例学习如何使用它们。 HTTP a. 工作原理 HTTP是一种基于HTTP协议的技术。当您

    2024年02月09日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包