UDP协议报文分析和主动发送UDP简单实现
- 前言
- 一、Wireshark 报文解析
- 1、UDP报文实例
- 2、报文格式分析
- ①、以太网头
- ②、IP头
- ③、UDP头
- 二、UDP主动传输数据的实现方式
- 1、说明
- 2、发送UDP前的约定
- 3、ARP报文
- 4、UDPIP报文
- 三、UDP报文校验
- 1、IP校验和计算
- 2、UDP校验和计算
前言
用 Wireshark 简单分析UDP报文解析,学习UDP报文的解析。在需要编写底层UDP报文的环境下(单片机等),简单编排UDP报文达到主动传输的目的。
一、Wireshark 报文解析
1、UDP报文实例
2、报文格式分析
一帧UDP报文由四个部分组成:以太网头、IP头、UDP头、数据。
帧头 | 帧数据含义 | 对应报文(HEX) | 位数 |
以太网头 | 目标MAC地址 | 00:aa:00:00:00:7b | 6*8 |
源MAC地址 | e0:db:55:f8:d1:e5 | 6*8 | |
协议类型 | 08 00 | 2*8 | |
IP头 | IP协议版本 | 4 | 4 |
IP头长度 | 5 | 4 | |
服务类型TOS | 00 | 8 | |
报文总长度(IP报文开始) | 00 21 | 2*8 | |
标识符(序号) | c2 a1 | 2*8 | |
标志位 | 00 00 | 2*8 | |
帧存活周期TTL | 40 | 8 | |
数据协议 | 11 | 8 | |
IP校验和 | 32 50 | 2*8 | |
源IP地址 | c0 a8 02 0f | 4*8 | |
目标IP地址 | c0 a8 02 7b | 4*8 | |
UDP头 | 源端口号 | 2c ee | 2*8 |
目标端口号 | 01 f6 | 2*8 | |
报文总长度(UDP报文开始) | 00 0d | 2*8 | |
UDP校验和 | 17 f3 | 2*8 | |
数据帧 | 传输数据 | 11 11 11 11 11 | n*8 |
①、以太网头
以太网头使用两种标准帧格式。第一种是上世纪80年代初提出的DIXv2格式,即EthernetII帧格式。EthernetII后来被IEEE802标准接纳,并写进了IEEE802.3x-1997的3.2.6节。第二种是1983年提出的IEEE802.3格式。普通情况下使用EthernetII帧格式更简单便捷。
报文中 Type 为" 08 00 "表示后续报文的协议为IP协议帧,若 Type 为"08 06 "表示后续报文为ARP帧。
②、IP头
若为主动发送的UDP报文,IP头中开头的“45 00”以及“00 00 40 11”可以不用修改,主要修改其他数据。其中“40”表示帧存活时间为64秒,“11”表示后续协议为UDP协议。
IP头的数据帧主要修改的部分为:IP头开始的数据长度、标识符id、IP校验、对端IP、本机IP。
数据长度:IP头的长度+后续报文长度。例如实例中的报文长度为0x0021(33),即IP头长度20、UDP头长度8和数据长度5相加(20+8+5)而来。
标识符ID:可以理解为报文的编号,一般是由上一帧的标识符计算加一。
对端IP:目标IP地址。
本机IP:本机IP地址。
③、UDP头
UDP头的数据帧主要注意UDP校验和的计算方式,需要同时计算UDP伪首部。
二、UDP主动传输数据的实现方式
1、说明
以下方法的测试环境是在局域网内,且无其他设备同时连接的环境下实现的,仅用与点对点的传输方式。
2、发送UDP前的约定
在发送UDP报文前,想要对端设备收到UDP报文,就必须知道对端的设备部分信息:目标MAC地址、目标IP地址、目标端口号。其中目标IP地址和目标端口号可以在约定后直接进行绑定,但目标MAC地址在很多情况下无法直接得出,且在对端设备更换后会随着变动。此时,我们可以用ARP协议获取MAC地址。
3、ARP报文
ARP(Address Resolution Protocol),地址解析协议,是根据IP地址获取物理地址的一个TCP/IP协议。
在发送UDP报文前,可以先发送一帧ARP报文请求,报文需要修改的部分为:本机MAC地址(30:3a:64:9f:32:06)、本机IP(0xc0a8 0067)、目标IP(0xc0a8 0065),其他部分可以参照图中请求报文的数据直接写死。报文中 06 04 后的两个字节是Opcode, 00 01 表示ARP请求报文,若为 00 02 表示应答报文。
发送ARP报文后会收到ARP的回复报文,回复报文中包含目标IP地址和MAC地址,和本机的IP地址和MAC地址,可以在确认ARP回复报文的目标IP为本机IP后,再将该MAC地址缓存,取得需要的MAC地址为(3e:c8:be:6e:f4:06)。
ARP报文举例,如图所示:
请求报文:
应答报文:
4、UDPIP报文
通过ARP报文获取到对端MAC地址后,就可以开始编辑发送UDP报文了。
(1)编写以太网头部。 写入目标MAC地址(00:aa:00:00:00:7b)、本机MAC地址(e0:db:55:f8:d1:e5)、Type(0x08 00)。
(2)将IP头的数据长度、IP校验和的四个字节置为0,先填充的IP头部的其他位。 写入IP版本以及长度(0x45)、区分服务领域(0x00)、ID号(0xc2 a1)、标志位Flags(0x00)、ttl(0x40)、协议号(0x11)、本机IP地址(0xc0 a8 02 0f)、目标IP地址(0xc0 a8 02 7b)。
(3)将UDP头的数据长度、IP校验和的四个字节置为0,先填充的UDP头部的其他位。 写入本机源端口(0x2c ee)、目标端口(0x01 f6)、ID号(0x00 00)。
(4)填充UDP需要传输的数据。 在UDP报文头之后写入需要传输的数据(0x11 11 11 11 11)。
(5)根据传输的数据长度,填充IP头中的总长度(0x00 21)。 该长度计算方式为:IP头报文长度20 + UDP报文头长度8 + 数据长度5 = 33。
(6)根据传输的数据长度,填充UDP头中的总长度(0x00 0d)。 该长度计算方式为:UDP报文头长度8 + 数据长度5 = 13。
(7)根据IP头报文已填充数据,计算校验和,填充IP头中的校验位(0x32 50)。
(7)根据IP头和UDP头报文已填充数据,计算校验和,填充UDP头中的校验位(0x17 f3)。
三、UDP报文校验
1、IP校验和计算
对IP报文头从首部开始以16bit为单位求和,将计算结果溢出16bit的部分加在低16bit上,取反。
忽略校验位数据,将上述实例报文的IP头从首部开始以16bit为单位拆分后,得到一组数据:0x4500,0x0021,0xC2A1,0x0000,0x4011,0xC0A8,0x020F,0xC0A8,0x027B。
将上述拆分的16bit数据累加: 0x4500+0x0021+0xC2A1+0x0000+0x4011+0xC0A8+0x020F+0xC0A8+0x027B=0x2 CDAD;
将上结果的高16位加在低16位数据得到:0xCDAF;
将得到的0xCDAF作位取反得:0x3250。
简单代码段如下:
/*sendbuf[]是要发送的报文,计算前要先填充其他数据*/
static u16_t
ipchksum()
{
u16_t i,index=14; //从sendbuf[14]开始计算,即IP头开始位置。
u32_t sum=0;
for(i=0;i<5;i++){
sum+=((u16_t)sendbuf[index]<<8)+sendbuf[index+1];
index+=2;
}
index+=2;
for(i=0;i<4;i++){
sum+=((u16_t)sendbuf[index]<<8)+sendbuf[index+1];
index+=2;
}
sum = (sum >> 16) + (sum & 0xffff);
return (~sum & 0xffff);
}
2、UDP校验和计算
对UDP报文头从首部开始以16bit为单位求和,求和时包含后续所有数据以及UDP伪首部数据;将计算结果溢出16bit的部分加在低16bit上,取反。
(1)先计算伪首部,伪首部由四部分组成:源地址(0xc0a8,0x020f)、目的地址(0xc0a8,0x027b)、UDP数据长度(0x000d)、协议类型(0x0011)。将UDP伪首部以16位求和得到(0x0001 85F8)。
(2)再计算UDP头部报文,由四部分组成:源端口(0x2cee)、目标端口(0x01f6)、UDP数据长度(0x000d)、数据(0x1111、0x1111、0x1100)。以16位求和得到(0x6213)。这里要注意的是数据结尾如果为单数,需要在低8位填充0x00,而不是高八位填充,即0x11填充为0x1100再求和。
(3)将UDP的伪首部和与UDP头部和求和(0x1 85f8 + 0x6213);将计算结果的高16位加到低16位得到(0xe80c);进行取反运算,得到最终UDP校验和0x17f3。
简单代码段如下。
/*sendbuf[]是要发送的报文,计算前要先填充其他数据*/
static u16_t
udpchksum(u16_t datalen)
{
u16_t i,index=26;
u32_t sum=0;
sum = 0x11; //计算伪首部
for(i=0;i<7;i++){
sum+=((u16_t)sendbuf[index]<<8)+sendbuf[index+1];
index+=2;
}
index-=2;
sum+=((u16_t)sendbuf[index]<<8)+sendbuf[index+1];
index+=4;
for(i=0;i<(datalen/2);i++){
sum+=((u16_t)sendbbf[index]<<8)+sendbuf[index+1];
index+=2;
}
if(datalen%2){
sum+=((u16_t)sendbbf[index]<<8);
}
sum = (sum >> 16) + (sum & 0xffff);
return (~sum & 0xffff);
}