1  syn flood

就是同步发送SYN数据包,这样的操作对于发送方(攻击者)来说是非常容易实现的,而对于接收方(目标)来说会需要消耗更多的资源去接收和处理数据包。除此之外,在发送完SYN数据包之后,我们不需要等待接收端返回的SYN/ACK数据包,我们只需要继续向对方发送SYN数据包并让服务器自己去处理就可以了。这样一来,当合法用户尝试连接服务器时,服务器已经有大量SYN连接需要去处理,因此将无法及时相应合法用户的请求。


2  Syn flood 原理

tcp三次握手,客户端首先通过发送SYN请求向处于监听状态的服务器发起连接,在经典的实现中服务器接受该数据包,并为该连接分配一定的资源,并发送SYN+ACK数据包。对服务器来说,接收到SYN并且回复SYN+ACK之后的状态变为SYN_RECV,此时该连接的状态称为半连接。而当客户端收到来自服务器的SYN+ACK之后,回复一个ACK给服务器,服务器收到这个ACK之后,连接状态变成ESTABLISHED,这个时候连接建立完成。在这个过程中,如果服务器一直没有收到来自客户端的ACK,那么服务器会在超时之后重传SYN+ACK。


如果恶意攻击者故意大量发送不断发送的伪造的SYN报文,那么服务器就会分配大量注定无用的资源,并且服务器保存半连接的队列是有长度限制的,如果当服务器收到大量攻击报文的时候,它就不能接收正常的连接了,换句话说,这台服务器就失去提供服务的能力了,这就是大名鼎鼎的SYN-Flood攻击的原理,它是一种典型的DoS攻击。






3  防止syn flood

(1)缩短SYN Timeout (连接等待超时)时间:减少syn ack 重试次数。

(2)syn cache & SYN Cookies:扩大缓存半连接表/无状态半连接

(3)根据源IP记录SYN连接:限制单ip连接数

(4)容忍策略(DNS方式):伪装服务器被攻击

(5)带宽控制技术(不了解)



4  Syn cookies原理

SYN cookies算法(和dpvs的不一样)

1. 设t为一个缓慢增长的时间戳(典型实现是每64s递增一次)

2. 设m为客户端发送的SYN报文中的MSS选项值

3. 设s是连接的元组信息(源ip,目的ip,源端口,目的端口)和t经过密码学运算之后得到的hash值,即s = hash(sip, dip, sport,dport, t),s的结果取低24位则初始序列号n为:

高5位为t mod 32

接下来的3位是m的编码值(实际上并不是编码值而是索引)

低24位为s

当客户端收到SYN+ACK报文之后,根据TCP标准,它会回复ACK报文,且报文中ack = n + 1,当服务器接收到ACK时,将ack的值减一就得到了它之前发送的n,服务器通过这种巧妙的方式间接保存了一部分SYN报文的信息。接下来,服务器对获得的n进行校验:

1. 将n的高五位与当前时间戳t%32进行比较,看看到达时间是否是正常的(基本上相等或者差1,太大了基本不对劲)

2. 根据t和连接元组重新计算s,看看是否与低24一致,如果不一致,说明这个报文是伪造的

3. 解码序号中隐藏的MSS信息


SYN Cookies技术由于在建立连接的过程中不需要在服务器端保存任何信息,实现了无状态的三次握手,从而有效的防御了SYN Flood攻击。但是该方法也存在一些弱点。由于cookie的计算只涉及到包头部分信息,在建立连接的过程中不在服务器端保存任何信息,所以失去了协议的许多功能,比如超时重传。此外,由于计算cookie有一定的运算量,增加了连接建立的延迟时间,因此,SYN Cookies技术不能作为高性能服务器的防御手段。通常采用动态资源分配机制,当分配了一定的资源后再采用cookie技术。还有一个问题是,当我们避免了SYN Flood攻击的同时,也提供了另一种拒绝服务攻击方式,攻击者发送大量的ACK报文,服务器忙于计算验证。



5  dpvs通过syn proxy实现syn cookies

分为以下三个阶段:

第一阶段:客户端与握手代理进行三次握手,第二步握手代理回复客户端的SYN-ACK报文携带的初始序列号(ISN1)由SYN Cookie算法生成。

第二阶段:当cookie验证通过后,握手代理充当客户端和服务端进行三次握手,并负责校正初始序列号(ISN2)和窗口大小;

第三阶段:在连接建立之后握手代理负责调整客户端和服务端之间数据传输过程中的序列号和确认号。



Syn cookies算法

相关代码:

syn_proxy_cookie_v4_init_sequence

secure_tcp_syn_cookie

check_tcp_syn_cookie

1)客服端发送syn包到syn proxy

dp_vs_pre_routing

|- dp_vs_synproxy_syn_rcv

|-syn_proxy_reuse_mbuf

         |-isn=syn_proxy_cookie_v4_init_sequence

th->ack_seq = htonl(ntohl(th->seq) + 1);

th->seq = htonl(isn);

th->window = 0;

((uint8_t *)th)[13] = 0x12;/* set syn-ack flag */

...

...

netif_xmit



2)客户端发送ack包到syn proxy

dp_vs_in

|-conn_sched

|-tcp_conn_sched

|-dp_vs_synproxy_ack_rcv

/* Do not check svc syn-proxy flag as it may be changed after syn-proxy step 1. */

|-syn_proxy_v4_cookie_check

|-dp_vs_schedule

|-dp_vs_conn_new

ack_mbuf->mbuf = mbuf;

list_add_tail(&ack_mbuf->list, &new->ack_mbuf);

new->ack_num++;

/* save ack_seq - 1 */

new->syn_proxy_seq.isn =

htonl((uint32_t) ((ntohl(th->ack_seq) - 1)));//proxy发送给client syn包的seq

/* save ack_seq */

new->fnat_seq.fdata_seq=ntohl(th->ack_seq);//client发送给proxy ack seq

cp->fnat_seq.isn = 0;


3)syn proxy发送syn包到rs

syn_proxy_send_rs_syn

syn_th->seq = htonl(ntohl(th->seq) - 1); //=client syn seq

syn_th->ack_seq = 0;

syn_th->window = htons(5000);



static inline void tcp_in_init_seq(struct dp_vs_conn *conn,

struct rte_mbuf *mbuf, struct tcphdr *th)

{

struct dp_vs_seq *fseq = &conn->fnat_seq;

uint32_t seq = ntohl(th->seq); 


if (fseq->isn != 0 && fseq->delta == fseq->isn - seq)

return; /* retransmit */

if (fseq->isn)

return;


fseq->isn = tcp_secure_sequence_number(conn->laddr.in.s_addr,

conn->daddr.in.s_addr, conn->lport, conn->dport);

//proxy发送给rs syn seq 初始值


fseq->delta = fseq->isn - seq; //两个syn包之间的差值

return;

}



static inline void tcp_in_adjust_seq(struct dp_vs_conn *conn, struct tcphdr *th)

{

th->seq = htonl(ntohl(th->seq) + conn->fnat_seq.delta);

/* recalc checksum later */

/* adjust ack_seq for synproxy,including tcp hdr and sack opt */

dp_vs_synproxy_dnat_handler(th, &conn->syn_proxy_seq);

return;

}


4)rs发送synack到syn proxy

dp_vs_synproxy_synack_rcv

cp->syn_proxy_seq.delta = ntohl(cp->syn_proxy_seq.isn) - ntohl(th->seq);


5)syn proxy发送ack到client

   ack包为保存的client到syn proxy的ack包,调整序列号后发送。

   同时发送ack到客户端,调整window:

syn_proxy_send_window_update


6)调整序列号

tcp_in_adjust_seq

th->seq = htonl(ntohl(th->seq) + conn->fnat_seq.delta);

tcph->ack_seq = htonl((uint32_t)(old_ack_seq - sp_seq->delta));


tcp_out_adjust_seq

tcph->ack_seq = htonl(ntohl(tcph->ack_seq)-conn->fnat_seq.delta);

tcph->seq = htonl((uint32_t)(old_seq+ cp->syn_proxy_seq.delta));


6  开启fullnat模式syn proxy

root@test:/usr/home/fengming5# ipvsadm -E -t 192.168.100.71:80 -j enable

root@test:/usr/home/fengming5# ipvsadm -ln

IP Virtual Server version 0.0.0 (size=0)

Prot LocalAddress:Port Scheduler Flags

  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn

TCP  192.168.100.71:80 wrr synproxy

  -> 192.168.100.72:80            FullNat 0      0          0


7  Syn flood 攻击工具

https://github.com/JuxhinDB/synner.git