3.6 SYN Cookie

在三次握手过程中,server端的TCP收到SYN请求后会建立一个request_sock保存在syn_table中。如果有恶意攻击者大量发送IP地址或端口不同的SYN包,则server端TCP的syn_table很快会被占满,而普通用户对server的正常访问会因为syn_table已满而被拒绝。这就是SYN Flood攻击。SYN Cookie技术就是为了应对SYN Flood攻击而产生的,它的原理是,在TCP服务器收到SYN包并返回SYN|ACK包时,不分配一个专门的数据区,而是根据这个SYN包计算出一个cookie值,在发送SYN|ACK时将这个cookie作为其序列号。在收到ACK包时,TCP服务器根据确认号得到之前的cookie,再根据这个cookie值检查这个ACK包的合法性。如果合法,再分配sock保存未来的TCP连接的信息。
    如果要Linux内核支持SYN Cookie功能,必须开启CONFIG_SYN_COOKIES编译选项。下面分析一下TCP对SYN Cookie功能的实现。

    TCP服务器在接收到SYN时,会调用tcp_v4_conn_request函数:

 1465 int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
1466 {
1467     struct tcp_options_received tmp_opt;
1468     struct request_sock *req;
1469     struct inet_request_sock *ireq;
1470     struct tcp_sock *tp = tcp_sk(sk);
1471     struct dst_entry *dst = NULL;  
1472     __be32 saddr = ip_hdr(skb)->saddr;
1473     __be32 daddr = ip_hdr(skb)->daddr;
1474     __u32 isn = TCP_SKB_CB(skb)->when;
1475     bool want_cookie = false;
1476     struct flowi4 fl4;   
1477     struct tcp_fastopen_cookie foc = { .len = -1 };
1478     struct tcp_fastopen_cookie valid_foc = { .len = -1 };
1479     struct sk_buff *skb_synack;    
1480     int do_fastopen;     
...
1486     /* TW buckets are converted to open requests without
1487      * limitations, they conserve resources and peer is
1488      * evidently real one.
1489      */
1490     if (inet_csk_reqsk_queue_is_full(sk) && !isn) {//如果存储request sock的accept队列已满且SYN包没有命中TIME_WAIT socekt
1491         want_cookie = tcp_syn_flood_action(sk, skb, "TCP");//如果用户使用sysctl_tcp_syncookies开启了syn cookie功能,则want_cookie为1
1492         if (!want_cookie)//如果使用syn cookie机制,不需要保存request sock,所以不需要受到accept queue的限制
1493             goto drop;   
1494     }
...
1506     req = inet_reqsk_alloc(&tcp_request_sock_ops);//申请一个request sock,但不会保存到accept queue中
1507     if (!req)
1508         goto drop;
...
1517     tcp_parse_options(skb, &tmp_opt, 0, want_cookie ? NULL : &foc);//如果使用syn cookie,则不能使用fast open功能,因为无处存储相关信息
1518                 
1519     if (want_cookie && !tmp_opt.saw_tstamp)//使用syn cookie时如果对端没有开启时间戳选项
1520         tcp_clear_options(&tmp_opt);  //则本端不支持SACK和窗口扩大选项,因为无处存储相关信息
...
1523     tcp_openreq_init(req, &tmp_opt, skb);
...
1537     if (want_cookie) {//使用syn cookie的话,
1538         isn = cookie_v4_init_sequence(sk, skb, &req->mss);//生成syn cookie并以之作为SYN|ACK的起始序列号
1539         req->cookie_ts = tmp_opt.tstamp_ok;
1540     } else if (!isn) {
...
1578     tcp_rsk(req)->snt_isn = isn;   
...
1598     skb_synack = tcp_make_synack(sk, dst, req,
1599         fastopen_cookie_present(&valid_foc) ? &valid_foc : NULL); 
...
1607     if (likely(!do_fastopen)) {
1608         int err;
1609         err = ip_build_and_send_pkt(skb_synack, sk, ireq->loc_addr,
1610              ireq->rmt_addr, ireq->opt);
1611         err = net_xmit_eval(err);
1612         if (err || want_cookie)//发送完SYN|ACK后,丢弃此request sock
1613             goto drop_and_free;
...
1629 drop_and_free:
1630     reqsk_free(req);
  1490:只有accpet队列满时才会启用syn cookie机制

  1538:生成cookie作为初始序列号isn

  1578:记录初始序列号,并在1598行的tcp_make_synack中被用作起始序列号:

2654 struct sk_buff *tcp_make_synack(struct sock *sk, struct dst_entry *dst,
2655                 struct request_sock *req,
2656                 struct tcp_fastopen_cookie *foc)
2657 {
2658     struct tcp_out_options opts;
2659     struct inet_request_sock *ireq = inet_rsk(req);
2660     struct tcp_sock *tp = tcp_sk(sk);
2661     struct tcphdr *th;
...
2703     memset(&opts, 0, sizeof(opts));
2704 #ifdef CONFIG_SYN_COOKIES
2705     if (unlikely(req->cookie_ts))
2706         TCP_SKB_CB(skb)->when = cookie_init_timestamp(req);  //生成时间戳
2707     else
2708 #endif
2709     TCP_SKB_CB(skb)->when = tcp_time_stamp;
2710     tcp_header_size = tcp_synack_options(sk, req, mss, skb, &opts, &md5,
2711                          foc) + sizeof(*th);
...
2726     tcp_init_nondata_skb(skb, tcp_rsk(req)->snt_isn,
2727                  TCPHDR_SYN | TCPHDR_ACK);
2728 
2729     th->seq = htonl(TCP_SKB_CB(skb)->seq);
2730     /* XXX data is queued and acked as is. No buffer/window check */
2731     th->ack_seq = htonl(tcp_rsk(req)->rcv_nxt);
2732 
...
  2706:cookie_init_timestamp函数会将窗口扩大选项和选择确认选项的信息编码成为时间戳的值:

 67 __u32 cookie_init_timestamp(struct request_sock *req)
 68 {
 69     struct inet_request_sock *ireq; 
 70     u32 ts, ts_now = tcp_time_stamp;
 71     u32 options = 0;
 72 
 73     ireq = inet_rsk(req);
 74 
 75     options = ireq->wscale_ok ? ireq->snd_wscale : 0xf; //窗口扩大选项信息
 76     options |= ireq->sack_ok << 4;  //选择确认选项信息
 77     options |= ireq->ecn_ok << 5;
 78 
 79     ts = ts_now & ~TSMASK;
 80     ts |= options;
 81     if (ts > ts_now) {
 82         ts >>= TSBITS;
 83         ts--;
 84         ts <<= TSBITS;
 85         ts |= options; 
 86     }
 87     return ts;
 88 }    
  赋给TCP_SKB_CB(skb)->when后再写入时间戳中:

560 static unsigned int tcp_synack_options(struct sock *sk,
 561                    struct request_sock *req,
 562                    unsigned int mss, struct sk_buff *skb,
 563                    struct tcp_out_options *opts,
 564                    struct tcp_md5sig_key **md5,
 565                    struct tcp_fastopen_cookie *foc)
 566 {
 567     struct inet_request_sock *ireq = inet_rsk(req);
...
 596     if (likely(ireq->tstamp_ok)) {
 597         opts->options |= OPTION_TS;
 598         opts->tsval = TCP_SKB_CB(skb)->when;
 599         opts->tsecr = req->ts_recent;
 600         remaining -= TCPOLEN_TSTAMP_ALIGNED;
 601     }
...
  tcp_init_nondata_skb函数记录将MSS选项编码的cookie值:

 356 static void tcp_init_nondata_skb(struct sk_buff *skb, u32 seq, u8 flags)
 357 {
 358     skb->ip_summed = CHECKSUM_PARTIAL;
 359     skb->csum = 0;
 360 
 361     TCP_SKB_CB(skb)->tcp_flags = flags;
 362     TCP_SKB_CB(skb)->sacked = 0;
 363 
 364     skb_shinfo(skb)->gso_segs = 1;
 365     skb_shinfo(skb)->gso_size = 0;
 366     skb_shinfo(skb)->gso_type = 0;
 367 
 368     TCP_SKB_CB(skb)->seq = seq;  //记录序列号
 369     if (flags & (TCPHDR_SYN | TCPHDR_FIN))  //SYN或FIN占用一个序列号
 370         seq++;
 371     TCP_SKB_CB(skb)->end_seq = seq;
 372 }
  回到tcp_v4_conn_request函数,来看看生成cookie的 cookie_v4_init_sequence函数:

163 __u32 cookie_v4_init_sequence(struct sock *sk, struct sk_buff *skb, __u16 *mssp)
164 {
165     const struct iphdr *iph = ip_hdr(skb);
166     const struct tcphdr *th = tcp_hdr(skb);
167     int mssind;
168     const __u16 mss = *mssp;
169
170     tcp_synq_overflow(sk);//记录SYN accept queue最后一次溢出的时间戳
171
172     for (mssind = ARRAY_SIZE(msstab) - 1; mssind ; mssind--)
173         if (mss >= msstab[mssind])
174             break;//找到一个最小的“最大MSS”值
175     *mssp = msstab[mssind];
176
177     NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_SYNCOOKIESSENT);
178
179     return secure_tcp_syn_cookie(iph->saddr, iph->daddr,
180                      th->source, th->dest, ntohl(th->seq),
181                      jiffies / (HZ * 60), mssind);//生成cookie
182 }        
   secure_tcp_syn_cookie函数:

91 static __u32 secure_tcp_syn_cookie(__be32 saddr, __be32 daddr, __be16 sport,
 92                    __be16 dport, __u32 sseq, __u32 count,
 93                    __u32 data)
 94 {
 95     /*
 96      * Compute the secure sequence number.
 97      * The output should be:
 98      *   HASH(sec1,saddr,sport,daddr,dport,sec1) + sseq + (count * 2^24)
 99      *      + (HASH(sec2,saddr,sport,daddr,dport,count,sec2) % 2^24).
100      * Where sseq is their sequence number and count increases every
101      * minute by 1.
102      * As an extra hack, we add a small "data" value that encodes the
103      * MSS into the second hash value.
104      */
105
106     return (cookie_hash(saddr, daddr, sport, dport, 0, 0) +
107         sseq + (count << COOKIEBITS) +
108         ((cookie_hash(saddr, daddr, sport, dport, count, 1) + data)
109          & COOKIEMASK));//将data中存储的最大MSS的信息编码进cookie中
110 }
  可见 cookie_v4_init_sequence函数会把对端的最大报文段选项MSS的值编码进cookie的值中.

  客户端收到SYN|ACK后发送ACK给服务器,服务器收到后会调用tcp_v4_hnd_req函数进行处理:

1739 static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
1740 {
1741     struct tcphdr *th = tcp_hdr(skb);
1742     const struct iphdr *iph = ip_hdr(skb);
1743     struct sock *nsk;
1744     struct request_sock **prev;    
1745     /* Find possible connection requests. */
1746     struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,
1747                                iph->saddr, iph->daddr);
1748     if (req)//由于没有建立request sock,故req为NULL
1749         return tcp_check_req(sk, skb, req, prev, false);      
...
1763 #ifdef CONFIG_SYN_COOKIES
1764     if (!th->syn)
1765         sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt));//如果cookie合法,根据cookie生成socket
1766 #endif
1767     return sk;
1768 }
   cookie_v4_check函数
266 struct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb,
267                  struct ip_options *opt)
268 {        
269     struct tcp_options_received tcp_opt;
270     struct inet_request_sock *ireq;
271     struct tcp_request_sock *treq;
...
286     if (tcp_synq_no_recent_overflow(sk) ||//listening socket的SYN accept queue最近没有溢出
287         (mss = cookie_check(skb, cookie)) == 0) {//cookie不合法,没得到了收到SYN时存储的对端的MSS值
288         NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_SYNCOOKIESFAILED);
289         goto out;
290     }
...
295     memset(&tcp_opt, 0, sizeof(tcp_opt));
296     tcp_parse_options(skb, &tcp_opt, 0, NULL);
297
298     if (!cookie_check_timestamp(&tcp_opt, sock_net(sk), &ecn_ok))
299         goto out;
300
301     ret = NULL;
302     req = inet_reqsk_alloc(&tcp_request_sock_ops); /* for safety */
303     if (!req)
304         goto out;
305
306     ireq = inet_rsk(req);
307     treq = tcp_rsk(req);
308     treq->rcv_isn       = ntohl(th->seq) - 1;
309     treq->snt_isn       = cookie;
310     req->mss        = mss;  //记录解密后得到的MSS
311     ireq->loc_port      = th->dest;
312     ireq->rmt_port      = th->source;
313     ireq->loc_addr      = ip_hdr(skb)->daddr;
314     ireq->rmt_addr      = ip_hdr(skb)->saddr;
315     ireq->ecn_ok        = ecn_ok;
316     ireq->snd_wscale    = tcp_opt.snd_wscale;
317     ireq->sack_ok       = tcp_opt.sack_ok;
318     ireq->wscale_ok     = tcp_opt.wscale_ok;
319     ireq->tstamp_ok     = tcp_opt.saw_tstamp;
320     req->ts_recent      = tcp_opt.saw_tstamp ? tcp_opt.rcv_tsval : 0;
321     treq->snt_synack    = tcp_opt.saw_tstamp ? tcp_opt.rcv_tsecr : 0;
322     treq->listener      = NULL;
323
324     /* We throwed the options of the initial SYN away, so we hope
325      * the ACK carries the same options again (see RFC1122 4.2.3.8)
326      */
327     if (opt && opt->optlen) {
328         int opt_size = sizeof(struct ip_options_rcu) + opt->optlen;
329
330         ireq->opt = kmalloc(opt_size, GFP_ATOMIC);
331         if (ireq->opt != NULL && ip_options_echo(&ireq->opt->opt, skb)) {
332             kfree(ireq->opt);
333             ireq->opt = NULL;
334         }
335     }
...
362
363     /* Try to redo what tcp_v4_send_synack did. */
364     req->window_clamp = tp->window_clamp ? :dst_metric(&rt->dst, RTAX_WINDOW);
365
366     tcp_select_initial_window(tcp_full_space(sk), req->mss,
367                   &req->rcv_wnd, &req->window_clamp,
368                   ireq->wscale_ok, &rcv_wscale,
369                   dst_metric(&rt->dst, RTAX_INITRWND));
370
371     ireq->rcv_wscale  = rcv_wscale;
372
373     ret = get_cookie_sock(sk, skb, req, &rt->dst);//生成socket
374     /* ip_queue_xmit() depends on our flow being setup
375      * Normal sockets get it right from inet_csk_route_child_sock()
376      */
377     if (ret)
378         inet_sk(ret)->cork.fl.u.ip4 = fl4;
379 out:    return ret;
380 }
  cookie解码函数 cookie_check会得到对端的MSS:

195 static inline int cookie_check(struct sk_buff *skb, __u32 cookie)
196 {
197     const struct iphdr *iph = ip_hdr(skb);
198     const struct tcphdr *th = tcp_hdr(skb);
199     __u32 seq = ntohl(th->seq) - 1;
200     __u32 mssind = check_tcp_syn_cookie(cookie, iph->saddr, iph->daddr,
201                         th->source, th->dest, seq,
202                         jiffies / (HZ * 60),           
203                         COUNTER_TRIES);    //解码cookie,找到生成cookie时隐藏的msstab数组下标
204
205     return mssind < ARRAY_SIZE(msstab) ? msstab[mssind] : 0;//如果cookie非法,则返回0,否则返回最大MSS的值
206 }
  check_tcp_syn_cookie
121 static __u32 check_tcp_syn_cookie(__u32 cookie, __be32 saddr, __be32 daddr,
122                   __be16 sport, __be16 dport, __u32 sseq,
123                   __u32 count, __u32 maxdiff)
124 {   
125     __u32 diff;
126     
127     /* Strip away the layers from the cookie */
128     cookie -= cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq;
129                         
130     /* Cookie is now reduced to (count * 2^24) ^ (hash % 2^24) */
131     diff = (count - (cookie >> COOKIEBITS)) & ((__u32) - 1 >> COOKIEBITS);//计算从发出cookie到收到此cookie经历了几分钟
132     if (diff >= maxdiff)//如果超过了maxdiff分钟,即为非法cookie
133         return (__u32)-1;
134
135     return (cookie -   
136         cookie_hash(saddr, daddr, sport, dport, count - diff, 1))
137         & COOKIEMASK;   /* Leaving the data behind */    //找到生成cookie时隐藏的信息
138 }   
  cookie_check_timestamp函数会解码得到SACK和窗口扩大选项的信息:
235 bool cookie_check_timestamp(struct tcp_options_received *tcp_opt,
236             struct net *net, bool *ecn_ok)
237 {   
238     /* echoed timestamp, lowest bits contain options */
239     u32 options = tcp_opt->rcv_tsecr & TSMASK;
240     
241     if (!tcp_opt->saw_tstamp)  {
242         tcp_clear_options(tcp_opt);
243         return true;    
244     }
245     
246     if (!sysctl_tcp_timestamps)
247         return false;   
248 
249     tcp_opt->sack_ok = (options & (1 << 4)) ? TCP_SACK_SEEN : 0;
250     *ecn_ok = (options >> 5) & 1;
251     if (*ecn_ok && !net->ipv4.sysctl_tcp_ecn)
252         return false;
253 
254     if (tcp_opt->sack_ok && !sysctl_tcp_sack)
255         return false;
256 
257     if ((options & 0xf) == 0xf)
258         return true; /* no window scaling */
259 
260     tcp_opt->wscale_ok = 1;
261     tcp_opt->snd_wscale = options & 0xf;
262     return sysctl_tcp_window_scaling != 0;
263 }
  后续流程与不使用SYN Cookie的情况是一样的.

  由此看出SYN Cookie的原理:

1、Server端收到SYN请求后不建立request_sock保存连接信息,而是将SYN包中的MSS值编码进cookie中,并将cookie作为初始序列号写入SYN|ACK中;如果SYN中开启了时间戳,则将SYN的SACK选项和窗口扩大选项信息编码进时间戳的值中,再将这个时间戳写入SYN|ACK中,最后将SYN|ACK发送出去

2、Client端收到SYN|ACK后,按照TCP协议的标准处理来发送ACK包;Server端在收到ACK后,从ACK的ack_seq中得到SYN请求的MSS值,从ACK包的时间戳回显值得到SYN请求中的SACK和窗口扩大选项;根据这些选项的信息建立新的sock

  根据上述原理,在TCP开启SYN Cookie功能的情况下如果攻击者想让SYN Flood攻击奏效,则必须完成全部三次握手流程才行,这样会加重攻击者的负担,减小攻击的危害。开启SYN Cookie功能的缺点是:如果client端没有开启时间戳,则SACK和窗口扩大选项无法使用。

你可能感兴趣的:(tcp,linux内核)