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层_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发送过程状态机切换图

如上图发送过程状态机切换,通过这张图可以直达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);
}

猜你喜欢

转载自blog.csdn.net/City_of_skey/article/details/84723087
tcp