urg push
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
紧急数据的分歧对于RFC793和RFC1122都说法不一,如下图所示:
如上图所示,RFC1122认为:紧急数据序列号=SEQ+Urgent Pointer,RFC793则认为:紧急数据序列号=SEQ+Urgent Pointer-1。 目前使用RFC 1122 为准,但是内核里面都考虑了。。。。。。
无论是 RFC 793 还是 RFC 1122 都认为两点:(1)紧急数据不止1个字节;(2)紧急数据的最后一个字节由 Urgent Pointer 指定(如前文所述:RFC 793 的指定方法有点问题,RFC 1122 对其做了修正)。但是,TCP 的数据结构(报文头)中,只有1个字段 Urgent Pointer 指定了紧急数据的结尾,却再也没有其他字段以能指定紧急数据的起始位置或者长度(通过长度能间接推导出起始位置)。
- 第1种方案是认为该报文中,从第1个字节开始,到 Urgent Pointer 结束,这段数据通通都是紧急数据;
- 第2种方案则不再纠结,根本不在意 RFC 是这么说的,就是认为紧急数据只有1个字节;
业界普遍采用第2种方案,比如著名的 Linux 就是采用第2种方案。本文遵从 Linux,也采用第2种方案。下面我们对 Urgent 的定义做一个简单的总结吧:
(1)URG = 1,紧急数据才有意义,否则报文中的数据没有紧急数据;
(2)紧急数据只有1个字节。当 URG = 1 时,紧急数据位于报文中的数据的序号 = SEQ + Urgent Pointer。
/* This is the 'fast' part of urgent handling. */ static void tcp_urg(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th) { struct tcp_sock *tp = tcp_sk(sk); /* Check if we get a new urgent pointer - normally not. */ if (th->urg) tcp_check_urg(sk, th); /* Do we wait for any urgent data? - normally not... */ 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); } } } /* * This routine is only called when we have urgent data * signaled. Its the 'slow' part of tcp_urg. It could be * moved inline now as tcp_urg is only called from one * place. We handle URGent data wrong. We have to - as * BSD still doesn't use the correction from RFC961. * For 1003.1g we should support a new option TCP_STDURG to permit * either form (or just set the sysctl tcp_stdurg). 如果 sysctl_tcp_stdurg 为 0(即标准行为),表示紧急指针指向紧急数据的下一个字节。 如果 sysctl_tcp_stdurg 为 1,表示紧急指针指向紧急数据的最后一个字节。 */ 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); if (ptr && !sock_net(sk)->ipv4.sysctl_tcp_stdurg) ptr--; ptr += ntohl(th->seq); /* Ignore urgent data that we've already seen and read. */ 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? */ 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. */ 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; WRITE_ONCE(tp->urg_seq, ptr);//记录当前urg data的seq到urg_seq中 /* Disable header prediction. */ tp->pred_flags = 0; }
紧急数据在接收方的行为
紧急数据在接收方的行为,指的是 TCP 接收方收到包含紧急数据的报文后,它该如何将这个报文中的数据提交给应用层,如图所示:
上图中,我们假设 TCP 采用满泻提交模式(即使采用直流提交模式,TCP 对于紧急数据的提交方式也是一样的)。
T0时刻,接收方缓存中有数据“xyz”。T1时刻,接收方收到了“abcde”,其中“c”是紧急数据。此时接收方处理紧急数据的方案是:
(1)抢在其他所有数据之前(抢在本次接收的普通数据“abde”之前,也抢在已经存入接收缓存的普通数据“xyz”之前),提交给应用层
(2)而且是在第一时间提交,毫无停顿。
(3)更进一步,紧急数据(“c”)根本不会经过接收方缓存,而是直接提交给应用层。
(4)在紧急提交了紧急数据以后,对于本次接收的其他非紧急数据(“abde”),接收方按照正常流程处理:或者是满泻提交、或者是直流提交。
我们将(1)~(3)步骤所描述的提交方式称为紧急提交模式。
紧急数据在发送方的行为
在 TCP 的官方表达中,其把紧急数据称为带外数据(out-of-band,OOB),但是实际上TCP 的数据发送,根本就没有什么所谓的带外数据发送。带外数据指的是发送采用与普通数据不一样的(逻辑)通道。而 TCP 发送紧急数据与普通数据,采用的是同一个通道。
紧急数据在 TCP 的接收方的提交模式用大白话说,就是“插队”。而在发送方时,紧急数据则不是按“插队”的方式来进行发送处理的,而是按部就班的进行处理。抽象地讲,紧急数据的应用场景是:如果发送客户端程序由于一些原因需要取消已经写入服务器的请求,那么它就需要向服务器紧急发送一个标识取消的请求。使用紧急数据的实际程序例子有telnet、rlogin、ftp 等命令。前两个程序(telnet和rlogin)会将中止字符作为紧急数据发送到远程端。这会允许远程端冲洗所有未处理的输入,并且丢弃所有未发送的终端输出。这会快速中断一个向客户端屏幕发送大量数据的运行进程。ftp命令也会使用紧急数据来中断一个文件的传输。
之前介绍PUSH标签时说过其标签既可以由用户打上,也可以由TCP端打上。而URG 标签,只能由用户主动打上。也就是说,紧急数据只能由用户标识,TCP 不会自动标识某个数据是紧急数据。假设用户调用 TCP 接口发送一串字符:SEND(“abcdefgh”,URG = 1)。根据前文描述我们知道,TCP 的具体实现(比如 linux)仅仅认为紧急数据只有1个字节,所以用户如果调用了上述接口,TCP 会认为只有最后1个字符“h”是紧急数据。考虑一个极端一点的情况,假设滑动窗口的大小等于3,那么 TCP 会将“abcdefgh”分割成3个报文发送。这3个报文的 URG 标签即 Urgent Pointer如下图:
由上图可以看到,前两个报文的URG标签都被打上了,而且 Urgent Pointer 指向了未来。只有报文3算是真正完成了紧急数据的发送。出现这种每次都有URG标签得报文的原因,在于RFC定义了紧急数据的结尾,但没有定义紧急数据的开始/长度,使得TCP的实现者无所适从,要么认为其中一个字节为紧急数据,要么认为从第一个字节到 Urgent Pointer 所指定的字节这个区间全是紧急数据。TCP 的这种问题,忍一忍也能过去:得罪了 RFC,那就不伺候了,就认为只有1个字节是紧急数据;多发了几个无关的紧急报文(图5-104所描述的情形),那就多发了,唉咋地咋地。但是,TCP 还可能将紧急数据给发错了。如下图:
TCP 实际上是接纳了两个紧急数据发送的任务,t1与t2时刻都在攒大包,在 T3 时刻却用1个报文给发出去了,正如上图5-106所示,这个报文的 SEQ = 1、URG = 1、Urgent Pointer = 12,也就是说仅仅是“h”是紧急数据,而“o”变成了普通数据(非紧急数据)。也就是 TCP 在紧急数据发送层面的一个问题:如果上一个紧急数据还没发送,有可能会被下一个紧急数据给“冲刷/替代”掉_——而且用户并不知道他的紧急数据何时能被正确发送,何时会被“冲刷/替代”。
总结一下Urgent 有多少让人迷惑的点吧:
(1)Urgent Pointer 关于紧急数据的指示,RFC 793 定义错了,RFC 1122 给纠正了过来,但是这中间经历了8年,混乱的种子就此埋下;
(2)RFC 一方面信誓旦旦地说“紧急数据是一串数据”,但是它只定义了紧急数据的结尾指示(Urgent Pointer),却没有定义紧急数据的起始位置,造成了具体实现者无所适从,最后绝大部分实现者只能选择“紧急数据其实就是1个字节”;
(3)TCP 所发送的报文中,可能是 URG = 1,但是该报文并不包含紧急数据;
(4)如果上一个紧急数据还没发送,有可能会被下一个紧急数据给“冲刷/替代”掉——而且用户并不知道他的紧急数据何时能被正确发送,何时会被“冲刷/替代”;
Urgent 与 PUSH 的比较
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
2020-06-11 Linux 网络栈监控和调优:接收数据 转载