一、为什么要使用syn-proxy
linux内核原生的ip_vs模块主要功能是实现负载均衡,但不能对类似DDOS这种flood类型的***包进行防护,而这种***包会被ip_vs模块转发至后端rs上,一般情况下rs缺少对于这种四层***的防御,或者说防护效率并不高。同时,在ip_vs模块向后端转发时,也会消耗lvs机器的CPU等硬件资源,对于lvs的转发性能也会造成一定影响。
为了解决上述问题,淘宝开源的FULLNAT模式版本的lvs在原生lvs上增加了一层flood***防护功能:syn-proxy。
二、什么是syn-cookie
SYN Cookie是对TCP服务器端的三次握手做一些修改,专门用来防范SYN Flood***的一种手段。它的原理是,在TCP服务器接收到TCP SYN包并返回TCP SYN + ACK包时,不分配一个专门的数据区,而是根据这个SYN包计算出一个cookie值。这个cookie作为将要返回的SYN ACK包的初始序列号。当客户端返回一个ACK包时,根据包头信息计算cookie,与返回的确认序列号(初始序列号 + 1)进行对比,如果相同,则是一个正常连接,然后,分配资源,建立连接。实现的关键在于cookie的计算,cookie的计算应该包含本次连接的状态信息,使***者不能伪造。
ip_vs在syn-proxy中也借鉴syn-cookie的机制实现了自己的syn-cookie,下图展示了syn-proxy的大体流程(红色标记为syn-cookie的生成和校验阶段):
三、实现思路
- 利用NF_INET_PRE_ROUTING处HOOK点处理函数ip_vs_pre_routing来处理client发来的syn报文(此时不创建session,不为该连接分配资源
- 在ip_vs_synproxy_syn_rcv函数中计算syn-cookie值,同时该值也被用做回复给client syn_ack报文的init seq
- 在tcp_conn_schedule函数中,对ack报文的cookie校验(即ack报文的ack_seq-1进行校验),校验通过则创建session,为该连接分配资源,并把ack报文保存在Session结构体中且向RS发送syn报文
四、关键技术点实现原理
syn-cookie的生成
计算syn-cookie(即发送给client的init_seq)
ip_vs_synproxy_cookie_v4_init_sequence函数
/*
* Generate a syncookie for ip_vs module.
* Besides mss, we store additional tcp options in cookie "data".
*
* Cookie "data" format:
* |[21][20][19-16][15-0]|
* [21] SACKOK
* [20] TimeStampOK
* [19-16] snd_wscale
* [15-0] MSSIND
*/
__u32 ip_vs_synproxy_cookie_v4_init_sequence(struct sk_buff *skb,
struct ip_vs_synproxy_opt *opts)
{
const struct iphdr *iph = ip_hdr(skb);
const struct tcphdr *th = tcp_hdr(skb);
int mssind;
const __u16 mss = opts->mss_clamp;
__u32 data = 0;
/* XXX sort msstab[] by probability? Binary search? */
/* 根据syn包的mss确定mssind */
for (mssind = 0; mss > msstab[mssind + 1]; mssind++)
;
opts->mss_clamp = msstab[mssind] + 1;
/* 由于在syn-proxy中,收到syn时并不立即分配资源,因此需要存储tcp option
* 此处的data后续被用做cookie hash参数
*/
data = mssind & IP_VS_SYNPROXY_MSS_MASK;
data |= opts->sack_ok << IP_VS_SYNPROXY_SACKOK_BIT; /* 21 */
data |= opts->tstamp_ok << IP_VS_SYNPROXY_TSOK_BIT; /* 20 */
data |= ((opts->snd_wscale & 0x0f) << IP_VS_SYNPROXY_SND_WSCALE_BITS); /* 16 */
/* 此时data22bit分别为
* |[21] [20] [19-16] [15-0]|
* SACKOK TimeStampOK snd_wscale MSSIND
*/
return secure_tcp_syn_cookie(iph->saddr, iph->daddr,
th->source, th->dest, ntohl(th->seq),
jiffies / (HZ * 60), data); /* jiffies为系统开机时间 */
}
syn-cookie生成函数
static __u32 secure_tcp_syn_cookie(__be32 saddr, __be32 daddr, __be16 sport,
__be16 dport, __u32 sseq, __u32 count,
__u32 data)
{
/*
* Compute the secure sequence number.
* The output should be:
* HASH(sec1,saddr,sport,daddr,dport,sec1) + sseq + (count * 2^24)
* + (HASH(sec2,saddr,sport,daddr,dport,count,sec2) % 2^24).
* Where sseq is their sequence number and count increases every
* minute by 1.
* As an extra hack, we add a small "data" value that encodes the
* MSS into the second hash value.
*/
/* 该返回值即为发给client包的init seq,其中包含了hash_value1 + seq + count<<24 + (hash_value2 + data(22bit)) & COOKIEMASK(24bit)
* hash_value1:根据四元组及count=0,c=0的sha1 hash值 (32bit)
* sseq:client的syn包seq (32bit)
* count:jiffies / (HZ * 60)即系统开机时间 (8bit)
* hash_value2:根据四元组及count=count,c=1的sha1 hash值 (32bit)
* data:根据tcp option拼接成的数据 (22bit) 【注】:tcp中的syn-cookie实现此处的data为mssind
*/
return (cookie_hash(saddr, daddr, sport, dport, 0, 0) +
sseq + (count << COOKIEBITS) +
((cookie_hash(saddr, daddr, sport, dport, count, 1) + data)
& COOKIEMASK));
}
syn-cookie的校验
static __u32 check_tcp_syn_cookie(__u32 cookie, __be32 saddr, __be32 daddr,
__be16 sport, __be16 dport, __u32 sseq,
__u32 count, __u32 maxdiff)
{
/* 此处传入的cookie为ack包的ack_seq-1,即lvs发送给client syn的seq,也即最原始的cookie值 */
__u32 diff;
/* Strip away the layers from the cookie */
/* 由上面生成cookie的代码我们知道,cookie包含以下几个部分:
* hash_value1(四元组,0,0) + seq(syn seq) + count(syn sys_time)<<24 + (hash_value2 + data(22bit)) & COOKIEMASK(24bit)
* 此时hash_value1 = cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq; -----> hashkey均为 (四元组,0,0)且对于同一tcp流来说四元组相同
* sseq = seq(syn seq) -----> 传入的sseq为ack包的seq - 1,即syn包的seq
* 因此经过cookie -= cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq后cookie的值将变为:
* cookie = count(syn sys_time)<<24 + (hash_value2 + data(22bit)) & COOKIEMASK(32bit)
*/
cookie -= cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq;
/* Cookie is now reduced to (count * 2^24) ^ (hash % 2^24) */
/*
* 分析此时的cookie组成:cookie = count(syn sys_time)<<24 + (hash_value2 + data(22bit)) & COOKIEMASK(24bit)
* 二进制的cookie组成可以表示成这样:(假设count=4)
* count(syn sys_time)<<24 + (hash_value2 + data(22bit)) & COOKIEMASK(24bit)
* 31 - - - - - - - 23 - - - - - - - 15 - - - - - - - 7 - - - - - - 0
* count << 24 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
* hash_value2 1 1 0 0 1 0 0 0 0 1 0 0 0 1 1 1 0 0 0 0 1 0 0 0 1 0 0 1 0 0 0 0
* + +
* data 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 0 0 0 1 0 0 1 0 0 1 0 0 0 0 0 0
* COOKIEMASK 0 0 0 0 0 0 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
* 通过上面的表示我们可以看出在cookie中的高8位存储着count值
* 我们记该函数中的count为conut_syn,记secure_tcp_syn_cookie函数(该函数用于生成syn-cookie)中的count为count_ack
* 很显然,count_syn - count_ack所得的结果即为client syn包与ack包到达lvs机器的时间之差diff,只要这个diff小于我们所期望的值,我们就认为cookie校验成功
* 因此diff = count_ack - (cookie >> COOKIEBITS),其中(cookie >> COOKIEBITS) = count_syn
*/
diff = (count - (cookie >> COOKIEBITS)) & ((__u32) - 1 >> COOKIEBITS); /* (__u32) - 1 >> COOKIEBITS 为8bit全1 */
/* diff的单位为分钟,默认的maxdiff为4 */
if (diff >= maxdiff)
return (__u32)-1;
/* 若syn-cookie检验成功,则返回值只剩下了22bit的data数据,data数据中包含syn tcp options,同时为该连接分配系统资源 */
return (cookie -
cookie_hash(saddr, daddr, sport, dport, count - diff, 1))
& COOKIEMASK; /* Leaving the data behind */
}
五、总结
本文介绍了lvs syn-proxy中的syn-cookie的生成和校验过程,但syn-proxy中还涉及了rs client间的seq转换,rs回复rst报文的seq修正等等复杂的逻辑,需要时间去好好研究,同时也希望本篇文章能够让大家了解syn-cookie及其原理。
六、参考文档
http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/
转载于:https://blog.51cto.com/10843840/2359426