# socks5-proxy **Repository Path**: TenzT/socks5-proxy ## Basic Information - **Project Name**: socks5-proxy - **Description**: No description available - **Primary Language**: Go - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-09-30 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SOCKS5_PROXY - 基于go实现socks5代理服务器 ## 初版,本地代理 ### 测试方式 - Proxifier配置(懒得写socks5客户端): - Proxifer的Proxies上增加socks5的Action,将电脑上的网络请求收束到服务器端口 - 启动socks5-proxy服务: 1. `go build -o proxy` 2. `./proxy` - 浏览器测试法:本地采用chrome进行测试,可以看到应用为`Google Chrome Helper`的都会被拦截,如下图所示。 ![](docs/Proxifier抓包.png) 此时在Proxifier的Rules里加上拦截`Google Chrome Helper`的规则即可 ![](docs/浏览器测试法.png) - 服务器测试法:用go写一个简单的HTTP服务器,实现基本的echo行为即可,详见`mock_server/mock_http_server.go`。同时加上Proxifier规则,在这里我使用curl进行测试,并对部署在本地的9999端口上的HTTP服务进行访问,因此规则应该为{Applications:curl, Target Hosts:127.0.0.1, TargetPorts: 9999, Action: 连接到socks5的8081端口} - `curl -H 'Connection: close' 127.0.0.1:9999/hello` ### 踩坑 1. 使用服务器测试法时一开始没有意识到Proxifier还需要增加Application的维度进行拦截,导致proxy新拉起的到9999的连接又被Proxifer拦截后打回到proxy上,然后就无限套娃了。所以用Proxifier要尽可能控制好拦截的粒度。 2. HTTP v1.1协议默认保持长连接,即不同的HTTP请求复用同一个TCP连接。通过`lsof -i:9999`查到连接仍是`ESTABLISHED`状态,所以ic.Copy因为读不到EOF而无法终止,导致无法释放连接,有大量建立了的连接无法释放;而当开始设计时为了好调试而没开goroutine处理每个连接,导致后面新的连接被这个无法自己释放的连接给阻塞住了。通过wireshark抓包确认没有`FIN`包。后面在请求头加上`Connection: close`明确指示成短连接,让HTTP服务器处理完之后马上释放连接,整个流程就跑通了。 ## 第二版——单协程的远程proxy - 背景:生产环境的机器往往部署在一个独立的网络中,需要通过ssh登录跳板机后才能进行运维相关的操作。CLI的界面使用起来相对麻烦,因此考虑构建一个代理服务器,将本地用一些CGI的运维工具时产生的网络数据传输代理到生产环境的网络中。 - 思路:代理拆分成本地client和远端server两部分,client负责接收本地的网络请求,并转发到server,server通过socks5协议对请求进行代理。client在启动时通过`ssh`命令行操作拉起远端的server,随后client通过server的stdio与server进行通信。 - 缺陷:目前的版本是本地直接利用`io.Copy`直接将client中Accept到的或者server中Dial形成TCP连接直接与server的stdio进行数据复制。原定的目标是stdio一直保持连接,然后在该通道上运输网络请求,因此server在拉起后会一直保持alive,不关闭stdio。然而`io.Copy`只有在源端关闭后才能读到EOF,进而结束复制。因此在client上以stdout为源端的复制操作会一直保持,阻塞后面新的连接。 - 优化思路:网络连接不与stdio直接连接,而是通过一个适配器。适配器通过在连接关闭时往对端写一个结束用的消息,来把“本地连接关闭”这个消息传达到远端,收到结束消息后的适配器被读的时候会返回EOF错误,结束复制操作。 ## 第三版——单协程的远程proxy优化 - 思路:为建立好的tunnel抽象成一个持续运输数据的流,同时考虑到后期需要扩展成并发的服务,因此从流中构建适配器。适配器和网络连接直接通过`io.Copy`作数据复制。 - 细节 1. client的适配器是主动拉起的,server端的Adaptor是被动拉起的 2. 使用proxifier时,socks5协议必须严格按照协议指定的长度来握手,比如第一次ack应该是[0x50, 0x00]的话,则不能够写成[0x50, 0x00, 0x00, 0x00],否则会引起RST - 缺陷:只能完成一次请求响应,再有新的连接无响应。 - 背后原因是在第一次的连接被关闭后,没有关闭适配器的input通道,新连接的数据进入了该通道。然而关闭后的adaptor不会再从该通道读取数据,因此stream的读任务被阻塞。 ## 第四版——并发的代理服务器 - 思路: 1. 实现通信协议,使得在stream层能够清晰得到nil 2. 为不同连接的适配器构造不同的input通道,多个input复用同一个stream - 踩坑:开始时适配器的Close方法没有加读写锁保护,导致Adaptor<-->Conn的两个复制流程结束后同时运行Close方法,并向远端写入了两次nil。对端在收到第一个nil后关闭了适配器,第二个nil会进入input通道,由于关闭后的适配器不会继续读数据,导致stream的读任务卡在了input上。 - 缺陷:当前只实现基本连接功能,还没提高系统的鲁棒性,比如强杀client后远端的server不会自动退出或是input通道在适配器关闭后没有随之关闭等,在后续版本中进行更新。 # 待实现 - 使用配置文件加载host和port # 已实现 - 重构代码,将握手部分抽离出来 - 补全MakeFile - 单协程的远程proxy: client一次只处理一个连接,远近端通过stdio进行通信。 - 加入消息复用后的远程proxy