Wooyme教你如何秽土转生

前言

租廉价VPS的时候经常会遇到一个问题,租到的这个IP已经被封了或者这个机房的线路烂的连魔改版bbr都救不了。这个时候我们就基本上只能任命,或者再roll一次,或者就放弃这个家了。
那么,就真的没有办法了吗?

灵感来源:DDOS

没错,就是DDOS,毕竟我也是做安全出身的。DDOS里有一种很独特的攻击方式叫做放大型DDOS,有时候也叫反射型DDOS。这种攻击的方式首先要寻找那些开放udp端口,并且你给它发包它能返回你一个更大的包的服务器,找到一批这样的服务器之后,就可以伪造成被攻击对象的ip像服务器发包,然后服务器就会把这个更大的返回包发向被攻击的机器。以此来实现放大

那放大型DDOS和我们想要的“秽土转生术”有什么关系呢。你品、你细品。想要让一台机器复活,我们就要为它找一个中间人。如果能够让墙内的机器通过中间人与我们的服务器通信,那不就等于复活了我们的服务器。而放大型DDOS多多少少有些这样的特质,所以我寻找了很多关于放大型DDOS的资料,终于让我找到了一个最接近的方案。

Memcached

https://paper.seebug.org/898/#37memcached

一切源于seebug的这篇文章。

Memcached是一个自由开源的,高性能,分布式内存对象缓存系统。Memcached是以LiveJournal旗下Danga Interactive公司的Brad Fitzpatric为首开发的一款软件。现在已成为mixi、hatena、Facebook、Vox、LiveJournal等众多服务中提高Web应用扩展性的重要因素。Memcached是一种基于内存的key-value存储,用来存储小块的任意数据(字符串、对象)。这些数据可以是数据库调用、API调用或者是页面渲染的结果。Memcached简洁而强大。它的简洁设计便于快速开发,减轻开发难度,解决了大数据量缓存的很多问题。它的API兼容大部分流行的开发语言。本质上,它是一个简洁的key-value存储系统。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。

最关键的是

Memcached Server在默认情况下同时开启了TCP/UDP 11211端口,并且无需认证既可使用Memcached的储存服务

无需认证既可使用,这太离谱了。

然后我又在ZoomEye上搜索了一下,全球有超过50万台机器开放着11211端口。这不就直接无限剑制了吗。

当然我后来又查到在某个更新里Memcached把默认的udp端口给取消了,也就是说现在的Memcached服务端大部分是只开了tcp端口的,毕竟再这样下去Memcached都快成为反射型DDOS的老大了。但是这个并不是问题,毕竟我们的目的不是DDOS,这个无需认证既可使用真的太香了。

通信

所以现在的问题只有一个,客户端和服务端如何通过Memcached中间人进行通信。这种通信方式明显会与常规的通信方式有很大的差别。这就像两个间谍,确定了要在某个地点交换情报,但是这两个人又不能见面,所以只能把情报放在一个角落里,等对方过段时间来拿。

下面看一下如何实现这样的一种通信。首先既然Memcached存储的是键值对,那么如何设计这个键就是我们要解决的第一个问题。

id + flagPair.first + Integer.toHexString(offsetRead)

上面这个就是我设计的键。由于我最终想要达到类似长连接的效果,所以引入了id来作为每个连接的标识符,然后flagPair是一个包含"s""c"Pair,以此来区分客户端和服务端,最后的offsetRead比较复杂,后面会详细说明。

那么有了这个key之后,服务端与客户端就可以通过读取和写入Memcached来通信了。

offset

idflagPair都是用来确定这个连接的,而offsetRead才是读取写入的关键。首先可以想到,有offsetRead那肯定也有offsetWrite。我规定每次写入都会使offsetWrite+1,同样的,每次成功的读取也会使offsetRead+1。那为什么要有offset呢?理论上说如果没有offset,连接双方永远在同一个键下读取写入也是完全可以的,但这种理论的前提是网络延迟要低于我们轮询的时间间隔,并且写入速度要小于读取速度。如果这两个条件不成立的话,就会出现后面的值覆盖先前的值,继而导致丢包。

所以为了在真实的网络情况下获得较好的性能,我们要在Memcached中建立一个缓冲区,而这个offset就成了表示缓冲区中各个数据位置的标志。

快速读取

在规定了offset之后,如何高效的读取依然是个问题。因为读取的时候是无法知道在这段时间里缓冲区中有多少条数据的。如果每一轮只读一个,那整体的带宽必然会受到很大限制。所以我加入了一种类似窗口的机制。

在初始状态下,每50ms发送4次GET请求.这里当然还有另外一个问题,由于延迟的存在,发送请求之后并不能立马得到返回,即使是使用香港的Memcached服务器做中继,延迟也普遍在100ms以上。所以如果我们等待请求返回后再发送下一轮请求的话,两次请求的间隔就可能是150ms了。

因此我采用了一种乐观的策略,即认为Memcached中永远有数据。即使前面的请求还没有返回,我们也假设前面的请求能成功获取数据,并且使offsetRead增加,所以每到50ms我们就会发送一轮新的请求。等到前面的请求返回了,如果返回为空,则重置offsetRead,如果有返回则不改动offsetRead,以此来保证降低延迟。

但是接着我们就会发现,如果每个数据最大是1kb,即使每个数据都达到最大值,且每个请求都能成功返回,那我们的带宽也只有1000/50*4=80kb。所以我们需要动态的修改4这个数字。在这里我引入了这样一种逻辑,如果每次发送的4个请求全部有数据返回,则认为后面还有更多的数据要返回。所以每当4个请求全部成功返回的时候,我们就加倍,使下一轮一次性发送8个请求,后续同理。直到达到我们设定的上限。这样假如上限是64,那峰值带宽就可以达到1000/50*64=1280kb的速度。而当我们的请求有半数以上返回空值的时候就直接重置这个窗口。

在加载视频或是下载的时候,通过这种逻辑往往可以得到比较好的效果。

总结

通过Memcached中继可以隐藏真正的服务器,我也在自己的Wsocks中加入了这样功能。不过在实际使用中也发现一些问题。第一、如果延迟过高,那么上述的逻辑中窗口爬升的速度会非常慢,整体网速也就很烂,所以比较推荐使用日本、香港这样的Memcached服务器。第二、电信网的表现非常差,可能因为Memcached与DDOS有关,导致来自11211端口的流量被电信屏蔽,这一点暂时无法解决。