原本计划将这个拿来在内部分享时使用,由于太忙,拖了一次,结果他们就把我给忘了,于是Keynote写到一半,刚到AH与ESP就停下了。还是把这些内容整理成文章,看了很多资料后,差不多可以把标题理解为基于UDP的代理和基于TCP的代理(虽然有点不太恰当)。

1. shadowsocks

1.1 socks5协议

http://www.cppblog.com/noflybird/archive/2009/12/26/104149.html

Socks5协议中文文档

http://blog.csdn.net/testcs_dn/article/details/7915505

rfc1928 && rfc1929

https://www.ietf.org/rfc/rfc1928.txt

https://www.ietf.org/rfc/rfc1929.txt

1.2 socks5握手流程 {.p1}

https://lengzzz.com/note/socks5-proxy-in-lua

  • client:协商method
  • server:选用method
  • client:发起请求
  • server:处理请求并回复

客户端首次发起连接

screen-shot-2016-11-06-at-5-19-34-pm

服务端首次响应

screenshot884-56-28

客户端的后续请求格式

screenshot884-57-21

服务端的后续响应格式

screenshot884-57-57

1.3 shadowsocks协议 {.p1}

The shadowsocks protocol is very similar to SOCKS5 but encrypted and simpler.

shadowsocks协议与socks5非常相似,但它更简单,而且经过加密。它的主要传输流程如下

  • 客户端服务器使用预共享密钥
  • 本地 proxy server+服务端 server
  • 客户端与服务端间的使用加密传输,从外层看,只是单纯发送TCP数据包
  • 远程服务器转发与接受响应,回传到本地 proxy server
  • 发送请求 {.p1}

    app => local socks server(encrypt) => shadowsocks server(decrypt) => real host

    接收响应 {.p1}

    app <= (decrypt) local socks server <= (encrypt) shadowsocks server <= real host

    1.4 shadowsocks握手流程 {.p1}

    https://shadowsocks.org/en/spec/protocol.html

    客户端与服务端间交换数据的格式

    screenshot885-00-38

    对于每个TCP连接,首次发送的数据包会携带一个IV(初始向量),它会和预共享密钥一起,被应用在加密中,加密算法也是预先协定的。

    screenshot8885-19-18

    在交换了第一个数据包后,双方都有了IV,预共享密钥,加密算法,于是后续的数据包就不再需要IV了。

    screenshot885-20-43

    对于UDP连接,由于双方没有建立连接,因此需要在每个数据包中携带IV,因此每个数据包的格式都一样。

    screenshot8885-19-18

    在windows下,运行shadowsocks后,可以看到一个监听1080端口UDP程序,它的源地址是127.0.0.1,但目标地址为空,也就是说关闭了发送UDP数据包功能。在macOS下运行shadowsocks-libev,查看端口1080的占用,其中也没有关于UDP的。在服务器上运行的程序是shadowsocks-libev,查看下端口占用,可以看到一个UDP项。可见服务端的UDP转发虽然开放了,但客户端并没有直接使用,使用Wireshark在本地抓包,可以看到DNS解析是在本地完成的(从路由器获取)。

    关于UDP转发的还有一点不太清楚的地方,对于每个客户端和服务器之间(用户机器和shadowsocks代理服务器之间)的连接都是一个TCP连接,那么对于这个连接上的所有传输应该都可以使用同一套密钥加密的(包括IV,加密算法,预共享密钥)。在复用TCP连接的情况下,转发来自本地应用的UDP数据包时 ,应该是没有必要每次携带IV的(因为shadowsocks只在sslocal和ssserver之间进行加密),除非这些数据包可能通过不同的TCP连接发送到服务器或者客户机直接向代理服务器发送UDP数据包,因此每次携带不同的密钥就有必要了。网上对UDP转发的步骤没有太多的解释,不过按照文档来看,应该是将UDP数据包封装在TCP报文中,之后交付代理服务器拆封转发,Wireshark抓包中,客户端与代理服务器之间也只有TCP连接,服务端开放的UDP端口应该只用于在服务端和目标主机之间的数据传输。

    抓包分析可以看看这篇文章

    Socks代理和http代理的区别

    关于socks5协议的UDP转发可以看这个

    Understanding the Socks5 Protocol

    而服务端的数据连接就比较清晰了,我们在配置文件中定义的本地端口(1080)并没有使用,真正被使用的是对外开放的shadowsocks端口,ssserver程序监听这些端口,解密请求数据,并重写数据包的源地址,再与目标地址建立连接,获得响应数据,之后重写响应数据源地址,回送给客户端。此外利用并发的方式建立代理通道,也让shadowscoks的效率也得到巨大的提升。

    1.5 小结

    socks5与shadowsocks都是基于TCP的,不论它是转发的数据包基于TCP协议的还是基于UDP协议,每个客户机到目标主机的连接,都表现为一个客户机到代理服务器的TCP连接,以及代理服务器到目标主机的连接。数据包的封装层次从下到上也按照物理层,数据链路层,网络层,传输层,最后使用socks协议的格式封装载荷。

    可以尝试只使用chrome+SwitchyOmega+shadowsocks浏览网页,会发现每打开一个网站,在代理服务器上使用netstat -at中列出的连接数会继续增加,所以在优化shadowsocks的文章里,都有提到提升系统的连接数限制,特别是在大量用户使用的情况下。

    shadowsocks的协议参考了socks5,但将服务端拆分为两部分。一部分在本地,它应该是一个完整支持socks5协议的服务端,接收所有来自本地的数据包,它与本地应用间的数据连接和使用socks5代理时是一致的,采用socks5的格式封装所有传输的数据,这部分是明文传输,在抓包中也可以看到内容;另一部分在代理服务器上,它与本地服务间的数据传输使用TCP连接,从抓包结果看,它只是一个TCP数据包,只是载荷全部经过加密,代理服务器接收到客户机数据包后,解密再转发给目标主机,接收到目标主机的响应数据后,加密,回传给本地服务,再由本地服务转发给本地应用。此外端口也可以自定义,虽然默认服务端口还是sock5协议的1080,但客户机与代理服务器之间的端口可以随意指定。

    这两个协议位于传输层和应用层之间,那么理论上不支持socks5协议的应用是无法使用的,也无法承载更底层的协议数据包,如ICMP数据包,因此对于一些不支持socks5协议的应用,我们可以使用privoxy将http数据包转发为socks5数据包,大多数的应用都支持http代理。

    2. 网络环境

    shadowsocks协议的下一层是TCP协议,而TCP协议的下层是IP层,于是我们就有了一套自动管理网络连接的工具了。我们生活在一个TCP/IP协议族的世界里,所以很容易忘记NAT的存在了……

    理想的网络连接是一个host to host的连接,两个主机都拥有独立的IPv4地址,这样两个主机之间需要考虑的只有路由了,两个主机之间也是对等的,都可以各自主动发起连接,基本没有Client和Server的区别。但实际环境下,我们都是在内部网络发起连接,有时甚至经过多次的网络地址转换(NAT),关于NAT的类型,可以看看这篇文章。华三的文章还是十分专业的。

    第五期(NAT专题)

    NAT直接破坏了两个主机之间的对等性,只有拥有独立IP的主机才能作为服务器,这使服务器无法主动建立连接,即使有客户主动发起建立的连接,还需要进行保活,服务端主动推送数据变得困难。此外在网络地址转换过程中,路由器会修改报文,改写源IP地址与源端口,并重新计算校验值,如AH数据包以及传输模式下的ESP包直接无法通过,而其他一些不依赖TCP,且不被路由器支持的协议,也需要套上一层UDP的马甲才能顺利通过路由器。

    #