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和窗口扩大选项无法使用。