go 语言实战入门案例之实现 Socks5
文章和代码已经归档至【Github 仓库:https://github.com/timerring/backend-tutorial 】或者公众号【AIShareLab】回复 go 也可获取。
实现 Socks 5 代理
介绍
socks5 协议它虽然是代理协议,但它并不能用来翻,它的协议都是明文传输。这个协议历史比较久远,诞生于互联网早期。它的用途是, 比如某些企业的内网为了确保安全性,有很严格的防火墙策略,但是带来的副作用就是访问某些资源会很麻烦。socks5 相当于在防火墙开了个口子,让授权的用户可以通过单个端口去访问内部的所有资源。实际上很多翻软件,最终暴露的也是一个 socks5 协议的端口。例如爬,在爬取过程中很容易会遇到 IP 访问频率超过限制。这个时候很多人就会去网上找一些代理 IP 池,这些代理 IP 池里面的很多代理的协议就是 socks5。
先看一下最终写完的代理服务器的效果。启动程序,然后在浏览器里面配置使用这个代理,此时打开网页。代理服务器的日志,会打印出你访问的网站的域名或者 IP ,这说明我们的网络流量是通过这个代理服务器的。也能在命令行去测试我们的代理服务器。我们可以用 curl -socks5 + 代理服务器地址,后面加一个可访问的 URL,如果代理服务器工作正常的话,那么 curl 命令就会正常返回。
原理
了解 socks5 协议的工作原理。正常浏览器访问一个网站,如果不经过代理服务器的话,就是先和对方的网站建立 TCP 连接,然后三次握手,握手完之后发起 HTTP 请求,然后服务返回 HTTP 响应。
如果设置代理服务器之后,流程会变得复杂一些。首先是浏览器和 socks5 代理建立 TCP 连接,代理再和真正的服务器建立 TCP 连接。这里可以分成四个阶段,握手阶段、认证阶段、请求阶段、 relay 阶段。
第一个握手阶段,浏览器会向 socks 5 代理发送请求,包的内容包括一个协议的版本号,还有支持的认证的种类,socks 5 服务器会选中一个认证方式,返回给浏览器。如果返回的是 00 的话就代表不需要认证,返回其他类型的话会开始认证流程,这里我们就不对认证流程进行概述了。
第三个阶段是请求阶段,认证通过之后浏览器会对 socks 5 服务器发起请求。主要信息包括版本号,请求的类型,一般主要是 connection 请求,就代表代理服务器要和某个域名或者某个 IP 地址某个端口建立 TCP 连接。代理服务器收到响应之后,会真正和后端服务器建立连接,然后返回一个响应。
第四个阶段是 relay 阶段。此时浏览器会发送正常发送请求,然后代理服务器接收到请求之后,会直接把请求转换到真正的服务器上。然后如果真正的服务器以后返回响应的话,那么也会把请求转发到浏览器这边。然后实际上代理服务器并不关心流量的细节,可以是 HTTP 流量,也可以是其它 TCP 流量。
TCP echo server
我们先在 go 里面写一个简单的 TCP echo server。为了方便测试, server 的工作逻辑很简单,你给他发送啥,他就回复啥,代码如下。
我们来简单测试一下我们的第一个 TCP 服务器,然后测试会需要用到 nc 命令。如果没有的话可以进行安装,这里用 nc 127.0.0.1 10803
,输入 timerring 然后服务器就会给你返回 timerring。先运行代码
然后另开一个终端进行测试:
auth
我们回忆一下认证阶段的逻辑,首先第一步的话,浏览器会给代理服务器发送一个包,然后这个包有三个字段,
第一个字段, version 也就是协议版本号,固定是 5
第二个字段 methods,认证的方法数目
第三个字段每个 method 的编码, 0 代表不需要认证, 2 代表用户名密码认证我们用 curl 命令测试一下当前版本的效果。此时 curl 命令肯定是不成功的,因为我们的协议还没实现完成。
但是我们看日志会发现, version 和 method 可以正常打印,说明当前我们的实现是正确的。
请求阶段
接下来我们开始做第三步,实现请求阶段,我们试图读取到携带 URL 或者 IP 地址+端口的包,然后把它打印出来。
现在我们来测试一下当前阶段的成果, 简单 curl 一下。
此时请求还是会失败,我们现在已经能看到正常打印出来访问的 IP 地址和端口,这说明我们当前的实现正常,这样我们就可以做最后一步,我们真正和这个端口建立连接,双向转发数据。
我们直接用 net.dial 建立一个 TCP 连接,建立完连接之后,我们同样要加一个 defer 来关闭连接。接下来需要建立浏览器和下游服务器的双向数据转发。
标准库的 io.copy 可以实现一个单向数据转发,双向转发的话,需要启动两个 goroutinue。
现在有一个问题,connect 函数会立刻返回,返回的时候连接就被关闭了。需要等待任意一个方向 copy 出错的时候,再返回 connect 函数。这里可以使用到标准库里面的一个 context 机制,用 context 连 with cancel 来创建一个 context。
在最后等待 ctx.Done() ,只要 cancel 被调用, ctx.Done 就会立刻返回。然后在上面的两个 goroutinue 里面调用一次 cancel 即可。
完整代码
我们可以试着在浏览器里面再测试一下,在浏览器里面测试代理需要安装这个 switchomega 插件,然后里面新建一个情景模式,代理服务器选 socks 5,端口 1080 ,保存并启用。此时你应该还能够正常地访问网站,代理服务器这边会显示出浏览器版本的域名和端口。
学习路线推荐
版权声明: 本文为 InfoQ 作者【timerring】的原创文章。
原文链接:【http://xie.infoq.cn/article/3bd596276f084e4d9b2ede959】。未经作者许可,禁止转载。
评论