WSocks疯牛病版技术内幕

项目地址

https://github.com/Wooyme/Wsocks
kcp-raw分支

前言

之前在LowEndBox上看到一个非常非常便宜的VPS,一年8刀。借着50块买不了吃亏,买不了上当的想法,就随手买了一年。当然,这个机器确实非常垃圾。不过这也很正常,OpenVZ的低价VPS肯定是超售的,而这样便宜的机器要是没有问题才是真的有问题。
最开始,我尝试在上面装了一下WSocks2.0,然后发现速度只有几k,基本上就是打开个网页都卡的水平,而且因为是openvz(最坑的是说好的SolusVM到了最后也不知所踪)所以bbr和锐速都上不了,本来是打算就此作罢了。但是无意中看到了几篇关于bbr原理的文章,然后几经周折,被打开了新世界的大门。

TCP拥塞控制

装过bbr或者锐速的朋友肯定体验过这两个补丁近乎开挂的速度。尤其是锐速,凭借其更加流氓的算法,能几乎把带宽跑满。那么为什么使用了这些模块之后能让速度更快呢? 这就要谈到TCP的拥塞控制了。

源自简书 https://www.jianshu.com/p/97e5d7e73ba0

拥塞控制就是防止过多的数据注入到网络中,这样可以防止网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机、所有的路由器,以及与降低网络传输性能有关的所有因素。

那么防止链路过载的方式主要有两种,一是 慢开始, 二是 拥塞避免

  • 慢开始

    慢开始算法中的主要方法就是有小到大逐渐增大发送窗口。
    举个例子:首先,发送方设置cwnd=1(为方便理解,这里用报文段的个数作为窗口大小的单位),在收到接收方发来的确认后(也就是下个传输轮次),设置cwnd=2,然后将发送窗口的数据发送出去。在一次收到接收方发来的确认后,发送方设置cwnd=4,再讲发送窗口中的数据发送出去。然后再重复上面的过程。

  • 拥塞避免

    TCP/IP 中规定无论是在慢开始阶段还是在拥塞避免阶段,只要发现网络中出现拥塞(没有按时收到确认),就要把ssthresh设置为此时发送窗口的一半大小(不能小于2)。

主要的问题在于拥塞避免阶段,常用的拥塞避免算法中,一旦发现超时,窗口大小就会下降的非常快,并且之后上升的速度也会非常慢,于是传输速度就很垃圾了。
而bbr和锐速都是修改了这部分算法,让窗口保持在比较大的阶段。当然这种行为本质是流氓行为,不然在Linux 4.10之后的内核中也不会默认关闭bbr。当只有少数人使用的时候,网速会得到非常高的提升,但是一旦大家都用了这种算法,效果也就会趋于平庸了。

更无耻的UDP

可是我们的OpenVZ并不能安装bbr或锐速。于是从内核层面修改算法就显得不太现实了。但是除了TCP之外,我们还有一个更加简单的协议可以选择————UDP。
UDP是真的很简单,没有流控、没有拥塞控制、也不管数据是否真的传达到了。一旦用了UDP,只管发就是了。当然,简单的背后就是各种的不可靠,无论是丢包或是到达顺序错误都会导致上层出错,这显然是我们不想看到的。于是Google了许久,找到了一个国人开发的UDP Base协议——KCP。

KCP https://github.com/skywind3000/kcp

KCP为UDP添加了类似连接的机制,包括连接标识符,ack,窗口,缓冲区等。它让UDP成为了可靠的传输协议。而且由于这些东西都是KCP添加的,所以都不受制于内核,完全可以自己调节。作者就为我们提供了 “极速模式”,实测效果确实很给力,通过开启快速重传、关闭流量控制、调大发送接收窗口,减小mtu,基本可以把服务器带宽压榨干净,而且延迟也确实降低了很多。

比UDP更无耻

在实际测试中发现,虽然KCP能达到锐速之类的效果,但是有时候因为ISP的干扰,UDP报文可能会被丢弃。毕竟因为TCP有阻塞控制,把资源分配给TCP会更加合理一些。但是我们既然都已经这么流氓了,再流氓点又如何呢。
TCP和UDP在报文上的区别仅仅是报头有些不同,我们完全可以用原始套接字(Raw Socket)自己构造一个TCP报头。这里不得不吹一下KCP的作者,简直就是天才。KCP虽然是以 “Reliable UDP”自诩,但是实际上KCP下层完全可以接任何协议,所以接一个畸形的TCP也是完全没问题的。
在KCP的output中,我取了udp2raw_tunnel的部分代码

https://github.com/wangyu-/udp2raw-tunnel

让KCP发送的数据变成披着TCP外皮的UDP包。现在只要解决接收的部分,就可以完成客户端与服务端的通信了。这里唯一的问题就是,这种畸形的TCP包,普通的TCP服务端是接收不到的。所以我加入了PCAP

PCAP的java封装 https://github.com/kaitoy/pcap4j

用于捕获所有从远程服务器的规定端口发送的数据,再把这些数据作为KCP的input传输进去。至此一个基于Raw Socket的KCP协议就完成了。

Option(伪造IP)

这里加个题外话。其实也不是题外话,在WSocks的kcp分支里,我实现了一个伪造IP发送UDP包的功能。该功能总的来说就是让服务端用真实的IP接收客户端请求,再从另一个IP发送返回的数据。以前做过一些伪造IP的实验,但都失败了,因为在实际的网络中,各种路由器、网关会检测这个IP是否属于自己包含的范围,如果不是这个包就会被丢掉。所以在公网上想要假装自己是某台机器其实是不现实的。但是如果换一个思路,我们并不需要假装自己是谁,我们只需要让自己不是自己就行了。于是我重新做了些测试,发现在一定范围内,修改IP完全是可行的。在我一台加利福尼亚的VPS上,可供选择的IP多达65535个。通过这种IP伪装的方式,一定程度上也可以保护服务器。

总结

以上就是WSocks疯牛病版的一些原理,总的来说就是一个逐渐突破下限的过程。既然想要空手套白狼,那只有比无耻更无耻了。