1. 程式人生 > >tcp的半連接與完全連接隊列(三)源碼分析

tcp的半連接與完全連接隊列(三)源碼分析

lin already .so sequence proc 狀態 ant 用途 title

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;
}

tcp的半連接與完全連接隊列(三)源碼分析