成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Linux 內(nèi)核網(wǎng)絡之 tcp 三次握手

系統(tǒng) Linux
今天我們一起來了解一下Linux 內(nèi)核網(wǎng)絡之 tcp 三次握手的相關(guān)流程。

三次握手流程如下圖:

服務器端監(jiān)聽

在client端向server端進行連接前,server處于監(jiān)聽狀態(tài)。流程如下:

int reqsk_queue_alloc(struct request_sock_queue *queue,
unsigned int nr_table_entries)
{
size_t lopt_size = sizeof(struct listen_sock);
struct listen_sock *lopt;
//計算半連接隊列的長度
nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
nr_table_entries = max_t(u32, nr_table_entries, 8);
..
//為半連接隊列申請內(nèi)存
lopt_size += nr_table_entries * sizeof(struct request_sock );
if (lopt_size > PAGE_SIZE)
/ 如果申請內(nèi)存大于1頁,則申請?zhí)摂M地址連續(xù)的空間 /
lopt = __vmalloc(lopt_size,GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,PAGE_KERNEL);
else
/ 申請內(nèi)存在1頁內(nèi),則申請物理地址連續(xù)的空間 */
lopt = kzalloc(lopt_size, GFP_KERNEL);
//全連接隊列頭初始化
queue->rskq_accept_head = NULL;
// 半連接隊列的最大長度
lopt->nr_table_entries = nr_table_entries;
...
//半連接隊列設置
queue->listen_opt = lopt;
return 0;
}

服務器端在監(jiān)聽時初始化半連接 hash 表,然后掛到接收隊列中,等待客戶端連接。

另外 queue->rskq_accept_head為全連接隊列,是一個以鏈表的形式進行管理全連接。

關(guān)于半連接和全連接的結(jié)構(gòu)如下:

client 發(fā)送 SYN 報文

client 向 server 進行 connect 時,最終會調(diào)用 tcp_v4_connect 向server發(fā)送 SYN 報文。

int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
...
//設置 socket 狀態(tài)為 TCP_SYN_SENT
tcp_set_state(sk, TCP_SYN_SENT);
...
//函數(shù)用來根據(jù) sk 中的信息,構(gòu)建一個完成的 syn 報文,并將它發(fā)送出去。
err = tcp_connect(sk);
...
}
int tcp_connect(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *buff;
tcp_connect_init(sk);
//申請 skb 并構(gòu)造為一個 SYN 包
buff = alloc_skb_fclone(MAX_TCP_HEADER + 15, sk->sk_allocation);
...
//添加到發(fā)送隊列 sk_write_queue 上
__skb_queue_tail(&sk->sk_write_queue, buff);
...
//發(fā)送syn報文
tcp_transmit_skb(sk, buff, 1, GFP_KERNEL);
...
/* Timer for repeating the SYN until an answer. */
//啟動重傳定時器
inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
return 0;
}

在 tcp_connect 中申請并構(gòu)造一個SYN 包,然后將其發(fā)出。同時還啟動了一個重傳定時器,該定時器的作用就是等到一定時間后若收不到服務器的反饋時進行重傳。

server 端接收 SYN 并發(fā)送 SYN+ACK

所有到server端的tcp數(shù)據(jù)包都會經(jīng)過網(wǎng)卡、軟中斷,最終到 tcp_v4_rcv。

在 tcp_v4_rcv 中根據(jù)報文報文頭中的信息,從監(jiān)聽的hash 表 listening_hash 中找到對應監(jiān)聽的 socket 結(jié)構(gòu)。然后進入 tcp_v4_do_rcv

進行握手處理。

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
...
//服務器收到第一步握手 SYN 或者第三步 ACK 都會走到這里
if (sk->sk_state == TCP_LISTEN) {
//從半連接表syn_table中取出節(jié)點
struct sock *nsk = tcp_v4_hnd_req(sk, skb);
...
}
}

由于當前 socket 是 listen 狀態(tài),首先會到 tcp_v4_hnd_req 去查看半連接隊列。服務器第一次響應 SYN 的時候,半連接隊列里是空的,所以相當于什么也沒干就返回了。

static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
{
// 從半連接隊里中查詢
struct request_sock *req = inet_csk_search_req(sk, &prev, th->source, iph->saddr, iph->daddr);
return sk;
}
struct request_sock *inet_csk_search_req(const struct sock *sk,
struct request_sock ***prevp,
const __be16 rport, const __be32 raddr,
const __be32 laddr)
{
for (prev = &lopt->syn_table[inet_synq_hash(raddr, rport, lopt->hash_rnd,
lopt->nr_table_entries)];
(req = *prev) != NULL;
prev = &req->dl_next) {
...
}
}
return req;
}

在 tcp_rcv_state_process 中根據(jù)各種狀態(tài)做不同的處理。

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
struct tcphdr *th, unsigned len)
{
...
case TCP_LISTEN:
...
//判斷是否為 SYN 包
if(th->syn) {
//調(diào)用 tcp_v4_conn_request
if (icsk->icsk_af_ops->conn_request(sk, skb) < 0)
return 1;
...
}
goto discard;
case TCP_SYN_SENT:
...
return 0;
}

由于對方發(fā)來的 SYN 報文, 調(diào)用 tcp_v4_conn_request 進行處理。

int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
...
//查看半連接隊列是否已滿
if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
#ifdef CONFIG_SYN_COOKIES
if (sysctl_tcp_syncookies) {
want_cookie = 1;
} else
#endif
goto drop;
}
//在全連接隊列滿的情況下,如果有 young_ack,那么直接丟
if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)
goto drop;
//分配 request_sock 內(nèi)核對象,該request_sock->sk 此時還為空
req = reqsk_alloc(&tcp_request_sock_ops);
if (!req)
goto drop;
...
tcp_rsk(req)->snt_isn = isn;
// 發(fā)送 syn+ack 包
if (tcp_v4_send_synack(sk, req, dst))
goto drop_and_free;
if (want_cookie) {
reqsk_free(req);
} else {
//添加到半連接隊列,并開啟計時器,
inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
}
return 0;
}

inet_csk_reqsk_queue_is_full 如果返回 true 就表示半連接隊列滿了,另外 sysctl_tcp_syncookies 判斷是否打開了內(nèi)核參數(shù) tcp_syncookies,如果未打開則返回 false。

如果半連接隊列滿了,而且 ipv4.tcp_syncookies 參數(shù)設置為 0,那么來自客戶端的握手包將 goto drop,也就是數(shù)據(jù)包直接丟棄,此時客戶端感知不到報文被 server 丟棄,依靠重傳定時器重傳。

SYN Flood 攻擊就是通過消耗光服務器上的半連接隊列來使得正常的用戶連接請求無法被響應。不過在現(xiàn)在的 Linux 內(nèi)核里只要打開 tcp_syncookies,半連接隊列滿了仍然也還可以保證正常握手的進行。

sk_acceptq_is_full 來判斷全連接隊列是否滿了, inet_csk_reqsk_queue_young 判斷的是有沒有 young_ack(未處理完的半連接請求)。

若全連接隊列滿的情況下,且同時有 young_ack ,那么內(nèi)核同樣直接丟掉該 SYN 握手包。

young_ack 是半連接隊列里保持著的一個計數(shù)器。記錄的是剛有SYN到達,沒有被SYN_ACK重傳定時器重傳過 SYN_ACK,同時也沒有完成過三次握手的 sock 數(shù)量。

從上面可以看到,若隊列已滿,server 端直接丟棄報文,并不通知客戶端。這時候客戶端只能通過發(fā)送 SYN 包時開啟的重傳定時器超時進行重傳。

server 構(gòu)造 synack 報文進行回應。

若啟用 syncookies,則是根據(jù)需要來判斷三次握手的,因此無需保存連接請求,直接將其釋放。

若未開啟 syncookies,則需將連接請求塊保存到其父傳輸控制塊中的半連接散列表中,并設置啟動連接定時器。計時器的作用是如果某個時間之內(nèi)還收不到客戶端的第三次握手的話,服務器會重傳 synack 包。

request_sock 內(nèi)核對象加入到半連接表中,如下圖

客戶端響應 SYNACK

client 收到 synack 包后,最終會走到 tcp_rcv_state_process中,此時socket的狀態(tài)為 TCP_SYN_SENT

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
struct tcphdr *th, unsigned len)
{
...
switch (sk->sk_state) {
case TCP_CLOSE:
goto discard;
case TCP_LISTEN:
...
case TCP_SYN_SENT:
//處理 synack 包,返回值大于0表示需給對方發(fā)送RST段,該TCP段的釋放由tcp_rcv_state_process調(diào)用者處理
queued = tcp_rcv_synsent_state_process(sk, skb, th, len);
if (queued >= 0)
return queued;
/* Do step6 onward by hand. */
//在處理完接收的段后,還需要處理緊急數(shù)據(jù),然后釋放該段,最后檢測是否有數(shù)據(jù)需要發(fā)送。
tcp_urg(sk, skb, th);
__kfree_skb(skb);
tcp_data_snd_check(sk, tp);
return 0;
}
...
return 0;
}

關(guān)于 synack 包的處理邏輯為 tcp_rcv_synsent_state_process。

static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
struct tcphdr *th, unsigned len)
{
...
if (th->ack) {
...
//若收到ack+rst段,則調(diào)用tcp_reset設置ECONNREFUSED錯誤碼,同時通知等待該套接口的進程,然后關(guān)閉套接口
if (th->rst) {
tcp_reset(sk);
goto discard;
}
//在SYS_SENT狀態(tài)下接收的段必須存在SYN標志,否則說明接收到的段無效,然后跳到discard_and_undo丟棄該段
if (!th->syn)
goto discard_and_undo;
TCP_ECN_rcv_synack(tp, th);
// 初始化與窗口有關(guān)的成員變量
tp->snd_wl1 = TCP_SKB_CB(skb)->seq;
tcp_ack(sk, skb, FLAG_SLOWPATH);// 刪除發(fā)送隊列和重傳定時器
/* Ok.. it's good. Set up sequence numbers and
? move to established.
*/
tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;
tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;
...
//設置已完成連接狀態(tài)
tcp_set_state(sk, TCP_ESTABLISHED);
...
//初始化擁塞控制
tcp_init_congestion_control(sk);
...
//若啟用了連接保活,則啟用連接保活定時器
if (sock_flag(sk, SOCK_KEEPOPEN))
inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tp));
if (!tp->rx_opt.snd_wscale)
__tcp_fast_path_on(tp, tp->snd_wnd);
else
tp->pred_flags = 0;
//若不處于SOCK_DEAD,則喚醒等待該套接口的進程,同時向套接口的異步等待隊列上的進程發(fā)送信號,通知他們該套接口可以輸出數(shù)據(jù)了
if (!sock_flag(sk, SOCK_DEAD)) {
/* 指向sock_def_wakeup,會喚醒調(diào)用connect()的進程,完成連接的建立 /
sk->sk_state_change(sk);
/ 如果使用了異步通知,則發(fā)送SIGIO通知進程可寫 */
sk_wake_async(sk, 0, POLL_OUT);
}
...
discard:
__kfree_skb(skb);
return 0;
} else {
// 發(fā)送ack段,同時更新窗口。
tcp_send_ack(sk);
}
return -1;
}
...
}

處理 TCP 段中存在 ACK 標志的情況。

啟動連接保活定時器。

喚醒調(diào)用connect阻塞的進程,然后發(fā)送 ACK 報文。

void tcp_send_ack(struct sock sk)
{
/ If we have been reset, we may not send again. */
//發(fā)送ack時,tcp必須不在TCP_CLOSE狀態(tài)
if (sk->sk_state != TCP_CLOSE) {
...
//為ack分配一個SKB,若分配失敗則在啟動延時確認定時器后返回
buff = alloc_skb(MAX_TCP_HEADER, GFP_ATOMIC);
...
/* Send it off, this clears delayed acks for us. */
//設置序號和發(fā)送時間,調(diào)用tcp_transmit_skb將ack段發(fā)送出去
TCP_SKB_CB(buff)->seq = TCP_SKB_CB(buff)->end_seq = tcp_acceptable_seq(sk, tp);
TCP_SKB_CB(buff)->when = tcp_time_stamp;
tcp_transmit_skb(sk, buff, 0, GFP_ATOMIC);
}
}

client 收到對端發(fā)送的 synack 包后,清除了 connect 時設置的重傳定時器,把 socket 狀態(tài)設置為 TCP_ESTABLISHED,同時喚醒調(diào)用 connect 而阻塞的進程,開啟保活定時器并發(fā)送第三次握手的 ack 確認包。

server 端處理 ACK 包

關(guān)于 server 處理 ack 報文的過程如下:

|-> tcp_v4_do_rcv

. |-> tcp_v4_hnd_req

. . |-> inet_csk_search_req // 從半連接中取出連接請求塊request_sock

. . |-> tcp_check_req

. . . |-> syn_recv_sock => tcp_v4_syn_recv_sock

. . . . |-> tcp_create_openreq_child

. . . . . |-> inet_csk_clone // 生成一個sock結(jié)構(gòu),設置 TCP_SYN_RECV 狀態(tài)

. . . |->

inet_csk_reqsk_queue_unlink // 把連接請求塊request_sock從半連接隊列中刪除

. . . |-> inet_csk_reqsk_queue_add //把request_sock和生成的sock進行關(guān)聯(lián),并掛到icsk_accept_queue 全連接隊列中

. |-> tcp_child_process

. . |-> tcp_rcv_state_process //設置狀態(tài) TCP_ESTABLISHED

. . |-> sk_data_ready => sock_def_readable //喚醒阻塞在accept上的進程

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
...
//服務器收到第一步握手 SYN 或者第三步 ACK 都會走到這里
if (sk->sk_state == TCP_LISTEN) {
//從半連接表syn_table中取出連接請求塊request_sock,同時生成一個新的sock結(jié)構(gòu)
struct sock *nsk = tcp_v4_hnd_req(sk, skb);
if (!nsk)
goto discard;
// 新生成的sock和監(jiān)聽的不一樣
if (nsk != sk) {
//設置狀態(tài) TCP_ESTABLISHED并喚醒阻塞在accept上的進程
if (tcp_child_process(sk, nsk, skb)) {
rsk = nsk;
goto reset;
}
return 0;
}
}
...
}
static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
{
...
// 從半連接hash表中獲取連接請求塊request_sock
struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,
iph->saddr, iph->daddr);
if (req)
return tcp_check_req(sk, skb, req, prev);
...
}
inet_csk_search_req

在半連接隊列里進行查找并返回一個半連接 request_sock 對象。然后進入到 tcp_check_req 中

struct sock *tcp_check_req(struct sock *sk,struct sk_buff *skb,
struct request_sock *req,
struct request_sock **prev)
{
...
//創(chuàng)建子 socket ,調(diào)用 tcp_v4_syn_recv_sock
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb,
req, NULL); //tcp_v4_syn_recv_sock
//清理半連接隊列
inet_csk_reqsk_queue_unlink(sk, req, prev);
inet_csk_reqsk_queue_removed(sk, req);
//把request_sock和生成的sock進行關(guān)聯(lián),并把request_sock添加到全連接隊列
inet_csk_reqsk_queue_add(sk, req, child);
return child;
...
}

創(chuàng)建子 socket 并初始化,然后把新生成newsk 加入到 ehash hash 表中, 以后當有報文到來時,從該 hash 表中找對應的sock結(jié)構(gòu),也就找到了對應的進程。

struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
struct request_sock *req,
struct dst_entry *dst)
{
...
//判斷接收隊列是不是滿了,若滿,丟棄
if (sk_acceptq_is_full(sk))
goto exit_overflow;
...
//創(chuàng)建 sock && 初始化
newsk = tcp_create_openreq_child(sk, req, skb);
...
// 把 newsk 加入到 已完成鏈接的ehash hash表中
__inet_hash(&tcp_hashinfo, newsk, 0);
__inet_inherit_port(&tcp_hashinfo, sk, newsk);
...
}

設置 TCP_ESTABLISHED 狀態(tài)

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb, struct tcphdr *th, unsigned len)
{
...
if (th->ack) {
...
switch(sk->sk_state) {
case TCP_SYN_RECV:
//設置狀態(tài) TCP_ESTABLISHED
tcp_set_state(sk, TCP_ESTABLISHED);
...
}

第三次握手的主要功能就是從半連接 hash表中摘除連接請求塊 request_sock,然后生成一個 sock 與之進行關(guān)聯(lián),然后再把 request_sock 添加到全連接隊列。

server 從 accept 中被喚醒

server 調(diào)用 accept 時由于 icsk_accept_queue 隊列沒有為空,進程被阻塞等待。

struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
{
...
if (sk->sk_state != TCP_LISTEN) /* socket必須處于監(jiān)聽狀態(tài) *
goto out_err;
/* Find already established connection /
// 發(fā)現(xiàn)沒有ESTABLISHED狀態(tài)的連接請求塊
if (reqsk_queue_empty(&icsk->icsk_accept_queue)) {
/ 等待超時時間,如果是非阻塞則為0 */
long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
/* If this is a non blocking socket don't sleep /
error = -EAGAIN;
if (!timeo) / 如果是非阻塞的,則直接退出 /
goto out_err;
/ 阻塞等待,直到有全連接。如果用戶有設置等待超時時間,超時后會退出 /
error = inet_csk_wait_for_connect(sk, timeo);
if (error)
goto out_err;
}
/ 獲取新連接的sock,釋放連接控制塊 */
newsk = reqsk_queue_get_child(&icsk->icsk_accept_queue, sk);
BUG_TRAP(newsk->sk_state != TCP_SYN_RECV);
out:
release_sock(sk);
return newsk;
out_err:
newsk = NULL;
*err = error;
goto out;
}

上三次握手完成后,server 被喚醒,此時全連接隊列 icsk_accept_queue 不空,server 調(diào)用 reqsk_queue_get_child() 從全連接隊列中獲取一個新的sock。

static inline struct sock *reqsk_queue_get_child(struct request_sock_queue *queue,
struct sock parent)
{
/ 從全連接隊列中,取出第一個ESTABLISHED狀態(tài)的連接請求塊 */
struct request_sock *req = reqsk_queue_remove(queue);
struct sock child = req->sk; / 一個已建立的連接 */
BUG_TRAP(child != NULL);
/* 當前backlog隊列的全連接數(shù)減一 */
sk_acceptq_removed(parent);
__reqsk_free(req);
return child;
}

accept 的作用就是從已經(jīng)建立好的全連接隊列中取出一個返回已完成連接的 sock 返回給用戶進程。

責任編輯:華軒 來源: 今日頭條
相關(guān)推薦

2021-03-08 18:08:08

TCP Connect 協(xié)議

2024-10-09 20:54:16

2023-09-07 16:46:54

TCP數(shù)據(jù)傳遞

2023-10-24 15:22:09

TCPUDP

2020-12-08 06:34:16

TCP握手SYN 報文

2022-10-10 07:34:36

TCP三次握手區(qū)塊鏈

2015-10-13 09:42:52

TCP網(wǎng)絡協(xié)議

2019-06-12 11:26:37

TCP三次握手四次揮手

2024-01-12 08:23:11

TCPACK服務器

2022-07-25 07:07:35

TCP客戶端服務器

2018-07-05 14:25:01

TCP握手原理

2019-12-12 10:36:43

TCPSYNIP

2022-07-07 09:00:17

TCP 連接HTTP 協(xié)議

2023-09-02 21:57:52

網(wǎng)絡TCP協(xié)議

2020-08-27 07:41:28

TCP協(xié)議數(shù)據(jù)

2018-10-15 08:06:33

TCP握手原理

2021-07-03 17:47:25

TCP控制協(xié)議

2019-02-01 09:38:16

2021-01-29 06:11:08

TCP通信三次握手

2021-05-18 12:27:40

TCP控制協(xié)議
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 国产免费一区二区三区网站免费 | 国产小视频在线看 | av天天看| 在线日韩精品视频 | 国产在线一区二区三区 | 日韩欧美一级精品久久 | 亚洲精品视频在线 | 久久久久久天堂 | 欧美日韩专区 | www亚洲免费国内精品 | 密室大逃脱第六季大神版在线观看 | 逼逼网 | 亚洲高清视频在线观看 | 中文字幕欧美一区二区 | 一级欧美| 在线亚洲免费 | 亚洲人成人一区二区在线观看 | 亚洲精品区 | 免费在线观看成人 | 亚洲啊v在线 | 久久在线免费 | 日本精品久久久久久久 | 亚洲人成人一区二区在线观看 | 91精品国产综合久久精品图片 | 成人精品久久 | 国产精品国产a | 狠狠干五月天 | 亚洲一区二区久久久 | 精品不卡 | 国产亚洲精品美女久久久久久久久久 | 国产一区二区三区免费观看视频 | 欧洲视频一区二区 | 韩国理论电影在线 | 久久亚洲国产精品日日av夜夜 | 国产精品视频在线观看 | 日韩高清中文字幕 | 成人亚洲视频 | 亚洲在线视频 | 亚洲一区二区网站 | av毛片| 日本不卡免费新一二三区 |