Win32如何定义IP数据报的首部

  文章出自:http://lang.9sssd.com/vcpp/art/169

[摘要]本文介绍Win32如何定义IP数据报的首部,包括IP数据报首部的定义、TCP报文段首部的定义和UDP报文段首部的定义相关内容。

在进行网络编程时,可能需要直接操作原始的IP数据报,例如编写网络嗅探器。此时要定义一个表示IP数据报首部的结构体来获取首部中的各个信息,问题也随之而来:平时我们使用的数据都是BYTE、WORD或者DWORD,但IP数据报首部的有些字段并不按照字节、字或双字对齐,字段的长度也不是一字节、两字节或四字节,这种不一致的现象使得结构体的定义很有难度。我见过几种IP数据报首部结构体的定义,虽然方法各异,但都是大量使用union,将几个字段塞进一个BYTE或WORD中,在代码中还要通过移位、按位与等操作获取实际的字段。其实,只要理解数据在内存中是如何摆放的,以及利用好结构体定义的特性,可以最大限度地使结构体的字段直接对应首部中的字段,避免在代码中使用大量的位操作。

位域

使用C/C++编程时我们都会大量定义和使用结构体,但可能有不少人还不知道定义结构体时可以使用“位域”这个特性。位域是指不需要占据整个字段长度,而只需要占据一个或多个位的字段。定义方法如下所示:

structByte {BYTELow : 4;BYTEHigh : 4;};

上面的代码定义了一个名为Byte的结构体,它有两个字段Low和High,这两个字段的类型都是BYTE,而且都指定了只占用四个位,所以它们共用一个BYTE的空间。又因为Low定义在High之前,所以Low使用这个BYTE的低四位,High使用高四位。

位域的类型除了BYTE之外,还可以使用WORD和DWORD。要注意的是,在使用位域时,对于那些希望共用同一个BYTE、WORD或DWORD的字段,要始终使用相同的类型来定义,只有当BYTE、WORD或DWORD的位分配完毕时,才使用另一种类型。例如,不要出现下面的定义:

structBitFields {WORDField1 : 8;BYTEField2 : 8;};structBitFields {BYTEField1 : 3;WORDField2 : 5;};

上面的定义令人难以理解(至少我是根本无法理解),而且也不知道编译器会如何处理这些定义。所以,为了程序的可理解性和正确性,应该中规中矩地使用位域:

structBitFields {BYTEField1 : 5;BYTEField2 : 3;WORDField3 : 7;WORDField4 : 3;WORDField5 : 6;};

有了位域,在访问这些字段时,编译器会自动进行位操作,还会自动进行截断,所以在代码中再也不需操心这些繁琐的操作了。

位序

字节序对大家来说都不会陌生,它表示字节在内存中的存放顺序。而位序与字节序的意义相近,它表示的是字节内每个位的存放顺序,也有大端和小端格式。我们通常只在字节级别上访问数据,几乎不会在位级别上访问数据,所以忽略了位序这个问题。现在我们用到了位域,已经深入到了位级别,因此很有必要了解一下Intel处理器的位序。遗憾的是,我没找到任何资料解析Intel处理器使用哪种位序,因此只能靠自己动手进行实验。

实验很简单,下面是实验的代码:

voidwmain() {structByte {BYTEZero : 1;BYTEOne : 1;BYTETwo : 1;BYTEThree : 1;BYTEFour : 1;BYTEFive : 1;BYTESix : 1;BYTESeven : 1;};Byte b = { 0 };b.Zero = 1;b.Five = 1;}

该代码将一个字节的第0位和第5位设置为1,其它位都是0。由于Visual Studio不能以二进制方式查看数据,因此我使用WinDbg进行查看:

左上角第一个字节就是变量b的数据,可以看到第0位和第5位已经设置为1。更重要的是,看到了位序是大端格式,即低地址存放高位,高地址存放低位。

了解了位域和位序之后,下面就开始定义IP数据报的首部了。

IP数据报首部的定义

首先来看一下IP数据报首部的格式:

对于那些占据整个BYTE、WORD或DWORD的字段,可以直接按照它们的先后顺序来定义而不会出现任何问题,而其它“不规则”的字段则需要进行特殊的设计。“不规则”的字段包括:版本、首部长度、片偏移以及三个标识位。

首先来看一下版本和首部长度,它们都是四字节长度,一开始我们会很自然地按照它们的先后顺序来定义:

structIPHeader {BYTEVersion : 4;BYTEHeaderLength : 4;};

然而这样是错误的,你使用Version字段得到的是首部长度,使用HeaderLength得到的是版本,两者调转过来了!之所以会出现这样的错误,恰恰是位序的问题。上文说过,Intel处理器使用的是大端格式的位序,所以IPHeader结构的第一个字节是这样的:

由于Version定义在前,所以Version使用0~3位,HeaderLength使用4~7位。很明显,这跟IP数据报首部的格式正好相反。所以,正确的定义应该是:

structIPHeader {BYTEHeaderLength : 4;BYTEVersion : 4;};

接下来的区分服务、总长度和标识都占据了整个BYTE和WORD的长度,所以按顺序定义它们就行了:

structIPHeader {BYTEHeaderLength : 4;BYTEVersion : 4;BYTEDS;WORDTotalLength;WORDID;};

接下来的三个标识位和片偏移则有点复杂。首先来看一下一个WORD中的位是如何放置的(注意是小端字节序):

对照IP数据报首部的格式图,可以看到三个标识位分别占用了WORD的7、6和5位,剩下的位都是片偏移的。虽然从图中看上去片偏移的位都连接在一起,可是如果以位编号的顺序来看的话,片偏移实际上被标识位分割成了两部分,分别在两个BYTE中:第一部分是0~4位,第二部分是8~F位。所以,无论如何也不能仅仅使用位域把这两部分组合在一起,必须在代码中使用位操作来组合。这确实是一个遗憾。

将这两部分组合起来有多种方法,我使用的方法是:将这个WORD分成两个BYTE,第一个BYTE 的最后三个位作为标致位,前面5个位作为片偏移的第一部分,第二个BYTE全部作为片偏移的第二部分,如下所示:

structIPHeader {BYTEHeaderLength : 4;BYTEVersion : 4;BYTEDS ;WORDTotalLength;WORDID;BYTEFragmentOffset0 : 5;BYTEMF : 1;BYTEDF : 1;BYTEReserved : 1;BYTEFragmentOffset1;};

注意第一个BYTE中的字段都按反顺序来定义,这也是位序的缘故,就不再解释了。为了得到完整的片偏移,要将第一部分左移8位,再加上第二部分:

FragmentOffset = (FragmentOffset0 << 8) + FragmentOffset1

好了,IP数据报首部中比较难搞的字段都已经解决了,剩下的都很容易解决,下面是完整的定义:

structIPHeader {BYTEHeaderLength : 4;       //首部长度BYTEVersion : 4;            //版本BYTEDS;                     //区分服务WORDTotalLength;            //总长度WORDID;                     //标识BYTEFragmentOffset0 : 5;    //片偏移BYTEMF : 1;                 //MF标识BYTEDF : 1;                 //DF标识BYTEReserved : 1;           //保留标识BYTEFragmentOffset1;        //片偏移BYTETTL;                    //生存时间BYTEProtocol;               //协议WORDChecksum;               //检验和DWORDSourceAddress;         //源地址DWORDDestinationAddress;    //目的地址};

TCP报文段首部的定义

既然说到了IP数据报首部,就不得不说一下TCP报文段的首部。下面是TCP报文段的首部格式:

上图中比较复杂的是数据偏移、保留字段以及一些标致位,不过有了上文的讲解,相信这不再是问题。下面直接给出完整的定义,不再详细解释了:

structTCPHeader {WORDSourcePort;             //源端口WORDDestinationPort;        //目的端口DWORDSequenceNumber;        //序号DWORDAcknowledgmentNumber;  //确认号BYTEReserved0 : 4;          //保留字段第一部分BYTEDataOffset : 4;         //数据偏移BYTEFIN : 1;                //FIN标识BYTESYN : 1;                //SYN标识BYTERST : 1;                //RST标识BYTEPSH : 1;                //PSH标识BYTEACK : 1;                //ACK标识BYTEURG : 1;                //URG标识BYTEReserved1 : 2;          //保留字段第二部分WORDWindow;                 //窗口WORDChecksum;               //检验和WORDUrgentPointer;          //紧急指针};

UDP报文段首部的定义

为了本文的完整性,这里也给出UDP报文段首部的格式以及定义。

structUDPHeader {WORDSourcePort;             //源端口WORDDestinationPort;        //目的端口WORDLength;                 //长度WORDChecksum;               //检验和};

Win32如何定义IP数据报的首部相关推荐

  1. [Win32]IP数据报的首部如何定义

    在进行网络编程时,可能需要直接操作原始的IP数据报,例如编写网络嗅探器.此时要定义一个表示IP数据报首部的结构体来获取首部中的各个信息,问题也随之而来:平时我们使用的数据都是BYTE.WORD或者DW ...

  2. 【计算机网络】网络层 : IP 数据报格式 ( IP 数据报首部格式 )

    文章目录 一.TCP / IP 协议栈 二.IP 数据报 格式 三.IP 数据报 首部格式 一.TCP / IP 协议栈 TCP / IP 协议栈 : ① 应用层 : HTTP , FTP , DNS ...

  3. 计算机网络-IP数据报计算(IP数据报分片)一个数据报部分长度为3400字节(使用固定首部)。现在经过一个网络传输,该网络的MTU为800字节:

    IP数据报计算(IP数据报分片) 题目: 一个数据报部分长度为3400字节(使用固定首部).现在经过一个网络传输,该网络的MTU为800字节: (1)应分为几个数据报片? (2)各数据报片的数据字段长 ...

  4. IP数据报首部检验和的详细计算过程

    目录 IP数据报检验的计算过程 引入 检验原理 题目案例及分析 题目要求 分析 计算过程图解 总结 IP数据报检验的计算过程 本篇文章只介绍IP数据报的检验过程,不对原理做过多讲解.内容通俗易懂,请放 ...

  5. 【Linux网络编程】IP 数据报格式详解

    IP 数据报首部 TCP/IP 协议定义了一个在因特网上传输的包,称为 IP 数据报 (IP Datagram).这是一个与硬件无关的虚拟包,由首部和数据两部分组成. 首部的前一部分是固定长度,共 2 ...

  6. IP协议 (通俗易懂),IP协议的主要功能及实现原理,IP地址分类,IP数据包分片,IP数据报格式。

    「作者主页」:士别三日wyx 「作者简介」:CSDN top100.阿里云博客专家.华为云享专家.网络安全领域优质创作者 「专栏简介」:此文章已录入专栏<计算机网络零基础快速入门> 本章重 ...

  7. 计算机网络 --- 网络层IP数据报

    IP数据报格式 首部 版本:IPv4/IPv6 首部长度:单位是4B,最小为5.也就是说如果首部长度的四个bit的出来的数是8,那么首部长度就是8 * 4B = 32B也就是32字节 区分服务:指示期 ...

  8. 计算机网络之网络层:2、IP数据报、IP数据报分片

    网络层:2.IP数据报 TCP/IP协议: IP数据报: IP数据报分片: 最大传送单元MTU: 与IP数据报分片相关的相关字段: TCP/IP协议: IP数据报: 首部长度:4bit,最小取值010 ...

  9. 再谈关于IP数据报分片

    之前特别强调过具体的编号分片,但是少了关于总长度以及编号的细节,这里添加. http://blog.csdn.net/u011240016/article/details/52799673 首先,特别 ...

最新文章

  1. uploadhandler.php,多个WordPress主题’upload-handler.php’任意文件上传漏洞
  2. TikTok 英国业务亏损、苹果从中国应用商店下架近4万款游戏、Zoom 接受调查等|Decode the Week...
  3. Spark _08窄依赖和宽依赖stage
  4. 内存分配方式以及堆和栈的区别
  5. 手把手教你学Dapr - 6. 发布订阅
  6. 基于Citus和ASP.NET Core开发多租户应用
  7. 清除服务器上传队列的文件,webUploader上传demo
  8. linux系统认不到设备,linux中/dev/找不到设备
  9. c语言实现线程相关操作,如何用C语言实现多线程
  10. 多边形区域填充算法_花一分钟看一个案例,PPT中图片填充形状的应用
  11. IPv6網絡開發范例
  12. 消格子时一个很深的bug的修复纪录
  13. ubuntu 的使用(三)—— 实用小工具
  14. 得到星期的sql语句和得到月末的sql语句
  15. sed用法详解(转载)
  16. 2022道路运输企业安全生产管理人员考试题及答案
  17. Vue + element + Springboot 通过邮箱找回密码
  18. bashne java_bash脚本中 if 语句 和 for 语句使用方法
  19. C# 之 扑克游戏 -- 21点规则介绍和代码实现
  20. 为什么要学习 Linux?

热门文章

  1. 不要自己默默加班了!争取领导支援的五大实战话术
  2. 川崎机器人here指令_川崎机器人定点修正坐标设置指导书.pdf
  3. 意迷观看欧冠决赛慌乱踩踏 公共安防再次升级
  4. 一夜狼人杀-一觉睡醒( ̄ー ̄)发现游戏结束了。。
  5. 周末学习总结(21.10.23)
  6. jeecg-boot实现分布式定时任务
  7. 期货的价格与执行价格(期货执行价格是什么意思)
  8. GhostNet详解及代码实现
  9. 如何补丁1个文件(linux diff patch)
  10. 数字编码电位器c语言,数字电位器x9c103应用电路