TCP 协议中的 SYN queue 和 accept queue 处理
若要理解本文意图说明的问题, 可能需要以下知识背景:
listen 系统调用的 backlog 参数含义, 以及与 net.core.somaxconn 参数的关系;
SYN flood 攻击与防护;
SYN queue 和 accept queue 的用途, 以及在不同 linux 版本中的实现差异;
----
在 SYN queue 未满的情况下, 在收到 SYN 包后, TCP 协议栈自动回复 SYN,ACK 包, 之后在收到 ACK 时, 根据 accept queue 状态进行后续处理;
若 SYN queue 已满, 在收到 SYN 时
若设置 net.ipv4.tcp_syncookies = 0 , 则直接丢弃当前 SYN 包;
若设置 net.ipv4.tcp_syncookies = 1 , 则令 want_cookie = 1 继续后面的处理;
若 accept queue 已满, 并且 qlen_young 的值大于 1 , 则直接丢弃当前 SYN 包;
若 accept queue 未满, 或者 qlen_young 的值未大于 1 , 则输出 "possible SYN flooding on port %d. Sending cookies.\n", 生成 syncookie 并在 SYN,ACK 中带上;
--- 以下源码取自 linux-2.6.32 版本 ---
- int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
- {
- ...
- #ifdef CONFIG_SYN_COOKIES
- int want_cookie = 0;
- #else
- #define want_cookie 0 /* Argh, why doesn't gcc optimize this :( */
- #endif
- ...
- /* TW buckets are converted to open requests without
- * limitations, they conserve resources and peer is
- * evidently real one.
- */
- // 判定 SYN queue 是否已满
- if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
- #ifdef CONFIG_SYN_COOKIES
- if (sysctl_tcp_syncookies) { // SYN queue 已满, 并且设置了 net.ipv4.tcp_syncookies = 1 , 则设置 want_cookie = 1 以便后续处理
- want_cookie = 1;
- } else
- #endif
- goto drop; // 否则, 直接丢弃当前 SYN 包
- }
- /* Accept backlog is full. If we have already queued enough
- * of warm entries in syn queue, drop request. It is better than
- * clogging syn queue with openreqs with exponentially increasing
- * timeout.
- */
- // 若此时 accept queue 也已满, 并且 qlen_young 的值大于 1(即保存在 SYN queue 中未进行 SYN,ACK 重传的连接超过 1 个)
- // 则直接丢弃当前 SYN 包(相当于针对 SYN 进行了速率限制)
- if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk)> 1)
- goto drop;
- ...
- // 若 accept queue 未满, 或者 qlen_young 的值未大于 1
- if (want_cookie) {
- #ifdef CONFIG_SYN_COOKIES
- syn_flood_warning(skb); // 输出 "possible SYN flooding on port %d. Sending cookies.\n"
- req->cookie_ts = tmp_opt.tstamp_ok; // 为当前 socket 设置启用 cookie 标识
- #endif
- // 生成 syncookie
- isn = cookie_v4_init_sequence(sk, skb, &req->mss);
- } else if (!isn) {
- ...
- }
- // 保存 syncookie 值
- tcp_rsk(req)->snt_isn = isn;
- // 回复 SYN,ACK , 若之前设置了 net.ipv4.tcp_syncookies = 1 则会释放对应的 socket 结构
- if (__tcp_v4_send_synack(sk, req, dst) || want_cookie)
- goto drop_and_free;
- // 启动 SYN,ACK 重传定时器
- inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
- return 0;
- drop_and_release:
- dst_release(dst);
- drop_and_free:
- reqsk_free(req);
- drop:
- return 0;
- }
- ---
若 accept queue 已满, 在收到三次握手最后的 ACK 时
若设置 tcp_abort_on_overflow = 1 , 则 TCP 协议栈回复 RST 包, 并直接从 SYN queue 中删除该连接信息;
若设置 tcp_abort_on_overflow = 0 , 则 TCP 协议栈将该连接标记为 acked , 但仍保留在 SYN queue 中, 并启动 timer 以便重发 SYN,ACK 包; 当 SYN,ACK 的重传次数超过 net.ipv4.tcp_synack_retries 设置的值时, 再将该连接从 SYN queue 中删除;
- ---
- /*
- * Process an incoming packet for SYN_RECV sockets represented
- * as a request_sock.
- */
- struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
- struct request_sock *req,
- struct request_sock **prev)
- {
- ...
- /* OK, ACK is valid, create big socket and
- * feed this segment to it. It will repeat all
- * the tests. THIS SEGMENT MUST MOVE SOCKET TO
- * ESTABLISHED STATE. If it will be dropped after
- * socket is created, wait for troubles.
- */
- // 调用 net/ipv4/tcp_ipv4.c 中的 tcp_v4_syn_recv_sock 函数
- // 判定 accept queue 是否已经满, 若已满, 则返回的 child 为 NULL
- child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);
- if (child == NULL)
- goto listen_overflow;
- // 在 accept queue 未满的情况下, 将 ESTABLISHED 连接从 SYN queue 搬移到 accept queue 中
- inet_csk_reqsk_queue_unlink(sk, req, prev);
- inet_csk_reqsk_queue_removed(sk, req);
- inet_csk_reqsk_queue_add(sk, req, child);
- return child;
- listen_overflow:
- // 若 accept queue 已满, 但设置的是 net.ipv4.tcp_abort_on_overflow = 0
- if (!sysctl_tcp_abort_on_overflow) {
- inet_rsk(req)->acked = 1; // 则只标记为 acked , 直接返回, 相当于忽略当前 ACK
- return NULL;
- }
- embryonic_reset:
- // 若 accept queue 已满, 但设置的是 net.ipv4.tcp_abort_on_overflow = 1
- NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_EMBRYONICRSTS); // 变更统计信息
- if (!(flg & TCP_FLAG_RST))
- req->rsk_ops->send_reset(sk, skb); // 发送 RST
- inet_csk_reqsk_queue_drop(sk, req, prev); // 直接从 SYN queue 中删除该连接信息
- return NULL;
- }
来源: http://www.bubuko.com/infodetail-2768504.html