- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
Socks 协议是一种代理 (Proxy) 协议, 例如我们所熟知的 Shdowsocks 便是 Socks 协议的一个典型应用程序, Socks 协议有多个版本, 目前最新的版本为 5, 其协议标准文档为 RFC 1928。 我们一起来使用.net 7 构建一个支持用户管理的高性能socks5代理服务端 。
VERSION | METHODS_COUNT | METHODS |
---|---|---|
1字节 | 1字节 | 1到255字节,长度zMETHODS_COUNT |
0x05 | 0x03 | 0x00 0x01 0x02 |
METHODS列表(其他的认证方法可以自行上网了解) 。
VERSION | METHOD |
---|---|
1字节 | 1字节 |
0x05 | 0x00 |
VERSION | METHOD |
---|---|
1字节 | 1字节 |
0x05 | 0x02 |
VERSION | USERNAME_LENGTH | USERNAME | PASSWORD_LENGTH | PASSWORD |
---|---|---|---|---|
1字节 | 1字节 | 1到255字节 | 1字节 | 1到255字节 |
0x01 | 0x01 | 0x0a | 0x01 | 0x0a |
VERSION | STATUS |
---|---|
1字节 | 1字节 |
0x01 | 0x00 |
VERSION | COMMAND | RSV | ADDRESS_TYPE | DST.ADDR | DST.PORT |
---|---|---|---|---|---|
1字节 | 1字节 | 1字节 | 1字节 | 1-255字节 | 2字节 |
VERSION | RESPONSE | RSV | ADDRESS_TYPE | DST.ADDR | DST.PORT |
---|---|---|---|---|---|
1字节 | 1字节 | 1字节 | 1字节 | 1-255字节 | 2字节 |
第3步成功后,进入数据转发阶段 。
RSV | FRAG | ADDRESS_TYPE | DST.ADDR | DST.PORT | DATA |
---|---|---|---|---|---|
2字节 | 1字节 | 1字节 | 可变长 | 2字节 | 可变长 |
从协议中我们可以看出,一个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();
}
}
打开服务 。
打开Proxifier配置到我们的服务 。
查看Proxifier已经流量走到我们的服务 。
服务端管理器 。
https://github.com/BruceQiu1996/Socks5Server 。
最后此篇关于c#构建具有用户认证与管理的socks5代理服务端的文章就讲到这里了,如果你想了解更多关于c#构建具有用户认证与管理的socks5代理服务端的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我会尽可能地解释我正在做的事情,以获得最好的可能的建议/解决方案。这一切都是在 java 中完成的。 我的客户有一个基于 SWING 的桌面应用程序,它将使用 WebStart 加载。我被指派为用户帐
看来这个page包含 Azure CLI 支持的与 Azure API 管理相关的所有功能。但它没有展示如何使用 Azure CLI 管理用户、产品、证书、订阅和 API 等实体。 Azure CLI
我设置了一个 Hadoop 1.2.x 版本,双节点集群。第一节点(NameNode、Jobtracker)和第二节点(Secondary NameNode、Datanode、TaskTracker)
对于内容驱动的网站,设计好坏的关键是关系型数据库。在这个教程中,我们已经使用了MySQL关系型数据库管理系统(RDBMS)建立了我们的数据库。对于网站的开发者来说,MySQL是一个较受欢迎的选择,这
在尝试运行MariaDB之前,首先确定其当前状态,运行或关闭。 有三个选项用于启动和停止MariaDB – 运行mysqld(MariaDB脚本)。 运行mysqld_safe启动脚本。
我在管理界面中遇到 StackedInlines 前缀的问题。我会尝试发布所有必要的代码。 models.py(简要) ##### Base classes class BaseItem(models
我是新来的。到目前为止,我一直在使用 MVC 模型并使用基本的 session 管理模型,即在 session 中存储一个 token 并检查每个请求。 我正在尝试对lift做同样的事情,但我的 se
我在 win 服务中使用 NHiberante。有时我得到 System.ObjectDisposedException: Session is closed! Object name: 'ISess
我正在尝试使用 HtmlUnit 登录 Facebook 页面并查看其 HTML 内容。我正在尝试通过 HtmlUnit 填写登录凭据,但在单击提交按钮时我没有看到正在执行的 session 。 在
我正在为一个相当大的项目开发一个带有 reactjs 的前端,该项目有两个主要接口(interface)。主站点的前端和管理员的前端。 我应该将它们开发为两个不同的项目还是 reactjs 中的一个项
短版 我有一个使用插件基础结构的应用程序。插件具有可配置的属性,可帮助它们了解如何完成工作。插件按配置文件分组以定义如何完成任务,配置文件存储在由 DataContractSerializer 序列化
如何管理 iPhone 应用程序中的用户 session ?我在应用程序的第一页上从用户那里获取了用户名和密码。用户可以随时注销。如何像其他 Web 应用程序一样在 iPhone 应用程序中存储 se
我正在使用 Azure API 管理,其中包含第三方论坛 (Discourse) 的链接。 api管理提供的默认登录系统用于注册用户。我想知道是否可以对 api 管理和论坛使用单点登录,这样用户就不必
我正在使用 Wordpress 建立一个网站,并且我想利用它的 session 。但我没有找到任何插件,甚至文档。在我开始破解之前有什么建议或引用吗? 注意:我问的是 WP 是否以及如何使用标准 PH
我已阅读《Azure in Action》一书中的以下内容:“在 Windows Azure 中,状态服务器或进程外 session 状态提供程序,不支持” 谁能告诉我为什么不支持这个。他们在书中没有
我有一个内联表单集,我想排除一些模型对象在表单集中显示。 例如。模型 B 具有模型 A 的外键,因此它是 1:n(A 对象有许多 B 对象)关系。现在在 A 管理编辑页面上,我已经获得了 B 的内联。
我正在开发一个基于 session 的项目。我在想,与银行类似,我会创建一张支票并为用户提供阻止 session 超时的能力。 我正在考虑创建一个 setInterval 来检查需要身份验证的空白页面
我正在为一位拥有 Magento 商店的客户工作。里面塞满了产品,但这些产品的名称有点乱。他并没有坚持一种命名约定,而是多年来使用了不同的约定。因此,每当他使用“管理”->“管理产品”部分中的“名称”
我使用大约十几个 XSLT 文件来提供大量输出格式。目前,用户必须知道导出的文件格式的扩展名,例如RTF、HTML、TXT。 我还想使用参数来允许更多选项。如果我可以将元数据嵌入 XSL 文件本身,那
我已阅读《Azure in Action》一书中的以下内容:“在 Windows Azure 中,状态服务器或进程外 session 状态提供程序,不支持” 谁能告诉我为什么不支持这个。他们在书中没有
我是一名优秀的程序员,十分优秀!