假設客戶端執行主動打開,已經經過第一次握手,即發送SYN包到服務器,狀態變為SYN_SENT,服務器收到該包后,回復SYN+ACK包,客戶端收到該包,進行主動打開端的第二次握手部分;流程中涉及到的函數和細節非常多,本篇只對主流程予以分析;
在ESTABLISHED和TIME_WAIT以外的狀態時接收到包,會調用tcp_rcv_state_process函數來處理,處理部根據不同狀態做對應處理,如果處於SYN_SENT狀態,則會調用tcp_rcv_synsent_state_process函數進入和該狀態的核心處理流程;
1 /* 2 * This function implements the receiving procedure of RFC 793 for 3 * all states except ESTABLISHED and TIME_WAIT. 4 * It's called from both tcp_v4_rcv and tcp_v6_rcv and should be 5 * address independent. 6 */ 7 8 int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb) 9 { 10 switch (sk->sk_state) { 11 case TCP_CLOSE: 12 goto discard; 13 14 case TCP_LISTEN: 15 /* 省略部分代碼 */ 16 17 case TCP_SYN_SENT: 18 tp->rx_opt.saw_tstamp = 0; 19 skb_mstamp_get(&tp->tcp_mstamp); 20 21 /* syn_sent狀態處理 */ 22 queued = tcp_rcv_synsent_state_process(sk, skb, th); 23 24 /* 下面返回值> 0,調用者需要發送rst */ 25 if (queued >= 0) 26 return queued; 27 28 /* Do step6 onward by hand. */ 29 /* 處理緊急數據 */ 30 tcp_urg(sk, skb, th); 31 __kfree_skb(skb); 32 33 /* 檢查是否有數據要發送 */ 34 tcp_data_snd_check(sk); 35 return 0; 36 } 37 38 }
tcp_rcv_synsent_state_process函數為SYN_SENT狀態輸入數據包的核心處理函數,這里我們只關系服務器發送SYN+ACK的處理流程,其他部分暫且省略;函數主要完成以下幾項工作:解析tcp選項;對序號,時間錯,標志位等合法性做檢查;調用函數tcp_ack對ack進行慢速路徑處理;進行窗口,時間戳選項,MSS最大報文段,PMTU路徑發現等初始化或更新;上述流程完畢后調用tcp_finish_connect完成連接建立,將連接狀態更新為TCP_ESTABLISHED,若需要則開啟保活定時器,以及對是否走快速路徑做判斷和標記;連接建立成功會進行一些喚醒操作;最后進行ACK模式是否切換的判斷和操作,決定如何回復ACK;
1 static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, 2 const struct tcphdr *th) 3 { 4 struct inet_connection_sock *icsk = inet_csk(sk); 5 struct tcp_sock *tp = tcp_sk(sk); 6 struct tcp_fastopen_cookie foc = { .len = -1 }; 7 int saved_clamp = tp->rx_opt.mss_clamp; 8 bool fastopen_fail; 9 10 /* 解析tcp選項 */ 11 tcp_parse_options(skb, &tp->rx_opt, 0, &foc); 12 13 /* 有時間戳,有回顯,記錄回顯 */ 14 if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr) 15 tp->rx_opt.rcv_tsecr -= tp->tsoffset; 16 17 if (th->ack) { 18 /* rfc793: 19 * "If the state is SYN-SENT then 20 * first check the ACK bit 21 * If the ACK bit is set 22 * If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send 23 * a reset (unless the RST bit is set, if so drop 24 * the segment and return)" 25 */ 26 /* ack <= una || ack > nxt,ack確認序號錯誤 */ 27 if (!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_una) || 28 after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) 29 goto reset_and_undo; 30 31 /* 對端時間戳不合法 */ 32 if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr && 33 !between(tp->rx_opt.rcv_tsecr, tp->retrans_stamp, 34 tcp_time_stamp)) { 35 NET_INC_STATS(sock_net(sk), 36 LINUX_MIB_PAWSACTIVEREJECTED); 37 goto reset_and_undo; 38 } 39 40 /* Now ACK is acceptable. 41 * 42 * "If the RST bit is set 43 * If the ACK was acceptable then signal the user "error: 44 * connection reset", drop the segment, enter CLOSED state, 45 * delete TCB, and return." 46 */ 47 48 /* 設置了rst標志 */ 49 if (th->rst) { 50 tcp_reset(sk); 51 goto discard; 52 } 53 54 /* rfc793: 55 * "fifth, if neither of the SYN or RST bits is set then 56 * drop the segment and return." 57 * 58 * See note below! 59 * --ANK(990513) 60 */ 61 /* 未設置syn標志 */ 62 if (!th->syn) 63 goto discard_and_undo; 64 65 /* rfc793: 66 * "If the SYN bit is on ... 67 * are acceptable then ... 68 * (our SYN has been ACKed), change the connection 69 * state to ESTABLISHED..." 70 */ 71 /* ecn標記 */ 72 tcp_ecn_rcv_synack(tp, th); 73 74 /* 記錄窗口更新時數據包序號 */ 75 tcp_init_wl(tp, TCP_SKB_CB(skb)->seq); 76 77 //輸入ack處理,使用慢速路徑,可能會更新發送窗口 78 tcp_ack(sk, skb, FLAG_SLOWPATH); 79 80 /* Ok.. it's good. Set up sequence numbers and 81 * move to established. 82 */ 83 84 /* 更新下一個要接收的序號 */ 85 tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1; 86 /* 更新窗口左邊,即窗口中最小的序號 */ 87 tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1; 88 89 /* RFC1323: The window in SYN & SYN/ACK segments is 90 * never scaled. 91 */ 92 /* 獲取發送窗口 */ 93 tp->snd_wnd = ntohs(th->window); 94 95 /* 沒有窗口擴大因子 */ 96 if (!tp->rx_opt.wscale_ok) { 97 /* 設置為0 */ 98 tp->rx_opt.snd_wscale = tp->rx_opt.rcv_wscale = 0; 99 /* 設置最大值 */ 100 tp->window_clamp = min(tp->window_clamp, 65535U); 101 } 102 103 /* 有時間戳選項 */ 104 if (tp->rx_opt.saw_tstamp) { 105 /* 在syn中有時間戳選項 */ 106 tp->rx_opt.tstamp_ok = 1; 107 108 /* tcp首部需要增加時間戳長度 */ 109 tp->tcp_header_len = 110 sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED; 111 112 /* mss需要減去時間戳長度 */ 113 tp->advmss -= TCPOLEN_TSTAMP_ALIGNED; 114 115 /* 設置回顯時間戳 */ 116 tcp_store_ts_recent(tp); 117 } else { 118 /* 記錄tcp首部長度 */ 119 tp->tcp_header_len = sizeof(struct tcphdr); 120 } 121 122 /* 有sack選項,開啟了fack算法,則打標記 */ 123 if (tcp_is_sack(tp) && sysctl_tcp_fack) 124 tcp_enable_fack(tp); 125 126 /* MTU探測相關初始化 */ 127 tcp_mtup_init(sk); 128 /* 計算mss */ 129 tcp_sync_mss(sk, icsk->icsk_pmtu_cookie); 130 /* 初始化rcv_mss */ 131 tcp_initialize_rcv_mss(sk); 132 133 /* Remember, tcp_poll() does not lock socket! 134 * Change state from SYN-SENT only after copied_seq 135 * is initialized. */ 136 137 /* 記錄用戶空間待讀取的序號 */ 138 tp->copied_seq = tp->rcv_nxt; 139 140 smp_mb(); 141 142 /* 連接建立完成的狀態改變和相關初始化 */ 143 tcp_finish_connect(sk, skb); 144 145 /* fastopen處理 */ 146 fastopen_fail = (tp->syn_fastopen || tp->syn_data) && 147 tcp_rcv_fastopen_synack(sk, skb, &foc); 148 149 /* 連接未在關閉態,則進行一些喚醒操作 */ 150 if (!sock_flag(sk, SOCK_DEAD)) { 151 sk->sk_state_change(sk); 152 sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT); 153 } 154 if (fastopen_fail) 155 return -1; 156 157 /* 有寫數據請求|| 收包accept || 延遲ack */ 158 if (sk->sk_write_pending || 159 icsk->icsk_accept_queue.rskq_defer_accept || 160 icsk->icsk_ack.pingpong) { 161 /* Save one ACK. Data will be ready after 162 * several ticks, if write_pending is set. 163 * 164 * It may be deleted, but with this feature tcpdumps 165 * look so _wonderfully_ clever, that I was not able 166 * to stand against the temptation 8) --ANK 167 */ 168 /* 標志ack調度 */ 169 inet_csk_schedule_ack(sk); 170 /* 進入快速ack模式 */ 171 tcp_enter_quickack_mode(sk); 172 /* 設置延遲ack定時器 */ 173 inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, 174 TCP_DELACK_MAX, TCP_RTO_MAX); 175 176 discard: 177 tcp_drop(sk, skb); 178 return 0; 179 } else { 180 /* 回復ack */ 181 tcp_send_ack(sk); 182 } 183 return -1; 184 } 185 186 /* 省略了rst以及syn狀態的代碼 */ 187 }
以上流程中比較重要的兩個函數為tcp_ack與tcp_finish_connect;
tcp_ack函數因為涉及內容較多,此處不做分析,后續單獨分析后在補充鏈接;
tcp_finish_connect函數進行連接建立完成的處理,函數將連接狀態更改為TCP_ESTABLISHED,檢查並重建路由項,初始化擁塞控制,若需要則開啟保活定時器,以及對是否走快速路徑做判斷和標記
1 /* tcp連接建立完成 */ 2 void tcp_finish_connect(struct sock *sk, struct sk_buff *skb) 3 { 4 struct tcp_sock *tp = tcp_sk(sk); 5 struct inet_connection_sock *icsk = inet_csk(sk); 6 7 /* 設置為已連接狀態 */ 8 tcp_set_state(sk, TCP_ESTABLISHED); 9 /* 記錄最后一次收到包的時間戳 */ 10 icsk->icsk_ack.lrcvtime = tcp_time_stamp; 11 12 if (skb) { 13 /* 設置接收路由緩存 */ 14 icsk->icsk_af_ops->sk_rx_dst_set(sk, skb); 15 security_inet_conn_established(sk, skb); 16 } 17 18 /* Make sure socket is routed, for correct metrics. */ 19 /* 檢查或重建路由 */ 20 icsk->icsk_af_ops->rebuild_header(sk); 21 22 /* 初始化度量值 */ 23 tcp_init_metrics(sk); 24 25 /* 初始化擁塞控制 */ 26 tcp_init_congestion_control(sk); 27 28 /* Prevent spurious tcp_cwnd_restart() on first data 29 * packet. 30 */ 31 /* 記錄最后一次發送數據包的時間 */ 32 tp->lsndtime = tcp_time_stamp; 33 34 tcp_init_buffer_space(sk); 35 36 /* 開啟了保活,則打開保活定時器 */ 37 if (sock_flag(sk, SOCK_KEEPOPEN)) 38 inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tp)); 39 40 /* 設置預測標志,判斷快慢路徑的條件之一 */ 41 if (!tp->rx_opt.snd_wscale) 42 __tcp_fast_path_on(tp, tp->snd_wnd); 43 else 44 tp->pred_flags = 0; 45 46 }