原始套接字-IP头包含选项
IP头包含选项
- 创建原始套接字之后,再打开IP_HDRINCL选项,即可在IP头中封装自己的协议,而不仅仅使用系统预定义的协议。一般可以使用这种方法发送原始UDP和TCP协议,而不是使用系统预定义的协议。一般的,可以使用这种方法来发送原始UDP和TCP数据(win xp sp2已经不支持原始tcp数据的发送了)。这里将介绍如何建立自己的UDP封包,一旦掌握了管理UDP头,即可掌握如何创建自己的协议头,或者封装在IP中的协议头。
- 对于IPV4来说,堆栈会检查IPV4头中的几个域,例如IPV4标识位就由堆栈设置,如果有必要堆栈将数据块分块。就是说,如果创建了一个原始IPV4封包,设置了IP_HDRINCL发送的封包比MTU大小大,堆栈将把数据分为多块。
- 当使用这个头包含选项之后,就需要自己在每个发送调用中填写IP头,以及任何封装在里面的协议头。
IP数据报格式
-
我们称网络层数据报为数据报,IP数据报包含IP头部分和文本部分。IP头有20字节的固定部分和一个长度可变的可选部分。IP头格式如下所示。
-
Version域,者四位指定了数据报的IP版本,对IPv4来说,此域值为4
-
IHL(IP Header Length的缩写),因为IP头的长度不是固定的所以 需要这四位IP数据报中数据部分的开始位置,大多数IP数据报不包含此选项,索引IP数据报有20个字节的头长度。
-
Type of service(服务器类型TOS)域:包含在IPV4头中,用来区分不同类型的IP数据报
-
Total length域:这里是IP数据报的总长度,IP头加数据。这个域是16字节长,所以IP数据报大小理论最大值是65535字节,然而数据包很少有超过1500字节的。
-
Identification域:用来标识以发送的IPV4封包,通常系统每发送一次这个封包就增加一次这个值。
-
Flags和Fragment offset域:当ipv4封包被分割为较小包时,使用这两个域。DF不要分割,这是给路由器的我一个命令,不要分割数据,主机无法将他们恢复回来。MF代表更多的分割。
-
Time to live:(生存时间TTL),包含TTL域是为了确保数据报不会永远呆在网络里打圈。每当数据包被路由器处理时,这个域就会减一,减到0这个报文就会被丢弃。
-
Protocol域:当IP数据报到达目的地时候才使用此域,它指定了数据报的数据部分将要传递给那个传输层协议。6代表要传递给TCP,17代表传递给UDP。
-
Header checksum域:头校验和帮助路由器检测接收到的IP数据报中的位错误。
-
Source address和Destination address域:指定此数据报源IP地址和目的IP地址。
-
Options域:选项域是一个长度可变的域,它包含了可选的信息,最大值是40字节。
-
为了在数据中包含IP头,下面定义CIPHeader结构
typedef struct _IPHeader//20个字节
{
UCHAR iphVerLen;//版本号和长度
UCHAR ipTos;//服务类型
USHORT ipLength;//封包总长度,既整个数据报的长度
USHORT ipID;//封包标识,唯一标识发送的每一个数据报
USHORT ipFlags;//标志
UCHAR IP TTL;//生存时间TTL
UCHAR ipPROTOCOL;//协议,可能是TCO、UDP、ICMP等
USHORT ipChecksum ;//校验和
ULONG ipSource;//源IP地址
ULONG ipDestination;//目的IP地址
}IPHeader,*PIPHeader;
UDP数据报格式
-
UDP头仅有八个字节长,包含四个域。
-
开头两个域分别是源端口号和目的端口号,每个都是16位。第三个域是UDP长度,这是UDP头部和数据报部分的总长。第四个域是校验和,之后就是应用层数据了。
-
如下定义UDPHeader结构描述UDP头
typedef struct _UDPHeader{
USHORT sourcePort;//源端口号
USHORT destinationPort;//目的端口号
USHORT len;//封包长度
USHORT checksum;//校验和
}UDPHeader,*PUDPHeader;
- 因为UDP是不可靠连接,所以计算校验和是可选的。和IP的校验和不同,UDP的校验和除了包含UDP节之外,还包含IP头中的几个域。计算UDP校验和所需的额外的域称为伪头,UDP校验和基于如下几个域。
* 32位的源IP地址
* 32位的目的IP地址
* 8位0域
* 8位协议域
* 16位UDP长度
* 16位源端口号
* 16位目的端口号
* 16位UDP长度
* 16位UDP校验和(0)
* UDP载荷
- 开始的5个域形成了UDP伪头,下面是自定义的UDP登报校验和的例程。
void ComputeUdpPseudoHeaderChecksum(
IPHeader *pIphdr,
UDPHeader *pUdphdr,
char* payload,
int payloadlen
){
char buff[1024];
char* ptr=buff;
int chksumlen=0;
ULONG zero=0;
//包含源ip地址和目的ip地址
memcpy(ptr,&pIphdr->ipSource,sizeof(pIphdr->ipSource));
ptr+=sizeof(pIphdr->ipSource);
chksumlen+=sizeof(pIphdr->ipSource)
memcpy(ptr,&pIphdr->ipDestination,sizeof(pIphdr->ipDestination));
ptr+=sizeof(pIphdr->ipDestination);
chksumlen+=sizeof(pIphdr->ipDestination);
//包含8位0域
memcpy(ptr,&zero,1);
ptr+=1;
chksumlen+=1;
//协议
memcpy(ptr,&pIphdr->ipProtocol,sizeof(pIphdr->ipProtocol));
ptr+=sizeof(pIphdr->ipProtocol);
chksumlen+=sizeof(pIphdr->ipProtocol);
//UDP长度
memcpy(ptr,&pUdphdr->len,sizeof(pUdphdr->len));
ptr+=sizeof(pUdphdr->len);
chksumlen+=sizeof(pUdphdr->len);
//UDP源端口号
memcpy(ptr,&pUdphdr->sourcePort,sizeof(pUdphdr->sourcePort));
ptr+=sizeof(pUdphdr->sourcePort);
chksumlen+=sizeof(pUdphdr->sourcePort);
//UDP目的端口号
memcpy(ptr,&pUdphdr->destinationPort,sizeof(pUdphdr->destinationPort));
ptr+=sizeof(pUdphdr->destinationPort);
chksumlen+=sizeof(pUdphdr->destinationPort);
//又是UDP长度
memcpy(ptr,&pUdphdr->len,sizeof(pUdphdr->len));
ptr+=sizeof(pUdphdr->len);
chksumlen+=sizeof(pUdphdr->len);
//16位的UDP校验和,置为0
memcpy(ptr,&zero,sizeof(USHORT));
ptr+=sizeof(pUdphdr->USHORT);
chksumlen+=sizeof(pUdphdr->USHORT);
//净荷
memcpy(ptr,payload,payloaden);
ptr+=payloaden
chksumlen+=payloaden
//补齐到下一个16位边界
for(int i=0li<payloadlen%2;i++){
*ptr=0;
ptr++;
chksumlen++;
}
pUdphdr->checksum=checksum((USHORT*)buff,chksumlen);//计算这个校验和,将结构填充到UDP头
}
- 校验和是以字为单位计算的,所以数据的长度如果不是单字倍数的话,需要以0补足。
发送原始UDP封包实例
- 发送原始UDP封包时,首先要以IPPROTO_UDP为协议类型创建一个原始套接字,打开原始套接字上的IP_HDRINCL选项,然后构建UDP封包,这要先设置IP头在设置UDP头,最后设置UDP净荷数据,初始化完整的UDP封包之后,调用sendto函数即可将他发送。
- 发送原始udp:https://github.com/MikotoMisakas/Network_programming/blob/develop/SendOriginalUdpSocket/SendOriginalUdpSocket/main.cpp