TCP带外数据

传输层协议使用带外数据(out-of-band,OOB)来发送一些重要的数据,如果通信一方有重要的数据需要通知对方时,协议能够将这些数据快速地发送到对方.为了发送这些数据,协议一般不使用与普通数据相同的通道,而是使用另外的通道.linux系统的套接字机制支持低层协议发送和接受带外数据.但是TCP协议没有真正意义上的带外数据.为了发送重要协议,TCP提供了一种称为紧急模式(urgentmode)的机制.TCP协议在数据段中设置URG位,表示进入紧急模式.接收方可以对紧急模式采取特殊的处理.很容易看出来,这种方式数据不容易被阻塞,可以通过在我们的服务器端程序里面捕捉SIGURG信号来及时接受数据或者使用带OOB标志的recv函数来接受.

定义带外数据

想 像一下在银行人们排起队等待处理他们的帐单。在这个队伍中每个人最后都会移到前面由出纳员进行服务。现在想像一下一个走入银行,越过整个队伍,然后用枪抵 住出纳员。这个就可以看作为带外数据。这个强盗越过整个队伍,是因为这把枪给了他凌驾于众人的权力。出纳员也会集中注意力于这个强盗身上,因为他知道当前 的形势是很紧急的。

相应的,一个连接的流式套接口上的带外数据的工作原理也与此类似。通常情况下,数据由连接的一端流到另一端,并且认为 数据的所有字节都是精确排序的。晚写入的字节绝不会早于先写入的字节到达。然而套接口API概念性的提供了一些实用程序,从而可以使得一串数据无阻的先于 通常的数据到达接收端。这就是所谓的发送带外数据。

从技术上来说,一个TCP流不可以发送带外数据。而他所支持的只是一个概念性的紧急数据,这些紧急数据作为带外数据映射到套接口API。这就带来了许多限制,这些我们会在后面进行讨论。
尽管我们可以立刻享受到在银行中越过整个队伍的利益,但是我们也会认识到使用枪来达到这样的目的是反社会的行为。一个TCP流通常希望以完美的队列来发送数据字节,那么乱序的发送数据就似乎与流的概念相违背。那么为什么要提供带外数据的套接口方法呢?

也 许我们已经意识到了,有时数据会以一定的方式变得紧急。一个流套接口会有一个大量的数据队列等待发送到网络。在远程端点,也会有大量已接收的,却还没有被 程序读取的数据。如果发送客户端程序由于一些原因需要取消已经写入服务器的请求,那么他就需要向服务器紧急发送一个标识取消的请求。如果向远程服务器发送 取消请求失败,那么就会无谓的浪费服务器的资源。
使 用带外数据的实际程序例子就是telnet,rlogin,ftp命令。前两个程序会将中止字符作为紧急数据发送到远程端。这会允许远程端冲洗所有未处理 的输入,并且丢弃所有未发送的终端输出。这会快速中断一个向我们屏幕发送大量数据的运行进程。ftp命令使用带外数据来中断一个文件的传输。

套接口与带外数据
重新强调套接口接口本身并不是限制因素是很重要的。带外数据的概念实际上映射到 TCP/IP通信的紧急数据模式。在今天,TCP流对于网络是很重要的,而在这一章我们仅专注于带外数据适应于TCP紧急数据的套接口使用。

实现上的变化

很不幸,TCP的实现在紧急数据就如何处理上有两种不同的解释。这些区别我们将会本章的后面进行详细的讨论。这些不同的解释是:

TCP紧急指针的RFC793解释
TCP紧急指针的BSD解释

现 在已经出现了平分的状态,因为原始的TCP规格允许两种解释。从而,一个"主机需要"的RFC标识正确的解释。然而,大多数的实现都基于BSD源码,而在 今天BSD方法还是一个通用的用法。从支持两种解释的角度而言,Linux处于分裂的状态。然而,Linux默认使用BSD解释。
现在我们稍做停顿,来检测一个我们Linux系统的当前设置。这决了我们这一章的例子是否可以产生同样的结果。

$ cat /proc/sys/net/ipv4/tcp_stdurg
0
$

这里显示的输出为0。这表示当前起作的为BSD解释。如果我们得到其他的输出结果(例如1),那么如果我们希望得到也本章的例子相同的结果,我们应将其改为0。

下面列出了tcp_stdurg设置可能的取值。tcp_stdurg值可以在Shell脚本中进行查询和设置,包括启动与关闭脚本。

/proc/sys/net/ipv4_stdurg的设置值:
0   BSD解释(Linux默认)
1   RFC793解释

如果我们需要将其设置改为0,我们需要root权限,然后输入下面的命令:
# echo 0 >/proc/sys/net/ipv4/tcp_stdurg
#

进行双重检测总是很明知的,所以在改变以后再列出其值来确定改变是否为内核所接受。我们也可以在上面的例子中使用cat命令来显示0值。

编写带外数据

一个write调用将会写入一个我们已习惯的带内数据。相应的,必须使用一个新的函数来写入带外数据。为了这个目的,在这里列出send函数地原型:
#include <sys/types.h>
#include <sys/socket.h>
int send(int s, const void *msg, int len, unsigned int flags);

这个函数需要四个参数,分别为:
1 要写入的套接口s
2 存放要写入的消息的缓冲地址msg
3 消息长度(len)
4 发送选项flags

send函数与write函数相类似,所不同的只是提供了额外的flags参数。这是实际的部分。send函数返回写入的字节数,如果发生错误则会返回-1,检测errno可以得到错误原因。
要发送带外数据,与write调用相似,使用前三个参数。如果我们为flags参数指定了C语言宏MSG_OOB,则数据是作为带外数据发送的,而不是通常的带内数据,如下面的例子代码:

char buf[64]; /* Data */
int len;      /* Bytes */
int s;        /* Socket */
. . .
send(s,buf,len,MSG_OOB);

如果所提供的flags参数没有MSG_OOB位,那么数据是作为通常数据写入的。这就允许我们使用同一个函数同时发送带内数据与带外数据。我们只需要简单的在程序控制中改变flags参数值来达到这个目的。

读取带外数据

带外数据可以用两种不同的方法进行读取:
单独读取带外数据
与带内数据混合读取

为了与通常数据流分开单独读取带外数据,我们需要使用recv函数。如果我们猜想recv函数与read函数相似,只是有一个额外的flags参数,那么我们的猜想是正确的。这个函数的原型如下:

#include <sys/types.h>
#include <sys/socket.h>
int recv(int s, void *buf, int len, unsigned int flags);

recv函数接受四参数,分别为:
1 要从中接收数据的套接口s(带内数据或带外数据)
2 要放置所接收的数据的缓冲区地址buf
3 接收缓冲区的最大长度
4 调用所需的flags参数

正如我们所看到的,recv函数是与send函数调用相对应的函数。为要接收带外数据,在flags参数中指定C宏MSG_OOB。没有MSG_OOB标志位,recv函数所接收的为通常的带内数据,就像通常的read调用一样。

recv函数返回所接收到的字节数,如果出错则返回-1,检测errno可以得到错误原因。

下面的代码例子演示了如何读取带外数据:
char buf[128];   /* Buffer */
int n;      /* No. of bytes */
int s;             /* Socket */
int len;         /* Max bytes */
. . .
n = recv(s,buf,len,MSG_OOB);

尽管指出带外数据可以与通常数据相混合还为时尚早,但是我们会在后面进行相关的讨论。

理解SIGURG信号

当带外数所到在时,接收进程需要收到通知。如果需要与通常数据流分开读取时更是如此。这样做的一个方法就是当带外数据到达时,使Linux内核向我们的进程发送一个SIGURG信号。

使用SIGURG信号通知需要两个先决条件:
我们必须拥有套接口
我们必须为SIGURG创建一个信号处理器

要接收SIGURG信号,我们的进程必须为套接口的所有者。要建立这样的拥有关系,我们可以使用fcntl函数。其函数原型如下:

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, long arg);

函数参数如下:
1 要在其上执行控制函数的文件描述符fd(或是套接口)
2 要执行的控制函数cmd
3 要设置的值arg

函数的返回值依赖于fcntl所执行的控制函数。对于课外阅读感兴趣的读者,fcntl的Linux man手册页详细的描述了cmd的F_SETOWN操作。

要将我们的进程创建为套接口的所有者,接收程序需要使用下面的代码:

int z; /* Status */
int s; /* Socket */
z = fcntl(s,F_SETOWN,getpid());
if ( z == -1 ) {
    perror("fcntl(2)");
    exit(1);
}

F_SETOWN操作会使得fcntl函数成功时返回0,失败时返回-1。

另外一个先决条件是程序必须准备好接收SIGURG信号,这是通过为信号创建一个信号处理器来做到的。我们很快就会看到这样的一个例子。

接收SIGURG信号

移开了这些烦琐的工作以后,现在我们可以来探索有趣的带外数据的概念了。下面所列的程序代码就是我们用来接收数据和当带外数据到达时处理带外数据的程序。他设计使用BSD解释来处理带外数据,而这也正是Linux的默认情况。
/*
* oobrec.c
*
* Example OOB receiver:
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>

extern void bail(char *on_what);
extern int BindAccept(char *addr);

static int s = -1;   /* Socket */

/*
* SIGURG signal handler:
*/
static void sigurg(int signo)
{
   int n;
   char buf[256];

n = recv(s,buf,sizeof buf,MSG_OOB);
   if(n<0)
       bail("recv(2)");

buf[n] = 0;
   printf("URG ‘‘%s‘‘ (%d) \n",buf,n);

signal(SIGURG,sigurg);
}

int main(int argc,char **argv)
{
   int z;   /* Status */
   char buf[256];

/*
   * Use a server address from the command
   * line,if one has been provided.
   * Otherwise,this program will default
   * to using the arbitrary address
   * 127.0.0.1:
   */
   s = BindAccept(argc >=2 ?argv[1] :"127.0.0.1:9011");

/*
   * Establish owership:
   */
   z = fcntl(s,F_SETOWN,getpid());
   if(z==-1)
       bail("fcntl(2)");

/*
   * Catch SIGURG:
   */
   signal(SIGURG,sigurg);

for(;;)
   {
       z = recv(s,buf,sizeof buf,0);
       if(z==-1)
           bail("recv(2)");
       if(z==0)
           break;
       buf[z] = 0;

printf("recv ‘‘%s‘‘ (%d) \n",buf,z);
   }

close(s);
   return 0;
}

然而,在我们将接收程序投入使用之前,我们还需要一个发送程序。

发送带外数据

下面列出的程序演示了一个简短的发送程序,他只可以传输一些小的字符串,然后停止发送带外数据。这个程序为了在接收端管理传送块使用了许多的sleep(3)调用。
/*
* oobsend.c
*
* Example OOB sender:
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>

extern void bail(char *on_what);
extern int Connect(char *addr);

/*
* Send in-band data:
*/
static void iband(int s,char *str)
{
   int z;

z = send(s,str,strlen(str),0);
   if(z==-1)
       bail("send(2)");

printf("ib: ‘‘%s‘‘ (%d) \n",str,z);
}

/*
* Send out-of-band data:
*/
static void oband(int s,char *str)
{
   int z;

z = send(s,str,strlen(str),MSG_OOB);
   if(z==-1)
       bail("send(2)");

printf("OOB ‘‘%s‘‘ (%d)\n",str,z);
}

int main(int argc,char **argv)
{
   int s = -1;

s = Connect(argc >=2
           ? argv[1]
           : "127.0.0.1:9011");

iband(s,"In the beginning");
   sleep(1);

iband(s,"Linus begat Linux,");
   sleep(1);

iband(s,"and the Penguins");
   sleep(1);

oband(s,"rejoiced");
   sleep(1);

iband(s,"exceedingly.");
   close(s);

return 0;
}

编译程序:
$ make oobrecv oobsend
gcc -c -D_GNU_SOURCE -Wall -Wreturn-type -g oobrecv.c
gcc -c -D_GNU_SOURCE -Wall -Wreturn-type -g mkaddr.c
gcc -c -D_GNU_SOURCE -Wall -Wreturn-type -g bindacpt.c
gcc oobrecv.o mkaddr.o bindacpt.o -o oobrecv
gcc -c -D_GNU_SOURCE -Wall -Wreturn-type -g oobsend.c
gcc oobsend.o mkaddr.o bindacpt.o -o oobsend
$

在编译完成以后,我们得到两个可执行程序:
oobrecv 是接收程序(一个服务器)
oobsend 是发送程序(一个客户端)

现在我们已经准备好来调用这两个程序了。

测试oobrecv与oobsend程序

最好是在两个不同的终端会话上运行这两个程序。使用两个不同的xterm窗口,或是两个不同的终端会话。首先在第一个终端会话中启动接收程序:
$ ./oobrecv

如果我们希望指定我们的以太网地址而不是使用默认的回环地址,那么这两个程序都接收一个地址与端口号对。例如,下面的将会工作在一个NIC卡地址为192.168.0.1的系统上:
$ ./oobrecv 192.168.0.1:9023

这会启动服务器在192.168.0.1的9023端口上监听。然而,为了演示,我们可以不指定参数来运行这个程序。

现在在第二个终端会话中启动发送程序:
$ ./oobsend
ib: ‘‘In the beginning‘‘ (16)
ib: ‘‘Linus begat Linux,‘‘ (18)
ib: ‘‘and the Penguins‘‘ (16)
OOB ‘‘rejoiced‘‘ (8)
ib: ‘‘exceedingly.‘‘ (12)
$

以ib:开始的行表明写入的带内数据。以OOB开始的行表明‘‘rejoiced‘‘是作为带外数据写入套接口的。

如果我们可以同时观察两个终端,我们就会发现接收程序报告数据稍晚于发送程序发送数据。其会话输出类似于下面的样子:
$ ./oobrecv
rcv ‘‘In the beginning‘‘ (16)
rcv ‘‘Linus begat Linux,‘‘ (18)
rcv ‘‘and the Penguins‘‘ (16)
URG ‘‘d‘‘ (1)
rcv ‘‘rejoice‘‘ (7)
rcv ‘‘exceedingly.‘‘ (12)
$

在这个终端会话中显示的以rcv开始的行表明接收到的通常的带内数据。以URG开始的行表明接收到SIGURG信号,并且信号处理程序被调用。在信号处理器中,紧急数据被读取并报告。我们应注意到一个很奇怪的事情--只有d字节被作为带外数据接收。为什么是这样?

时间: 2024-10-27 13:58:59

TCP带外数据的相关文章

TCP带外数据学习总结(概念,发送接收过程,数据到达检测,代码实现)

最近在学习<Linux高性能服务器编程> 这本书,书中零零散散的讲了TCP带外数据的一些知识,在这里把这些知识总结以下,方便自己,也方便他人. 本文主要分为以下四个方面总结,分别为 TCP带外数据的概念,如何发送和接收带外数据,怎么检测带外数据的到达,最后介绍相关函数以及代码实现. 第一部分: TCP带外数据的概念 有很多传输层此协议都具有带外数据(OUT Of Band) 的概念,其作用是迅速通告通信的另一方本段发生的重要事件.带外数据具有比普通数据更高的优先级,理论上应该被立即发送和立即接

TCP带外数据(URG,MSG_OOB)

前言: blog原文地址:http://blog.csdn.net/ordeder/article/details/43243425 本文系读书笔记,主要参考材料:http://wenku.baidu.com/view/f04a4dff9e31433239689341.html TCP的带外数据: 头部标志: URG位,紧急指针. 数据包中:一个紧急指针只指向一个字节的带外数据的后已字节位置.紧急数据时插在正常数据流中进行传输.紧急指针用于指出带外数据字节在正常字节流中的位置. 问题:为何不直接

TCP带外数据读写

#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main( int argc, char* argv[] ) {

TCP带外数据测试

带外数据的应用情况 如果发送客户端程序由于一些原因需要取消已经写入服务器的请求,那么他就需要向服务器紧急发送一个标识取消的请求. 使用带外数据的实际程序例子就是telnet,rlogin,ftp命令. 前两个程序(telnet和rlogin)会将中止字符作为紧急数据发送到远程端.这会允许远程端冲洗所有未处理的输入,并且丢弃所有未发送的终端输出.这会快速中断一个向我们屏幕发送大量数据 的运行进程. ftp命令使用带外数据来中断一个文件的传输. TCP的带外数据(TCP紧急数据) TCP协议没有真正

《网络编程》带外数据

带外数据 有些传输层协议具有带外(Out Of Band,OOB)数据的概念,用于迅速通告对端本端所发生的重要事件.因此,带外数据比普通数据(也称为带内数据)有更高的优先级,它应该总是立即被发送,而不论发送缓冲区中是否有排队等待发送的普通数据或因流量控制而导致发送端的通告窗口大小为 0(即停止发送数据) .带外数据的传输可以使用一条独立的传输层连接,也可以映射到传输普通数据的连接中. UDP 没有实现带外数据传输,TCP 也没有真正的带外数据.只不过 TCP 利用其首部中的 紧急指针标志 和 紧

Linux下同时接受普通数据和带外数据

#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include

IT设备的救命稻草-如何正确构建OOB带外网络

现实生活中,无论是传统的大型园区网络,运营商.或是现今流行的数据中心.虚拟化等技术,往往归根结底都是大量的网络设备以及服务器堆叠而成.自然而然,当网络或者服务器因为软件故障或者人为操作失误的原因导致系统宕机后,如何第一时间登陆到故障设备,并快速恢复业务已经成为考验运维人员的一大难题. 其实,试想如果网络中存在一个完善的OOB带外网络,在故障发生时,网络控制中心可通过此网络登录网络设备或者服务器的带外管理接口或者Console接口.从而第一时间获取故障信息并予以修正,或者收集log文件上报厂家.岂

mysql 带外注入

带外通道 有时候注入发现并没有回显,也不能利用时间盲注,那么就可以利用带外通道,也就是利用其他协议或者渠道,如http请求.DNS解析.SMB服务等将数据带出. payload SELECT LOAD_FILE(CONCAT('\\\\',( SELECT DATABASE() ),'.xx.xx\\x)); 其中的load_file的地址为一个远程文件,mysql在load_file()一个远程文件时会发送dns请求包去解析,所以可以带出数据,'\\data.xx.xx\x' ,xx.xx为自

删除带外键的表【foreign key constraint fails】报错

title: 删除带外键的表[foreign key constraint fails]报错 date: 2018-08-02 21:59:06 tags: 数据库 --- 遥想当时正在学hibernate的时候,刚好学到了一对多,多对多的关联操作.时间也正是刚好在那是有了一个项目,把各表的间的结构还理清,俗话说学到就要用到,就把这些表的结构都能配置级联关系的都把它配上.没想到就在这里给自己放了个小坑.前几天在一个帖子中看到别人说,尽量少配些ORM约束,数据库的外键约束什么的.当时还不以为然.没