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

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

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

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

協(xié)議流程

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

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

  1. 0x00 不需要認(rèn)證(常用)
  2. 0x02 賬號(hào)密碼認(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ā)送賬號(hào)密碼
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 用戶名長(zhǎng)度
  3. USERNAME 用戶名字節(jié)數(shù)組,長(zhǎng)度為USERNAME_LENGTH
  4. PASSWORD_LENGTH 密碼長(zhǎng)度
  5. PASSWORD 密碼字節(jié)數(shù)組,長(zhǎng)度為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ā)送連接請(qǐng)求
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 綁定,客戶端會(huì)接收來自代理服務(wù)器的鏈接,著名的FTP被動(dòng)模式
    3. 0x03 UDP ASSOCIATE UDP中繼
  3. RSV 保留字段
  4. ADDRESS_TYPE 目標(biāo)服務(wù)器地址類型
    1. 0x01 IP V4地址
    2. 0x03 域名地址(沒有打錯(cuò),就是沒有0x02),域名地址的第1個(gè)字節(jié)為域名長(zhǎng)度,剩下字節(jié)為域名名稱字節(jié)數(shù)組
    3. 0x04 IP V6地址
  5. DST.ADDR 目標(biāo)服務(wù)器地址(如果COMMAND是0x03,即UDP模式,此處為客戶端啟動(dòng)UDP發(fā)送消息的主機(jī)地址)
  6. DST.PORT 目標(biāo)服務(wù)器端口(如果COMMAND是0x03,即UDP模式,此處為客戶端啟動(dòng)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é)可變長(zhǎng)2字節(jié)可變長(zhǎng)
  1. RSV 保留為
  2. FRAG 分片位
  3. ATYP 地址類型
    1. 0x01 IP V4地址
    2. 0x03 域名地址(沒有打錯(cuò),就是沒有0x02),域名地址的第1個(gè)字節(jié)為域名長(zhǎng)度,剩下字節(jié)為域名名稱字節(jié)數(shù)組
    3. 0x04 IP V6地址
  4. DST.ADDR 目標(biāo)地址
  5. DST.PORT 目標(biāo)端口
  6. DATA 數(shù)據(jù)

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

從協(xié)議中我們可以看出,一個(gè)Socks5協(xié)議的連接需要經(jīng)過握手,認(rèn)證(可選),建立連接三個(gè)流程。那么這是典型的符合狀態(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, //收到客戶端的命令請(qǐng)求請(qǐng)求代理
        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);
            };

對(duì)應(yīng)狀態(tài)扭轉(zhuǎn)創(chuàng)建相應(yīng)的處理方法, 基本都是解析客戶端發(fā)來的數(shù)據(jù)包,判斷是否合理,最后返回一個(gè)響應(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) //校驗(yàn)報(bào)文
            {
                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) //是否支持賬號(hào)密碼認(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>
        /// 客戶端請(qǐng)求連接
        /// </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é)點(diǎn)
                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);
                        }
                        //代理端啟動(dòng)的端口信息回復(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é)點(diǎn)
                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)證的配置時(shí),我們會(huì)返回給客戶端0x02的認(rèn)證方式,此時(shí),客戶端需要上傳用戶名和密碼,如果認(rèn)證成功我們就可以將用戶信息與連接對(duì)象做綁定,方便后續(xù)管理。

在客戶端通過tcp或者udp上傳數(shù)據(jù)包,需要代理服務(wù)器轉(zhuǎn)發(fā)時(shí),我們記錄數(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)管理界面修改某用戶的密碼或者過期時(shí)間的時(shí)候
1.修改密碼,強(qiáng)制目前所有使用該用戶名密碼的連接斷開
2.我們每個(gè)連接會(huì)有一個(gè)定時(shí)服務(wù),判斷是否過期
從而實(shí)現(xiàn)用戶下線。

//更新密碼或者過期時(shí)間后
public void UpdateUserPasswordAndExpireTime(string password, DateTime dateTime)
        {
            if (password != Password)
            {
                Dispose();
            }
            if (DateTime.Now > ExpireTime)
            {
                Dispose();
            }
        }
/// <summary>
        /// 過期自動(dòng)下線
        /// </summary>
        public void WhenExpireAutoOffline()
        {
            Task.Run(async () =>
            {
                while (true)
                {
                    if (DateTime.Now > ExpireTime)
                    {
                        Dispose();
                    }
                    await Task.Delay(1000);
                }
            }, CancellationTokenSource.Token);
        }

持久化

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

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

黄频国产免费高清视频,久久不卡精品中文字幕一区,激情五月天AV电影在线观看,欧美国产韩国日本一区二区
人人狠狠综合久久亚洲区 | 日本特黄特色特刺激大片 | 亚洲午夜精品宅男国产天堂 | 日韩精品一区二区蜜桃 | 一本色道久久综合亚洲精品不卡 | 亚洲视频免费观看 |