TCP的URG标志和内核实现之三:接收的实现

大致的处理过程

TCP的接收流程:在tcp_v4_do_rcv中的相关处理(网卡收到报文触发)中,会首先通过tcp_check_urg设置tcp_sock的urg_data为TCP_URG_NOTYET(urgent point指向的可能不是本报文,而是后续报文或者前面收到的乱序报文),并保存最新的urgent data的sequence和对于的1 BYTE urgent data到tcp_sock的urg_data (如果之前的urgent data没有读取,就会被覆盖)。

用户接收流程:在tcp_recvmsg流程中,如果发现当前的skb的数据中有urgent data,首先拷贝urgent data之前的数据,然后tcp_recvmsg退出,提示用户来接收OOB数据;在用户下一次调用tcp_recvmsg来接收数据的时候,会跳过urgent data,并设置urgent data数据接收完成。

相关的数据结构和定义

tcp_sock结构:

1、 urg_data成员,其高8bit为urgent data的接收状态;其低8位为保存的1BYTE urgent数据。urgent data的接收状态对应的宏的含义描述:

#defineTCP_URG_VALID    0x0100/*urgent data已经读到了tcp_sock::urg_data*/

#defineTCP_URG_NOTYET 0x0200/*已经发现有urgent data,还没有读取到tcp_sock::urg_data*/

#defineTCP_URG_READ     0x0400/*urgent data已经被用户通过MSG_OOB读取了*/

2、 urg_seq成员,为当前的urgent data的sequence

流程详情

TCP的接收过程

在tcp_rcv_established的slow_path中

slow_path:
	if (len < (th->doff << 2) || tcp_checksum_complete_user(sk, skb))
		goto csum_error;
	/*
	 *	Standard slow path.
	 */
	if (!tcp_validate_incoming(sk, skb, th, 1))
		return 0;
step5:
	if (th->ack &&
	    tcp_ack(sk, skb, FLAG_SLOWPATH | FLAG_UPDATE_TS_RECENT) < 0)
		goto discard;
	tcp_rcv_rtt_measure_ts(sk, skb);
	/* 处理紧急数据. */
	tcp_urg(sk, skb, th);

也就是在报文的CRC验证和sequence验证完成后,就会通过tcp_urg来处理接收到的urgent data :


static void tcp_urg(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th)
{
	struct tcp_sock *tp = tcp_sk(sk);

	/*收到了urgent data,则检查和设置urg_data和urg_seq成员*/
	if (th->urg)
		tcp_check_urg(sk, th);

	/* Do we wait for any urgent data? - normally not...
	发现了有urgent data,但是还没有保存到tp->urg_data*/
	if (tp->urg_data == TCP_URG_NOTYET) {
		u32 ptr = tp->urg_seq - ntohl(th->seq) + (th->doff * 4) -
			  th->syn;

		/* Is the urgent pointer pointing into this packet? */
		if (ptr < skb->len) {
			u8 tmp;
			if (skb_copy_bits(skb, ptr, &tmp, 1))
				BUG();
			tp->urg_data = TCP_URG_VALID | tmp;
			if (!sock_flag(sk, SOCK_DEAD))
				sk->sk_data_ready(sk, 0);
		}
	}
}
检查和设置urg_data和urg_seq成员的处理函数tcp_check_urg的具体流程

static void tcp_check_urg(struct sock *sk, const struct tcphdr *th)
{
	struct tcp_sock *tp = tcp_sk(sk);
	u32 ptr = ntohs(th->urg_ptr);
	/*两种urgent point的解析方式:
	一是指向urgent data之后的第一个字节
	二是执行urgent data的结束字节(RFC1122)
	sysctl_tcp_stdurg被设置表示当前采用的是第二种模式
	不需要把urgent point -1来指向urgent data的结束字节*/
	if (ptr && !sysctl_tcp_stdurg)
		ptr--;
	ptr += ntohl(th->seq);

	/* Ignore urgent data that we've already seen and read. 
	如果copied_seq已经大于urgent point,那么对于从tcp_rcv_established
	来执行的,前面的tcp_validate_incoming已经拒绝了这种报文(
	接收窗口外),这里要处理的是哪种情形?*/
	if (after(tp->copied_seq, ptr))
		return;

	/* Do not replay urg ptr.
	 *
	 * NOTE: interesting situation not covered by specs.
	 * Misbehaving sender may send urg ptr, pointing to segment,
	 * which we already have in ofo queue. We are not able to fetch
	 * such data and will stay in TCP_URG_NOTYET until will be eaten
	 * by recvmsg(). Seems, we are not obliged to handle such wicked
	 * situations. But it is worth to think about possibility of some
	 * DoSes using some hypothetical application level deadlock.
	 */
	/*	这种情况什么时候发生?没搞明白*/
	if (before(ptr, tp->rcv_nxt))
		return;

	/* Do we already have a newer (or duplicate) urgent pointer? 
	如果当前已经进入urg数据读取模式,且urgent point不大于当前
	保存的值,那么之前已经开始了读取tp->urg_seq对应的
	urgent 数据,无需重复处理了*/
	if (tp->urg_data && !after(ptr, tp->urg_seq))
		return;

	/* Tell the world about our new urgent pointer.*/
	sk_send_sigurg(sk);

	/* We may be adding urgent data when the last byte read was
	 * urgent. To do this requires some care. We cannot just ignore
	 * tp->copied_seq since we would read the last urgent byte again
	 * as data, nor can we alter copied_seq until this data arrives
	 * or we break the semantics of SIOCATMARK (and thus sockatmark())
	 *
	 * NOTE. Double Dutch. Rendering to plain English: author of comment
	 * above did something sort of 	send("A", MSG_OOB); send("B", MSG_OOB);
	 * and expect that both A and B disappear from stream. This is _wrong_.
	 * Though this happens in BSD with high probability, this is occasional.
	 * Any application relying on this is buggy. Note also, that fix "works"
	 * only in this artificial test. Insert some normal data between A and B and we will
	 * decline of BSD again. Verdict: it is better to remove to trap
	 * buggy users.
	 */
	 /*用户下一次要读取的数据就是用户还没有读取的urgent数据
	且当前存在新的用户未读取数据*/
	if (tp->urg_seq == tp->copied_seq && tp->urg_data &&
	    !sock_flag(sk, SOCK_URGINLINE) && tp->copied_seq != tp->rcv_nxt) {
		struct sk_buff *skb = skb_peek(&sk->sk_receive_queue);
		tp->copied_seq++;
		if (skb && !before(tp->copied_seq, TCP_SKB_CB(skb)->end_seq)) {
			__skb_unlink(skb, &sk->sk_receive_queue);
			__kfree_skb(skb);
		}
	}

	tp->urg_data = TCP_URG_NOTYET;
	tp->urg_seq = ptr;

	/* Disable header prediction. */
	tp->pred_flags = 0;
}

用户接收数据接口

用户接收URG数据的接口

在用户接收数据的tcp_recvmsg函数中,如果用户通过MSG_OOB来接收数据,会进入tcp_recv_urg处理

static int tcp_recv_urg(struct sock *sk, struct msghdr *msg, int len, int flags)
{
	struct tcp_sock *tp = tcp_sk(sk);

	/* No URG data to read. 
	用户已经读取过了*/
	if (sock_flag(sk, SOCK_URGINLINE) || !tp->urg_data ||
	    tp->urg_data == TCP_URG_READ)
		return -EINVAL;	/* Yes this is right ! */

	if (sk->sk_state == TCP_CLOSE && !sock_flag(sk, SOCK_DONE))
		return -ENOTCONN;
	/*当前的tp->urg_data为合法的数据,可以读取*/
	if (tp->urg_data & TCP_URG_VALID) {
		int err = 0;
		char c = tp->urg_data;
		/*标识urgent data已读*/
		if (!(flags & MSG_PEEK))
			tp->urg_data = TCP_URG_READ;

		/* Read urgent data. */
		msg->msg_flags |= MSG_OOB;

		if (len > 0) {
			if (!(flags & MSG_TRUNC))
				err = memcpy_toiovec(msg->msg_iov, &c, 1);
			len = 1;
		} else
			msg->msg_flags |= MSG_TRUNC;

		return err ? -EFAULT : len;
	}

	if (sk->sk_state == TCP_CLOSE || (sk->sk_shutdown & RCV_SHUTDOWN))
		return 0;

	/* Fixed the recv(..., MSG_OOB) behaviour.  BSD docs and
	 * the available implementations agree in this case:
	 * this call should never block, independent of the
	 * blocking state of the socket.
	 * Mike 
	 */
	return -EAGAIN;
}

用户接收普通数据的接口中的相关处理

在用户接收数据的tcp_recvmsg函数中,在查找到待拷贝的skb后,首先拷贝urgent data数据前的数据,然后退出接收过程,在用户下一次执行tcp_recvmsg的时候跳过urgent data,设置urgent data读取结束

查找到准备拷贝的skb后的处理:

found_ok_skb:
/* Ok so how much can we use? */
used = skb->len - offset;
if (len < used)
	used = len;

/* 当前有urg_data数据*/
if (tp->urg_data) {
	u32 urg_offset = tp->urg_seq - *seq;
	/*urgent data在当前待拷贝的数据范围内*/
	if (urg_offset < used) {
		if (!urg_offset) {/*待拷贝的数据就是urgent data,跨过该urgent data,
		只给用户读取后面的数据*/
			if (!sock_flag(sk, SOCK_URGINLINE)) {
				++*seq;
				urg_hole++;
				offset++;
				used--;
				if (!used)
					goto skip_copy;
			}
		} 
		} else/*指定只拷贝urgent data数据之前的,完成后在下一次循环
		开始的位置,会退出循环,返回用户;下一次用户调用tcp_recvmsg
		就进入到上面的分支了*/
			used = urg_offset;
	}
} 
skip_copy:
		/*用户读取的数据跨过了urgent point,设置读取结束
		开启fast path*/
		if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) {
			tp->urg_data = 0;
			tcp_fast_path_check(sk);
		}
		if (used + offset < skb->len)
			continue;

在接收完urgent data数据前的所有数据之后, tcp_recvmsg的以下代码片段得到执行,这段代码退出当前接收过程,提示用户有urgent data数据到来,需要用MSG_OOB来接收
if (tp->urg_data && tp->urg_seq == *seq) {
	if (copied)
		break;
	if (signal_pending(current)) {
		copied = timeo ? sock_intr_errno(timeo) : -EAGAIN;
		break;
	}
}

后记

TCP的urg数据,由于定义和实现上的混乱,当前已经不建议使用,但是为了兼容之前已经已经存在的实现,该机制会长期在内核中存在,如果不了解该机制及其内核行为,有可能就很难解释一些奇怪的问题:比如某段代码不小心地造成send接口事实上设置了MSG_OOB,就会造成接收端少了一个BYTE。














你可能感兴趣的:(TCP协议和linux实现)