TCP傳送函式tcp_transmit_skb
上一篇介紹了TCP協議層和套接字層的介面tcp_sendmsg函式是將使用者地址空間資料複製到核心地址空間,接下來的工作是交給tcp_transmit_skb函式向IP層傳送資料包,tcp_transmit_skb傳送的資料包有
(1)重傳資料包tcp_retransmit_skb。
(2)探測路由最大傳送單元資料包。
(3)傳送復位連線資料包
(4)傳送連線請求資料包
(5)傳送回答ACK資料包
(6)視窗探測資料包
應用層、TCP層、IP層之間介面關係如下圖:
通過上圖我們知道無論是應用層引數的資料包、TCP協議連線管理的資料包、重傳資料包都是通過tcp_transmit_skb最後呼叫ip_queue_xmit傳送給IP層,下面分析tcp_transmit_skb函式。
1、初始化區域性變數
tcp_transmit_skb傳送TCP資料段,就要初始化TCP協議頭等資料結構,主要如下:
inet:初始化AF_INET地址族套接字struct inet_sock *inet。
tp:TCP選項結構,包含TCP配置和連線資訊。
tch:TCP控制緩衝區,用於構造TCP協議頭
th:TCP協議頭資料結構。
icsk:inet連線控制套接字。
static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, gfp_t gfp_mask) { //連線控制套接字 const struct inet_connection_sock *icsk = inet_csk(sk); //AF_INET地址族套接字 struct inet_sock *inet; //TCP選項結構 struct tcp_sock *tp; //TCP控制緩衝區 struct tcp_skb_cb *tcb; struct tcp_out_options opts; unsigned tcp_options_size, tcp_header_size; struct tcp_md5sig_key *md5; struct tcphdr *th; int err; ... }
2、克隆Socket Buffer
檢視clone_it是否要克隆Socket Buffer,應用Socket Buffer可能正被其他程序使用,就要克隆一個份。
... //如果還有其他程序使用Socket Buffer //就要克隆Socket Buffer if (likely(clone_it)) { if (unlikely(skb_cloned(skb))) skb = pskb_copy(skb, gfp_mask); else skb = skb_clone(skb, gfp_mask); if (unlikely(!skb)) return -ENOBUFS; } ...
3、構建TCP協議選項
檢視資料包是否是一個SYN包(TCPCB_FLAG_SYN)如果是就呼叫tcp_syn_options構建SYN資料段的選項資料,包括時間戳、視窗大小、選擇回答(SACK),否則呼叫tcp_establishe_options構架常規TCP選項,並返回TCP選項長度。TCP協議頭總的長度等於TCP選項長度+TCP協議頭長度。
...
//獲取AF_INIT協議組套接字
inet = inet_sk(sk);
//TCP選項結構體
tp = tcp_sk(sk);
//TCP控制緩衝區協議頭
tcb = TCP_SKB_CB(skb);
memset(&opts, 0, sizeof(opts));
//是否是SYN請求資料包
if (unlikely(tcb->flags & TCPCB_FLAG_SYN))
//構建TCP選項包括時間戳、視窗大小、選擇回答SACK
tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
else
//構建常規TCP選項
tcp_options_size = tcp_established_options(sk, skb, &opts,
&md5);
//tCP頭部長度包括選擇長度+ TCP頭部
tcp_header_size = tcp_options_size + sizeof(struct tcphdr);
...
3、阻塞控制
確定網路上有多少資料包最好,大多數情況下是按照保守情況處理,網路上有多少資料包做好的詳細資訊是用收到的SACK的資訊來確認,tp->pachets_out確定傳送佇列中是否為空,阻塞控制計算方法:傳送佇列上的資料包+必須快速重傳資料包-留在網路上的資料包,如果等於0表示不會阻塞,這時傳送時間標誌。
...
//網路阻塞控制管理
//阻塞控制計算 傳送佇列資料包數-留在網路上的資料+ 重傳資料包
if (tcp_packets_in_flight(tp) == 0)
//設定傳送資料包事件標誌
tcp_ca_event(sk, CA_EVENT_TX_START);
...
阻塞控制計算方法:傳送佇列上的資料包+必須快速重傳資料包-留在網路上的資料包
static inline unsigned int tcp_packets_in_flight(const struct tcp_sock *tp)
{
return tp->packets_out - tcp_left_out(tp) + tp->retrans_out;
}
4、構建TCP協議頭
構建TCP協議頭主要的資料域:源埠、目的埠、資料段初始序列號,tcp_select_window計算視窗大小,如果是SYN請求包就不需要計算視窗大小。
...
/* Build TCP header and checksum it. */
//構建TCP協議頭
th = tcp_hdr(skb);
th->source = inet->inet_sport;
th->dest = inet->inet_dport;
th->seq = htonl(tcb->seq);
th->ack_seq = htonl(tp->rcv_nxt);
*(((__be16 *)th) + 6) = htons(((tcp_header_size >> 2) << 12) |
tcb->flags);
//SYN包不需要計算視窗
if (unlikely(tcb->flags & TCPCB_FLAG_SYN)) {
/* RFC1323: The window in SYN & SYN/ACK segments
* is never scaled.
*/
th->window = htons(min(tp->rcv_wnd, 65535U));
} else {
//計算視窗大小
th->window = htons(tcp_select_window(sk));
}
th->check = 0;
th->urg_ptr = 0;
...
5、傳送資料包
傳送資料包到IP層
...
//傳送資料包到IP層,
//函式指標實際指向ip_queue_ximit
err = icsk->icsk_af_ops->queue_xmit(skb);
if (likely(err <= 0))
return err;
...
6、傳送過程狀態機切換
如上圖傳送過程狀態機切換,通過這張圖可以直達TCP協議從連線建立到關閉的過程,FIN包後有兩個FIN_WAIT狀態,因為TCP連線上雙向全雙工的,當傳送一個FIN包進入FIN_WAIT1狀態,當收到對端的ACK進入FIN_WAIT2狀態此時不能再發送資料包了,只能接受對端的資料包,當收到對端的FIN包在傳送ACK給對端進入TIME_WAIT狀態,此時TCP套接字不會立即關閉,此時再去繫結這個埠就會提示此埠已經繫結,因為要保證對端收到ACK,假如對端沒有收到ACK就會再次傳送FIN包,有了超時時間就能再次收到對端的FIN然後回覆ACK,這個TIME_WAIT時間一般是2min。
tcp_transmit_skb:
static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,
gfp_t gfp_mask)
{
//連線控制套接字
const struct inet_connection_sock *icsk = inet_csk(sk);
//AF_INET地址族套接字
struct inet_sock *inet;
//TCP選項結構
struct tcp_sock *tp;
//TCP控制緩衝區
struct tcp_skb_cb *tcb;
struct tcp_out_options opts;
unsigned tcp_options_size, tcp_header_size;
struct tcp_md5sig_key *md5;
struct tcphdr *th;
int err;
BUG_ON(!skb || !tcp_skb_pcount(skb));
/* If congestion control is doing timestamping, we must
* take such a timestamp before we potentially clone/copy.
*/
if (icsk->icsk_ca_ops->flags & TCP_CONG_RTT_STAMP)
__net_timestamp(skb);
//如果還有其他程序使用Socket Buffer
//就要克隆Socket Buffer
if (likely(clone_it)) {
if (unlikely(skb_cloned(skb)))
skb = pskb_copy(skb, gfp_mask);
else
skb = skb_clone(skb, gfp_mask);
if (unlikely(!skb))
return -ENOBUFS;
}
//獲取AF_INIT協議組套接字
inet = inet_sk(sk);
//TCP選項結構體
tp = tcp_sk(sk);
//TCP控制緩衝區協議頭
tcb = TCP_SKB_CB(skb);
memset(&opts, 0, sizeof(opts));
//是否是SYN請求資料包
if (unlikely(tcb->flags & TCPCB_FLAG_SYN))
//構建TCP選項包括時間戳、視窗大小、選擇回答SACK
tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
else
//構建常規TCP選項
tcp_options_size = tcp_established_options(sk, skb, &opts,
&md5);
//tCP頭部長度包括選擇長度+ TCP頭部
tcp_header_size = tcp_options_size + sizeof(struct tcphdr);
//網路阻塞控制管理
//阻塞控制計算 傳送佇列資料包數-留在網路上的資料+ 重傳資料包
if (tcp_packets_in_flight(tp) == 0)
//設定傳送資料包事件標誌
tcp_ca_event(sk, CA_EVENT_TX_START);
//是指skb->data指標
skb_push(skb, tcp_header_size);
//設定ksb->transport_header指標
skb_reset_transport_header(skb);
//設定skb所屬套接字
skb_set_owner_w(skb, sk);
/* Build TCP header and checksum it. */
//構建TCP協議頭
th = tcp_hdr(skb);
th->source = inet->inet_sport;
th->dest = inet->inet_dport;
th->seq = htonl(tcb->seq);
th->ack_seq = htonl(tp->rcv_nxt);
*(((__be16 *)th) + 6) = htons(((tcp_header_size >> 2) << 12) |
tcb->flags);
//SYN包不需要計算視窗
if (unlikely(tcb->flags & TCPCB_FLAG_SYN)) {
/* RFC1323: The window in SYN & SYN/ACK segments
* is never scaled.
*/
th->window = htons(min(tp->rcv_wnd, 65535U));
} else {
//計算視窗大小
th->window = htons(tcp_select_window(sk));
}
th->check = 0;
th->urg_ptr = 0;
/* The urg_mode check is necessary during a below snd_una win probe */
if (unlikely(tcp_urg_mode(tp) && before(tcb->seq, tp->snd_up))) {
if (before(tp->snd_up, tcb->seq + 0x10000)) {
th->urg_ptr = htons(tp->snd_up - tcb->seq);
th->urg = 1;
} else if (after(tcb->seq + 0xFFFF, tp->snd_nxt)) {
th->urg_ptr = htons(0xFFFF);
th->urg = 1;
}
}
tcp_options_write((__be32 *)(th + 1), tp, &opts);
if (likely((tcb->flags & TCPCB_FLAG_SYN) == 0))
TCP_ECN_send(sk, skb, tcp_header_size);
#ifdef CONFIG_TCP_MD5SIG
/* Calculate the MD5 hash, as we have all we need now */
if (md5) {
sk_nocaps_add(sk, NETIF_F_GSO_MASK);
tp->af_specific->calc_md5_hash(opts.hash_location,
md5, sk, NULL, skb);
}
#endif
icsk->icsk_af_ops->send_check(sk, skb);
if (likely(tcb->flags & TCPCB_FLAG_ACK))
tcp_event_ack_sent(sk, tcp_skb_pcount(skb));
if (skb->len != tcp_header_size)
tcp_event_data_sent(tp, skb, sk);
if (after(tcb->end_seq, tp->snd_nxt) || tcb->seq == tcb->end_seq)
TCP_ADD_STATS(sock_net(sk), TCP_MIB_OUTSEGS,
tcp_skb_pcount(skb));
//傳送資料包到IP層,
//函式指標實際指向ip_queue_ximit
err = icsk->icsk_af_ops->queue_xmit(skb);
if (likely(err <= 0))
return err;
tcp_enter_cwr(sk, 1);
return net_xmit_eval(err);
}