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_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);
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;
}
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;
if (tp->urg_data && tp->urg_seq == *seq) {
if (copied)
break;
if (signal_pending(current)) {
copied = timeo ? sock_intr_errno(timeo) : -EAGAIN;
break;
}
}