Linux tcp_syncookies 洪水防御 cookie_v4_init_sequence

发布时间:2026/7/21 13:34:13

Linux tcp_syncookies 洪水防御 cookie_v4_init_sequence Linux tcp_syncookies 洪水防御 cookie_v4_init_sequence 实现SYN cookies 是内核抵御 SYN flood 攻击的核心机制基于 stateless 的初始序列号生成算法在 listen socket 的半连接队列request_sock_queue满时启用。核心函数 cookie_v4_init_sequence 生成 SYNACK 响应的初始序列号ISN该序列号编码了连接四元组的哈希值和部分 TCP 选项信息使服务器在收到 ACK 时无需保存半连接状态即可重建连接参数。c__u32 cookie_v4_init_sequence(const struct sk_buff *skb,struct request_sock *req){const struct iphdr *iph ip_hdr(skb);const struct tcphdr *th tcp_hdr(skb);int mssind;__u32 seq;mssind cookie_v4_check(iph, th, req);seq cookie_hash(iph-saddr, iph-daddr,th-source, th-dest,COOKIE_MESSAGE_SYNACK) mssind;return seq;}cookie_hash 是核心哈希函数使用静态密钥syncookie_secret[2]和 SHA-1/MD5 对五元组saddr、daddr、sport、dport、密钥索引 时间戳计数计算。密钥在系统启动时通过 syncookie_init 使用 get_random_bytes 初始化此后不改变。每 60 秒时间槽计数器递增保证旧 cookie 在窗口过期后失效。cstatic u32 __cookie_v4_init_sequence(const struct iphdr *iph,const struct tcphdr *th,u16 *mssp){int mssind;const __u16 *ports;ports (__u16 *)th;mssind tcp_syncookie_mss(iph, th, ports);if (mssind 0)return 0;*mssp tcp_syncookie_mssvals[mssind];return cookie_hash(iph-saddr, iph-daddr,ports[0], ports[1],COOKIE_MESSAGE_SYNACK) mssind;}ISN 低 4 位mssind 范围 0-7编码 MSS 索引对应 8 种预设 MSS 值536、1100、1300、1400、1460、1420、1440、1452。tcp_syncookie_mssvals[8] 数组在 cookie 初始化时预定义覆盖了常见的以太网 MTU1460和 PPPoE1452等场景。发送端在 SYNACK 中通过这些编码向客户端通告 MSS客户端必须在 ACK 的确认序列号中精确回传该值服务端才能正确重建 MSS。ACK 到达后的 cookie 校验在 cookie_v4_check 中执行cstruct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb){struct iphdr *iph ip_hdr(skb);const struct tcphdr *th tcp_hdr(skb);__u32 cookie ntohl(th-ack_seq) - 1;__u32 seq;int mssind;mssind cookie 0x07;seq cookie_hash(iph-saddr, iph-daddr,th-source, th-dest,COOKIE_MESSAGE_ACK) mssind;if (seq ! cookie)return NULL;req inet_reqsk_alloc(tcp_request_sock_ops, sk, false);if (!req)return NULL;tcp_ao_syncookie(sk, skb, req, AF_INET);tcp_parse_options(sock_net(sk), skb, tcp_opt, 0, NULL);ireq-ireq_opt tcp_v4_save_options(sock_net(sk), skb);tcp_ao_syncookie_mapping(sk, skb, req, AF_INET);...return child;}cookie_v4_check 通过 ack_seq - 1 提取 cookie然后使用 COOKIE_MESSAGE_ACK 重新计算哈希并与 cookie 比较。这里的时间窗口处理通过 is_tcp_ts_valid 检测 ACK 携带的 TSval若在宽限期内默认 tcp_syncookies_cookie_ts 用于减少误判即使当前时间槽已移位但 TSval 仍在 cookie 的生命周期内仍视为合法。基于 SYN cookie 的连接无法使用 TCP 的时间戳扩展选项因为半连接状态不保存 TS val但接收 ACK 时若发现对方携带了 TSopt内核通过 tcp_parse_options 提取并赋值给 ireq-ts_recent 和 ireq-rcv_tsval。这是针对后来的 ACK 使用 PAWS 检测乱序 ACK 的依据。核心竞态条件cookie_v4_init_sequence 在 tcp_conn_request 的 BH 上下文中被调用此时 listen socket 未加锁。多个 CPU 可以同时执行 tcp_v4_conn_request 并对同一个 listen socket 调用 cookie_v4_init_sequence但 cookie_hash 使用 RCU 保护的密钥无写操作因此完全可重入。cookie 启用条件tcp_conn_request 中检查 listen_opt-syn_queue max_syn_retries 溢出或内存不足时调用 cookie_v4_init_sequence。默认启用条件由 sysctl_tcp_syncookies 控制0关闭1仅在半连接队列满时启用2强制启用。当 sysctl_tcp_syncookies 2 时tcp_conn_request 无条件跳过 inet_csk_reqsk_queue_is_full 检查直接使用 SYN cookie。cint tcp_conn_request(struct request_sock_ops *rsk_ops,const struct tcp_request_sock_ops *af_ops,struct sock *sk, struct sk_buff *skb){struct tcp_fastopen_cookie foc { .len -1 };struct tcp_options_received tmp_opt;struct request_sock *req;bool want_cookie false;if (listen_opt-synflood_warned tcp_sk(sk)-synflood_warned !sock_net(sk)-ipv4.sysctl_tcp_syncookies) {...want_cookie false;} else if (inet_csk_reqsk_queue_is_full(sk)) {want_cookie tcp_syn_flood_action(sk, skb, TCP);}...if (want_cookie !tmp_opt.tstamp_ok)tcp_clear_options(tmp_opt);req inet_reqsk_alloc(rsk_ops, sk, want_cookie);if (!req) {if (want_cookie)goto drop_and_reuse;goto drop;}...}SYN cookie 与 TFOTCP Fast Open同时启用时的交互若同时启用 TFO 和 SYN cookie且收到带 TFO cookie 的 SYNtcp_conn_request 优先验证 TFO cookie。只有 TFO cookie 验证失败且 syncookies 启用才 fallback 到 SYN cookie。TFO cookie 的编码通过 cookie_init_timestamp 集成到 TSval 的高位字节中与 SYN cookie 互不覆盖。SYN cookie 的安全限制由于 cookie 仅编码 8 种 MSS在接收 ACK 时重建的连接丢失了窗口缩放wscale、选择性确认SACK、时间戳timestamp等 TCP 选项。内核在 cookie_v4_check 最后调用 tcp_synack_no_fastopen 检测客户端是否支持快速打开但 wscale 只能通过客户端 ACK 中携带的 SYN 选项被动恢复在 ACK 未携带选项时使用默认值。连接建立后的吞吐可能低于正常连接但对于抗洪来说是可接受的折衷。cstatic __u32 cookie_hash(__be32 saddr, __be32 daddr, __be16 sport, __be16 dport,int msg_type){u32 *key syncookie_secret[msg_type];u32 tmp[4];tmp[0] saddr;tmp[1] daddr;tmp[2] (sport 16) dport;tmp[3] msg_type jiffies / HZ - 60;return syncookie_hmac(tmp, sizeof(tmp), key);}jiffies / HZ - 60 表示当前时间槽60 秒颗粒度。cookie 的生命周期为 120 秒两个时间槽超过后通过 seq 匹配失效客户端的 ACK 被丢弃。这种设计防止了重放攻击即使攻击者捕获了 cookie在时间槽切换后旧 cookie 失效。

相关新闻