5. 认识代理和 socks5
代理本质上是一个中间服务器。设客户端为 C,目标服务器为 S,代理服务器为 P,则他们的关系是:
可以看到,代理的主要工作就是转发数据。假设 C 和 S 之间存在某种障碍,导致他们之间无法直接进行通信,但是 C 和 P、P 和 S 之间可以自由通信,那么就可以利用 P 来进行 C 和 S 之间的通信。
本文接下来使用 client 指代 C,proxy server 指代 P,target server 指代 S。
socks5 是一个代理协议,位于应用层和传输层之间,也就是 HTTP 和 TCP 之间,所以 socks5 会用 TCP 来传输 HTTP 报文。
socks5 规定的具体通信过程如下(以最简单的不需要认证和 CONNECT CMD为例):
- client 发送一个 handshake:
+----+----------+----------+
|VER | NMETHODS | METHODS |
+----+----------+----------+
| 1 | 1 | 1 to 255 |
+----+----------+----------+
上图中,第二行的数字表示的含义不是值/取值范围,而是长度。比如 VER 下面的 1 代表 VER 这个数据的长度为 1 个字节。同理,NMETHODS 这个数据的长度为 1 个字节。而 METHODS 这个数据的长度为 1 至 255 个字节。
- 第一个字节 VER 为版本号,socks5 统一为
0x05。 - 第二个字节 NMETHODS 为 client 支持的认证方式的数量。
- 从第三个字节开始,每一个字节代表一种认证方式。
由于传递的是二进制数据,一个字节一个字节读取,信息是挨着的,没法区分界限,所以用特定的字节来表示某个数据的字节长度 n,用之后的 n 个字节来表示数据,这个方式非常常见。举个例子,127.0.0.14000 这13个字节,被读取的时候是没法知道哪部分是 ip,哪部分是 port。但是如果是09127.0.0.14000这 15 个字节,并且事先规定好前 2 个字节代表 ip 的长度,那么很轻松的就能分辨出,127.0.0.1是 ip,4000是端口。(一般情况下,长度会用无符号整数表示,上面的例子 09 实际上应该是 0x09)
- proxy 发送一个 response:
+----+--------+
|VER | METHOD |
+----+--------+
| 1 | 1 |
+----+--------+
- 第一个字节 VER 同样为版本号,固定
0x05。 - 第二个字节 METHOD 为服务端从客户端发来的认证方式中,挑选出来的一种认证方式。其中
0x00代表不需要认证。
- client 发送一个 request:
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
通过 handshake 建立连接后,client 就需要向 proxy 发请求。既然是代理协议,那么 client 至少需要告诉 proxy,我想访问的目标服务器的 ip 和 port,否则 proxy 根本不知道自己需要连接到哪里。
上述字段具体含义如下:
-
VER 不用多说
-
CMD 代表本次 socks 连接的命令
0x01表示 CONNECT0x02表示 BIND0x03表示 UDP 转发
-
RSV 为保留字段,一般保留字段都是
0x00 -
ATYP 为目标服务器地址的类型,DST.ADDR 为目标服务器的地址,DST.PORT 为目标服务器的端口
0x01表示 ipv4,此时 DST.ADDR 为 4 个字节。举个例子,ATYP + DST.ADDR + DST.PORT 为01 c0 a8 01 01代表目标服务器的 ip 为192.168.1.10x02表示域名。由于域名的长度不固定,DST.ADDR 的长度也不会固定。还记得针对这种变长的数据应该怎么传输吗,没错,length first。所以此时 DST.ADDR 会用第一个字节代表域名的长度 n ,后续 n 个字节代表域名。举个例子,ATYP + DST.ADDR + DST.PORT 为03 09 62 61 69 64 75 2e 63 6f 6d 01 bb代表目标服务器的域名是baidu.com,端口为4430x03表示 ipv6,此时 DST.ADDR 为 16 个字节
- proxy 发送一个 response
+----+-----+-------+------+----------+----------+
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
- REP 代表本次的响应类型,
0x00代表成功 - ATYPE 为 proxy 的地址类型
- BND.ADDR 为 proxy 的地址,BND.PORT 为 proxy 的端口
这里的 BND.ADDR 是 proxy 自身的地址,BND.PORT 是 proxy 用来发起与 target server 连接的端口,而不是与 client 连接的端口。这两个值的作用查阅了部分文章,大致意思是:socks5 proxy server 可能是一个集群,不止一个 server,可能用于开始与 client 进行 socks5 交互的 server 和后续转发消息的 server 不是同一个。但是经过我的实验,BND.ADDR 和 BND.PORT 随便写了两个值,照样完成了转发。个人感觉这两个值实际上是用于 BIND CMD(忘记 BIND 是什么的,看上面 client 发送的 request,CMD 有三种类型)。总而言之,一般情况下,BND.ADDR 写0x00000000,BND.PORT 写 0X0000。
- 开始实际上的数据转发
经过上面的 socks5 协议的部分之后,client 就会将真正需要转发的数据发给 proxy server,proxy server 需要转发给 target server。同理,target server 处理完来自 client 的数据后,也会发送自己的数据给 proxy server,proxy server 需要转发回给 client。这里的转发就是简单的 TCP socket 操作了,详见第三章。