Go语言基础-Proxy实现
Go语言基础-Proxy的实现
一、前置知识:SOCKS5代理
SOCKS是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递。SOCKS是“SOCKet Secure”的缩写。
某些企业为了安全性,设置了很强的防火墙,带来的副作用是哪怕你是管理员,访问资源也很麻烦。SOCKS5就是开了一个小口子,用来访问资源的。
防火墙内的客户端想要访问外部的服务器时,会跟SOCKS代理服务器进行连接。客户端的请求会通过代理服务器发送到外部的服务器,最终响应也会通过代理服务器发送回客户端。
基本模型
协商过程(认证)
SOCKS5比SOCKS4a多了验证、IPv6、UDP支持。
建立与SOCKS5服务器的TCP连接后客户端需要先发送请求来确认协议版本及认证方式,格式为(以字节为单位):
VER | NMETHODS | METHODS |
---|---|---|
1 | 1 | 1-255 |
- VER是SOCKS版本,这里应该是0x05;
- NMETHODS是METHODS部分的长度;
- METHODS是客户端支持的认证方式列表,每个方法占1字节。基本的有:
- 0x00 不需要认证
- 0x01 GSSAPI
- 0x02 用户名、密码认证
服务器从客户端提供的方法中选择一个并通过以下消息通知客户端(以字节为单位):
VER | METHOD |
---|---|
1 | 1 |
- VER是SOCKS版本,这里应该是0x05;
- METHOD是服务端选中的方法。如果返回0xFF表示没有一个认证方法被选中,客户端需要关闭连接。
请求阶段
客户端发送请求的格式
认证结束后客户端就可以发送请求信息。如果认证方法有特殊封装要求,请求必须按照方法所定义的方式进行封装。
SOCKS5请求格式(以字节为单位):
VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
---|---|---|---|---|---|
1 | 1 | 0x00 | 1 | 动态 | 2 |
- VER是SOCKS版本,这里应该是0x05;
- CMD是SOCK的命令码
- 0x01表示CONNECT请求
- 0x02表示BIND请求
- 0x03表示UDP转发
- RSV 0x00,保留
- ATYP DST.ADDR类型
- 0x01 IPv4地址,DST.ADDR部分4字节长度
- 0x03 域名,DST.ADDR部分第一个字节为域名长度,DST.ADDR剩余的内容为域名,没有\0结尾。
- 0x04 IPv6地址,16个字节长度。
- DST.ADDR 目的地址
- DST.PORT 网络字节序表示的目的端口
服务器响应的格式
服务器按以下格式回应客户端的请求(以字节为单位):
VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
---|---|---|---|---|---|
1 | 1 | 0x00 | 1 | 动态 | 2 |
- VER是SOCKS版本,这里应该是0x05;
- REP应答字段
- 0x00表示成功
- 0x01普通SOCKS服务器连接失败
- 0x02现有规则不允许连接
- 0x03网络不可达
- 0x04主机不可达
- 0x05连接被拒
- 0x06 TTL超时
- 0x07不支持的命令
- 0x08不支持的地址类型
- 0x09 - 0xFF未定义
- RSV 0x00,保留
- ATYP BND.ADDR类型
- 0x01 IPv4地址,DST.ADDR部分4字节长度
- 0x03域名,DST.ADDR部分第一个字节为域名长度,DST.ADDR剩余的内容为域名,没有\0结尾。
- 0x04 IPv6地址,16个字节长度。
- BND.ADDR 服务器绑定的地址
- BND.PORT 网络字节序表示的服务器绑定的端口
二、前置知识:bufio包的使用
bufio是有缓存的io,避免因为多次写操作而降低程序的性能。
具体可以参考这个文章:https://zhuanlan.zhihu.com/p/129781512
有时间,再自己总结吧...
三、代码实现
1. v1:简易的echo server
与linuxC++的实现过程类似。
- 首先监听IP地址和端口号,调用net.Listen();
- 调用server.Accept()等待并建立连接;
- 当有连接建立之后,启动一个协程,来处理这个连接的数据
1 |
|
nc安装教程如下:https://www.cnblogs.com/linyufeng/p/13206252.html
使用nc命令可以直接和一个ip端口建立tcp连接。
2. v2:协商阶段
main函数不需要修改,在process函数中新增鉴权的部分:
1 |
|
auth函数的实现流程如下:
- 使用reader.ReadByte将版本号读取出来;
- 使用reader.ReadByte将mthodsize读取出来,并建立相应大小的method数组;
- 使用io.ReadFull将method填充完全;
- 往连接中写入版本号和方法
conn.Write([]byte{socks5Ver, 0x00})
运行程序后,在终端执行curl --socks5 127.0.0.1:1080 -v http://www.qq.com
,可以显示出版本号、method和认证成功。
3. v3:请求阶段
我们在auth下面再实现一个connect函数,该函数用于读取客户端发送的数据,然后与服务器进行tcp连接;之后将连接的信息发送回给客户端。
1 |
|
读取客户端的数据主要包括版本号Ver、命令码CMD、保留字RSB、ATYP目标地址类型、DST.ADDR 一个可变长度的值、DST.PORT 目标端口。
写回给客户端的过程属性类似,略。
运行程序后,可以正确返回ip地址和端口号:
4. v4:relay阶段
将客户端的请求发送到服务器,将服务器的响应发送回给客户端。
1 |
|
运行程序如下所示:
5.使用插件进行测试
可以使用Proxy SwitchyOmega插件。
该软件本身不提供给代理服务器,需要连接我们自己的代理服务器。
在启动右上角的小圈圈后,浏览器访问的网页都会经过我们的代理服务器。
参考资料
[1] https://zhuanlan.zhihu.com/p/129781512
[2] https://zh.m.wikipedia.org/zh-hans/SOCKS
[3] 字节青训营《Go语言上手 - 基础语言》