这次的话题是个大家耳朵都听起茧的话题:TCP的三次握手。不过呢,老王这次聊的这个话题,还是跟原来教科书上可能有些不一样,因为当我第一次接触到的时候,都觉得有点意思。所以就分享给大家~
TCP是一个可靠的传输协议(相对UDP而言),他的可靠是建立在几个手段之上的,比如:三次握手、滑动窗口、超时重传等等。其中三次握手是最重要的手段之一。所谓的三次握手,就是在建立TCP连接的时候,客户端和服务器需要做几次通讯,确认相互之间的信息。具体的流程如下图:
当客户端发起三次握手的时候:
1、先发起一个SYN=1的包,并且带一个序列号(Seq);
2、服务器收到这个包以后,将这个数据放入到一个队列中,这个队列叫syn_table。并且发送一个返回包,作为响应,这个返回包有自己的序列号(Seq),以及一个Ack,Ack的值就是客户端发来的Seq值加一;
3、客户端收到返回信息以后,将服务器的Seq加一作为Ack又发给服务器;
4、服务器收到这第三个包,验证没问题以后,就将这个连接放入到request_sock_queue,三次握手完成。
对于写程序的人而言,我们客户端在调用connect的时候,发起三次握手操作。当三次握手完成后,服务器从request_sock_queue中获取对应的连接,返回给accept对应的fd。
以上就是三次握手的原理以及程序对应的调用。
那问题就来了,如果,客户端是一个恶意的用户,他故意先发起一个第一个握手包,服务器收到以后放入等待队列,并返回确认。但是,客户端不再发送第三个确认包,会怎么样?服务器会进行多次重试发送(一般是五次,linux系统有对应的配置tcp_synack_retries)。
这带来的问题就是,服务器需要额外的开销,以及等待队列被占用。更严重的,就是如果有大量的客户端发起这种连接,我们的服务器的等待队列会很快就被占满(队列长队配置在:tcp_max_syn_backlog中),导致后来的请求不再被服务器接收,从而阻止服务器服务其他用户。这就是著名的Syn-Flood攻击。
那我们有办法防止这个Syn-Flood攻击吗?答案当然是有的!
linux似乎从2.2开始,就有tcp_syncookies这样一个配置,如果开启了这个配置,就会产生一个效果:
当syn等待队列满的时候,新来的请求就不再放入队列中,而是采用一个算法来解决。具体是什么算法呢,跟着老王往下看。
我们不是讲了三次握手的第二次是由服务器发送给客户端嘛,linux很巧妙的在第二次握手的Seq上做了手脚。他将用户请求的参数(包括请求的地址、端口等),加上服务器的一个序列号等做了一次运算,得到了一个32位的无符号整数,并将这个整数作为Seq值发送给客户端。这个时候有两种情况发生:
1、客户端是攻击者:客户端不再做出第三次握手的响应。这个时候对服务器而言,并没有任何损失,因为他不占用服务器任何资源;
2、客户端是正常的用户:客户端会将这个Seq的值加一作为Ack返回给服务器。服务器拿着这个Ack值减一,进行刚才算法的逆运算进行校验,看是否得到和发出去的数据一致。如果能得到,则是一个有效的响应,否则就不是。
通过这样的一个算法,就能比较有效的区分是否是正常的请求,同时能减少服务器的压力和被攻击的可能。
以下是 tcp_syncookies 的说明
tcp_syncookies (Boolean; since Linux 2.2)
Enable TCP syncookies. The kernel must be compiled with CONFIG_SYN_COOKIES. Send out syncookies when the syn backlog queue of a socket overflows. The syncookies feature attempts to protect a socket from a SYN flood attack. This should be used as a last resort, if at all. This is a violation of the TCP protocol, and conflicts with other areas of TCP such as TCP extensions. It can cause problems for clients and relays. It is not recommended as a tuning mechanism for heavily loaded servers to help with overloaded or misconfigured conditions. For recommended alternatives see tcp_max_syn_backlog, tcp_synack_retries, and tcp_abort_on_overflow.
具体的算法如下
u32 count = tcp_cookie_time();
return (cookie_hash(saddr, daddr, sport, dport, 0, 0) +
sseq + (count << COOKIEBITS) +
((cookie_hash(saddr, daddr, sport, dport, count, 1) + data)
& COOKIEMASK));
这里的count每分钟加一,如果客户端返回的count超过2,就是超时了。
好了,有了这样的算法是否也是就万无一失呢?
也不是!因为客户端可以发起第三个握手包进行后面的攻击,比如DoS、DDoS、DRDoS等等。
这些攻击的防御,我们会陆续来讲解。