上一篇介绍了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);
}