狠狠色丁香婷婷综合尤物/久久精品综合一区二区三区/中国有色金属学报/国产日韩欧美在线观看 - 国产一区二区三区四区五区tv

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網站管理員

C#構建具有用戶認證與管理的socks5代理服務端

freeflydom
2025年5月12日 9:31 本文熱度 108

Socks 協(xié)議是一種代理 (Proxy) 協(xié)議, 例如我們所熟知的 Shdowsocks 便是 Socks 協(xié)議的一個典型應用程序, Socks 協(xié)議有多個版本, 目前最新的版本為 5, 其協(xié)議標準文檔為 RFC 1928。
我們一起來使用.net 7 構建一個支持用戶管理的高性能socks5代理服務端。

協(xié)議流程

1 client -> server 客戶端與服務端握手
VERSIONMETHODS_COUNTMETHODS
1字節(jié)1字節(jié)1到255字節(jié),長度zMETHODS_COUNT
0x050x030x00 0x01
0x02
  1. VERSION SOCKS協(xié)議版本,目前固定0x05
  2. METHODS_COUNT 客戶端支持的認證方法數(shù)量
  3. METHODS 客戶端支持的認證方法,每個方法占用1個字節(jié)

METHODS列表(其他的認證方法可以自行上網了解)

  1. 0x00 不需要認證(常用)
  2. 0x02 賬號密碼認證(常用)
2.1 server -> client 無需認證,直接進入第3步,命令過程
VERSIONMETHOD
1字節(jié)1字節(jié)
0x050x00
2.2、server -> client 密碼認證
VERSIONMETHOD
1字節(jié)1字節(jié)
0x050x02
2.2.1、client -> server 客戶端發(fā)送賬號密碼
VERSIONUSERNAME_LENGTHUSERNAMEPASSWORD_LENGTHPASSWORD
1字節(jié)1字節(jié)1到255字節(jié)1字節(jié)1到255字節(jié)
0x010x010x0a0x010x0a
  1. VERSION 認證子協(xié)商版本(與SOCKS協(xié)議版本的0x05無關系)
  2. USERNAME_LENGTH 用戶名長度
  3. USERNAME 用戶名字節(jié)數(shù)組,長度為USERNAME_LENGTH
  4. PASSWORD_LENGTH 密碼長度
  5. PASSWORD 密碼字節(jié)數(shù)組,長度為PASSWORD_LENGTH
2.2.2、server -> client 返回認證結果
VERSIONSTATUS
1字節(jié)1字節(jié)
0x010x00
  1. VERSION 認證子協(xié)商版本
  2. STATUS 認證結果,0x00認證成功,大于0x00認證失敗
3.1 client -> server 發(fā)送連接請求
VERSIONCOMMANDRSVADDRESS_TYPEDST.ADDRDST.PORT
1字節(jié)1字節(jié)1字節(jié)1字節(jié)1-255字節(jié)2字節(jié)
  1. VERSION SOCKS協(xié)議版本,固定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個字節(jié)為域名長度,剩下字節(jié)為域名名稱字節(jié)數(shù)組
    3. 0x04 IP V6地址
  5. DST.ADDR 目標服務器地址(如果COMMAND是0x03,即UDP模式,此處為客戶端啟動UDP發(fā)送消息的主機地址)
  6. DST.PORT 目標服務器端口(如果COMMAND是0x03,即UDP模式,此處為客戶端啟動UDP發(fā)送消息的端口)
3.2 server -> client 服務端響應連接結果
VERSIONRESPONSERSVADDRESS_TYPEDST.ADDRDST.PORT
1字節(jié)1字節(jié)1字節(jié)1字節(jié)1-255字節(jié)2字節(jié)
  1. VERSION SOCKS協(xié)議版本,固定0x05
  2. RESPONSE 響應命令,除0x00外,其它響應都應該直接斷開連接
    1. 0x00 代理服務器連接目標服務器成功
    2. 0x01 代理服務器故障
    3. 0x02 代理服務器規(guī)則集不允許連接
    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、數(shù)據轉發(fā)

第3步成功后,進入數(shù)據轉發(fā)階段

  1. CONNECT 則將client過來的數(shù)據原樣轉發(fā)到目標,接著再將目標回來的數(shù)據原樣返回給client
  2. BIND
  3. UDP ASSOCIATE
udp轉發(fā)的數(shù)據包
  1. 收到客戶端udp數(shù)據包后,解析出目標地址,數(shù)據,然后把數(shù)據發(fā)送過去
  2. 收到服務端回來的udp數(shù)據后,根據相同格式,打包,然后發(fā)回客戶端
RSVFRAGADDRESS_TYPEDST.ADDRDST.PORTDATA
2字節(jié)1字節(jié)1字節(jié)可變長2字節(jié)可變長
  1. RSV 保留為
  2. FRAG 分片位
  3. ATYP 地址類型
    1. 0x01 IP V4地址
    2. 0x03 域名地址(沒有打錯,就是沒有0x02),域名地址的第1個字節(jié)為域名長度,剩下字節(jié)為域名名稱字節(jié)數(shù)組
    3. 0x04 IP V6地址
  4. DST.ADDR 目標地址
  5. DST.PORT 目標端口
  6. DATA 數(shù)據

狀態(tài)機控制每個連接狀態(tài)

從協(xié)議中我們可以看出,一個Socks5協(xié)議的連接需要經過握手,認證(可選),建立連接三個流程。那么這是典型的符合狀態(tài)機模型的業(yè)務流程。

創(chuàng)建狀態(tài)和事件枚舉

public enum ClientState
    {
        Normal,
        ToBeCertified,
        Certified,
        Connected,
        Death
    }
    public enum ClientStateEvents
    {
        OnRevAuthenticationNegotiation, //當收到客戶端認證協(xié)商
        OnRevClientProfile, //收到客戶端的認證信息
        OnRevRequestProxy, //收到客戶端的命令請求請求代理
        OnException,
        OnDeath
    }

根據服務器是否配置需要用戶名密碼登錄,從而建立正確的狀態(tài)流程。

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);

在狀態(tài)扭轉中如果出現(xiàn)異常,則直接跳轉狀態(tài)到“Death”,

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

對應狀態(tài)扭轉創(chuàng)建相應的處理方法, 基本都是解析客戶端發(fā)來的數(shù)據包,判斷是否合理,最后返回一個響應。

/// <summary>
        /// 處理認證協(xié)商
        /// </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);//客戶端發(fā)起代理的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的認證方式,此時,客戶端需要上傳用戶名和密碼,如果認證成功我們就可以將用戶信息與連接對象做綁定,方便后續(xù)管理。

在客戶端通過tcp或者udp上傳數(shù)據包,需要代理服務器轉發(fā)時,我們記錄數(shù)據包的大小作為上傳數(shù)據包流量記錄下來,反之亦然。
示例:記錄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.我們每個連接會有一個定時服務,判斷是否過期
從而實現(xiàn)用戶下線。

//更新密碼或者過期時間后
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);
        }

持久化

用戶數(shù)據包括,用戶名密碼,使用流量,過期時間等存儲在server端的sqlite數(shù)據庫中。通過EFcore來增刪改查。
如下定期更新用戶流量到數(shù)據庫

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();
            }
        }

效果示例

打開服務

打開Proxifier配置到我們的服務

查看Proxifier已經流量走到我們的服務

服務端管理器

源碼以及如何使用

https://github.com/BruceQiu1996/Socks5Server

轉自https://www.cnblogs.com/qwqwQAQ/p/17410319.html

?


該文章在 2025/5/12 9:31:27 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業(yè)務管理,結合碼頭的業(yè)務特點,圍繞調度、堆場作業(yè)而開發(fā)的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統(tǒng),標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved