超碰人人人人人,亚洲AV午夜福利精品一区二区,亚洲欧美综合区丁香五月1区,日韩欧美亚洲系列

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

C#構(gòu)建具有用戶認(rèn)證與管理的socks5代理服務(wù)端

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

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

協(xié)議流程

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

METHODS列表(其他的認(rèn)證方法可以自行上網(wǎng)了解)

  1. 0x00 不需要認(rèn)證(常用)
  2. 0x02 賬號密碼認(rèn)證(常用)
2.1 server -> client 無需認(rèn)證,直接進(jìn)入第3步,命令過程
VERSIONMETHOD
1字節(jié)1字節(jié)
0x050x00
2.2、server -> client 密碼認(rèn)證
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 認(rèn)證子協(xié)商版本(與SOCKS協(xié)議版本的0x05無關(guān)系)
  2. USERNAME_LENGTH 用戶名長度
  3. USERNAME 用戶名字節(jié)數(shù)組,長度為USERNAME_LENGTH
  4. PASSWORD_LENGTH 密碼長度
  5. PASSWORD 密碼字節(jié)數(shù)組,長度為PASSWORD_LENGTH
2.2.2、server -> client 返回認(rèn)證結(jié)果
VERSIONSTATUS
1字節(jié)1字節(jié)
0x010x00
  1. VERSION 認(rèn)證子協(xié)商版本
  2. STATUS 認(rèn)證結(jié)果,0x00認(rèn)證成功,大于0x00認(rèn)證失敗
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 連接上游服務(wù)器
    2. 0x02 BIND 綁定,客戶端會接收來自代理服務(wù)器的鏈接,著名的FTP被動模式
    3. 0x03 UDP ASSOCIATE UDP中繼
  3. RSV 保留字段
  4. ADDRESS_TYPE 目標(biāo)服務(wù)器地址類型
    1. 0x01 IP V4地址
    2. 0x03 域名地址(沒有打錯,就是沒有0x02),域名地址的第1個字節(jié)為域名長度,剩下字節(jié)為域名名稱字節(jié)數(shù)組
    3. 0x04 IP V6地址
  5. DST.ADDR 目標(biāo)服務(wù)器地址(如果COMMAND是0x03,即UDP模式,此處為客戶端啟動UDP發(fā)送消息的主機(jī)地址)
  6. DST.PORT 目標(biāo)服務(wù)器端口(如果COMMAND是0x03,即UDP模式,此處為客戶端啟動UDP發(fā)送消息的端口)
3.2 server -> client 服務(wù)端響應(yīng)連接結(jié)果
VERSIONRESPONSERSVADDRESS_TYPEDST.ADDRDST.PORT
1字節(jié)1字節(jié)1字節(jié)1字節(jié)1-255字節(jié)2字節(jié)
  1. VERSION SOCKS協(xié)議版本,固定0x05
  2. RESPONSE 響應(yīng)命令,除0x00外,其它響應(yīng)都應(yīng)該直接斷開連接
    1. 0x00 代理服務(wù)器連接目標(biāo)服務(wù)器成功
    2. 0x01 代理服務(wù)器故障
    3. 0x02 代理服務(wù)器規(guī)則集不允許連接
    4. 0x03 網(wǎng)絡(luò)無法訪問
    5. 0x04 目標(biāo)服務(wù)器無法訪問(主機(jī)名無效)
    6. 0x05 連接目標(biāo)服務(wù)器被拒絕
    7. 0x06 TTL已過期
    8. 0x07 不支持的命令
    9. 0x08 不支持的目標(biāo)服務(wù)器地址類型
    10. 0x09 - 0xFF 未分配
  3. RSV 保留字段
  4. BND.ADDR 代理服務(wù)器連接目標(biāo)服務(wù)器成功后的代理服務(wù)器IP
  5. BND.PORT 代理服務(wù)器連接目標(biāo)服務(wù)器成功后的代理服務(wù)器端口
4、數(shù)據(jù)轉(zhuǎn)發(fā)

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

  1. CONNECT 則將client過來的數(shù)據(jù)原樣轉(zhuǎn)發(fā)到目標(biāo),接著再將目標(biāo)回來的數(shù)據(jù)原樣返回給client
  2. BIND
  3. UDP ASSOCIATE
udp轉(zhuǎn)發(fā)的數(shù)據(jù)包
  1. 收到客戶端udp數(shù)據(jù)包后,解析出目標(biāo)地址,數(shù)據(jù),然后把數(shù)據(jù)發(fā)送過去
  2. 收到服務(wù)端回來的udp數(shù)據(jù)后,根據(jù)相同格式,打包,然后發(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 目標(biāo)地址
  5. DST.PORT 目標(biāo)端口
  6. DATA 數(shù)據(jù)

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

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

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

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

根據(jù)服務(wù)器是否配置需要用戶名密碼登錄,從而建立正確的狀態(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)扭轉(zhuǎn)中如果出現(xiàn)異常,則直接跳轉(zhuǎn)狀態(tài)到“Death”,

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

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

/// <summary>
        /// 處理認(rèn)證協(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默認(rèn)頭為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) //是否支持賬號密碼認(rèn)證
            {
                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>
        /// 接收到客戶端認(rèn)證
        /// </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);//目標(biāo)服務(wù)器的終結(jié)點
                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);
                        }
                        //代理端啟動的端口信息回復(fù)給客戶端
                        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終結(jié)點
                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.");
            }
        }

連接與用戶管理

當(dāng)服務(wù)器采用需要認(rèn)證的配置時,我們會返回給客戶端0x02的認(rèn)證方式,此時,客戶端需要上傳用戶名和密碼,如果認(rèn)證成功我們就可以將用戶信息與連接對象做綁定,方便后續(xù)管理。

在客戶端通過tcp或者udp上傳數(shù)據(jù)包,需要代理服務(wù)器轉(zhuǎn)發(fā)時,我們記錄數(shù)據(jù)包的大小作為上傳數(shù)據(jù)包流量記錄下來,反之亦然。
示例:記錄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);
        }

當(dāng)管理界面修改某用戶的密碼或者過期時間的時候
1.修改密碼,強(qiáng)制目前所有使用該用戶名密碼的連接斷開
2.我們每個連接會有一個定時服務(wù),判斷是否過期
從而實現(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ù)據(jù)包括,用戶名密碼,使用流量,過期時間等存儲在server端的sqlite數(shù)據(jù)庫中。通過EFcore來增刪改查。
如下定期更新用戶流量到數(shù)據(jù)庫

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

效果示例

打開服務(wù)

打開Proxifier配置到我們的服務(wù)

查看Proxifier已經(jīng)流量走到我們的服務(wù)

服務(wù)端管理器

源碼以及如何使用

https://github.com/BruceQiu1996/Socks5Server

轉(zhuǎn)自https://www.cnblogs.com/qwqwQAQ/p/17410319.html

?


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