若要理解本文意图说明的问题,可能需要以下知识背景:
-
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)
{
...
int want_cookie = 0;
...
/* 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) {
if (sysctl_tcp_syncookies) { // SYN queue 已满,并且设置了 net.ipv4.tcp_syncookies = 1 ,则设置 want_cookie = 1 以便后续处理
want_cookie = 1;
} else
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) {
syn_flood_warning(skb); // 输出 "possible SYN flooding on port %d. Sending cookies.\n"
req->cookie_ts = tmp_opt.tstamp_ok; // 为当前 socket 设置启用 cookie 标识
// 生成 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 中删除; ---
struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
struct request_sock *req,
struct request_sock **prev)
{
...
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);
if (child == NULL)
goto listen_overflow;
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:
if (!sysctl_tcp_abort_on_overflow) {
inet_rsk(req)->acked = 1;
return NULL;
}
embryonic_reset:
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_EMBRYONICRSTS);
if (!(flg & TCP_FLAG_RST))
req->rsk_ops->send_reset(sk, skb);
inet_csk_reqsk_queue_drop(sk, req, prev);
return NULL;
}