我是靠谱客的博主 高贵钢笔,这篇文章主要介绍IP/TCP/UDP报文解析(1)IP报文,现在分享给大家,希望可以做个参考。

目录

    • 前言
    • 正文
      • IP报文格式一览
      • IP校验和
        • 校验和计算
        • 校验数据
      • 片偏移的计算
      • 报文解析代码
    • 总结

前言

关于IP报文的解析涉及到很多位运算的使用,如果不熟位运算规则的小伙伴,可以查看我的这篇博文《Java中的位运算》

正文

IP报文格式一览

这是IP报在网络传输中的格式
一个完整的IP包通常被看做 首部 和 数据 两部分,首部中又包含 固定长度(20字节)和可变长度(最大60-20=40字节),IP报头包含以下数据:

  • 版本号:在IP数据报中第1个字节的前4位,现在有IPv4和IPv6 两个IP协议版本,所以这里可能的值有0010(IPv4 十进制 4)和 0100(IPv6 十进制6),端与端之间只有同协议版本才能正常通信。
  • 首部长:首部长的作用是标识IP报头的长度,在IP报文中第1个字节的后4位,其最大取值为15,其单位为4字节,即首部长最大为15 * 4 = 60字节,通常IP报头默认长度为5 * 4 = 20字节。
  • 服务类型:服务类型的作用是标识IP报文的处理方式,在IP报文中第2个字节。前三位代表包的处理优先级,取值范围0-7,0为最低 7为最高。后4位是TOS(注意TOS之前还有一个位无效位),表示本数据报在传输过程中需要得到的服务,依次表示为 D(Minimum delay)最小延迟、T(Maximum throughput)最大传输率、R(Maximum reliability)最高可靠性、C(Minimum cost)最小成本。这里的服务类型只是代表用户希望得到的服务,并不一定要强制执行。
  • 总长度:总长度的作用是标识IP报文首部分+数据部分的总长度,在IP报文中第3、4字节中,其最大取值为65535,其单位为1字节,所以一个IP报最大为65535 * 1 = 65535字节。
  • 序列号:序列号的作用是标识该IP报文请求,由发起请求方随机生成且本地唯一,每次分包在上一个包基础上该值+1填入,在IP报文中第5、6字节中。
  • 标志位:标志位的作用是标识该IP报是否发生分片行为DF(Don’t fragment)和是否还有分片MF(More Fragment) ,在IP报文中第7个字节的前3位,但只有后两位是有效的,依次为0 DF MF。有三个可能的值:未发生分片010、发生分片且该包后续没有分片000、发生分片且该包后续还有分片001。
  • 片偏移: 片偏移的作用是在IP报发生分包时标识该包中的数据相对于原始数据的起始位置,在IP报文中第7个字后5位和第8个字节。最大取值8191,单位是8字节,所以片偏移的最大值为8191 * 8 = 65528字节。
  • 生存时间:生存时间的作用是标识该请求能穿过路由的最大次数,以防止路由环路。该值每经过一次网络路由后减小1,信息送达或该值为0时停止传递。在IP报文中第9个字节中。
  • 上层协议:上层协议标识IP包中数据部分是属于那种协议的封装,在数据报中第10个字节。列举几个常用的值 :
协议二进制十进制
TCP000001106
UDP0001000117
ICMP000000011
IGMP000000102
OSPF0101100189
  • 首部校验和:首部校验和用来校验IP报头是否准确。在IP数据报的第11、12个字节中,关于校验和的计算和使用等下再说。
  • 源IP地址:源IP地址标识这个IP包的初始发送地址,在IP数据报的第13、14、15、16个字节中,所以IP地址通常被写为255.255.255.255这样的格式。
  • 目的IP地址:目的IP地址标识这个IP包最终送达的地址。
  • 选项和填充:作用是附加IP包处理信息,可变长度,最大为40字节。

IP校验和

校验和计算

计算首部校验和的过程如下

  1. 将首部校验和置零
  2. 如果首部的字节长度为奇数,则在计算时需要在首部末尾添加一个全为0的空字节
  3. 将首部中所有字节划分成16位长度的数进行相加求和
  4. 和的溢出位折叠求和。
  5. 将和做非运算得到最终校验和
  6. 将校验和填入首部校验和位。

具体实现请看文末的代码。

校验数据

校验数据相对简单,大体上和计算时差不多,只是校验计算的时候不需要重置校验和位,最后的结果为0时说明该IP报文正确,若不为0说明该报文错误将其丢弃。

片偏移的计算

由于数据链路层单次发送的数据单元最大为1500字节(1贞的大小 这个值貌似受源机硬件影响),所以当IP报文总长度大于这个值的时候,数据链路层会对IP包进行拆分发送,当产生分包后,每个包中的数据相对于原始IP包中的数据,就会有一个衔接位置,那么这个位置该如果计算呢?
这里举例说明,假如:一个原始IP包总长度4000,报头长20。

当发送第1个拆分包的时候,其数据起始位就是IP包起始位,所以其偏移为0,目前发送出去的数据就是1500 - 20 = 1480字节(下标0 - 1479),剩余数据2520,还有拆分包。

当发送第2个拆分包的时候,其数据起始位置应该在原始数据的1480位上,因为片偏移的单位是8字节,所以该包的片偏移值为1480/8 = 185,目前发送出去的数据是1480*2 = 2560,剩余1440,还有拆分包

当发送第3个拆分包的时候,其数据起始位置应该在原始数据的2560位上,所以该包的片偏移值为2560/8 = 370,剩余0,发送结束

这里具体的计算规则我也不是太熟

报文解析代码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
public class IPPacket extends Packet{ private final static String TAG = IPPacket.class.getSimpleName(); private final static int VERSION_BIT = 0; private final static int HEADER_LENGTH_BIT = 0; private final static int TOS_BIT = 1; private final static int TOTAL_LENGTH_BIT = 2; private final static int SEQUENCE_BIT = 4; private final static int TAG_BIT = 6; private final static int OFFSET_BIT = 6; private final static int LIVE_BIT = 8; private final static int PROTOCL_BIT = 9; public final static int PROTOCL_ICMP = 1; public final static int PROTOCL_IGMP = 2; public final static int PROTOCL_OSPF = 89; public final static int PROTOCL_TCP = 6; public final static int PROTOCL_UDP = 17; private final static int HEADER_SUM_BIT = 10; private final static int LOCAL_IP_BIT = 12; private final static int REMOTE_IP_BIT = 16; public IPPacket(byte[] bytes, int... parameters){ super(bytes,parameters); } /** * 获取IP报文协议版本 * 协议版本 * 在IP报文第0个字节中前4位 占1/2字节 共4位 * @return */ public int getVersion(){ return (bytes[VERSION_BIT] & 0xFF) >> 4; } /** * 获取IP报文头长度 * 报头长度 * 在IP报文第0个字节中后4位 占1/2字节 共4位 * 最小表示为 0000(十进制0) 最大表示为 1111(十进制15) 单位为4字节 * 也就是报头 最小为0 * 4字节0 * 4 * 8 = 0位,最大为15 * 4字节15 * 4 * 8 = 480位 * 一般报头长度是0101(十进制5),也就是5 * 4 = 20字节 * @return */ public int getHeaderLength(){ return (bytes[HEADER_LENGTH_BIT] & 0xF) * 4; } /** * 获取TOS服务字段 * 服务类型 * 在IP报文中第1个字节 占1字节 共8位 * 一般情况不使用 也没查到具体有什么用 * @return */ public int getTos(){ return bytes[TOS_BIT] & 0xFF; } /** * 获取IP报文总长度 * 数据报总长度 * 在IP报文中第2、3字节 占2字节 共16位 单位为1字节 * 标注没有分片前整个IP请求的总长度(包括 数据 和 报头) * @return */ public int getTotalLength(){ return byteToInt(bytes[TOTAL_LENGTH_BIT],bytes[TOTAL_LENGTH_BIT + 1]); } /** * 获取序列码 * 序列码 * 在IP报文中第4个和第5个字节 占2字节 共16位 * 分片请求时用作同IP请求标识 * 开始发送数据时由发送方生成。标识发送方发送的每一个数据报,如果发送的数据报未发生分片,则此值依次加1,如果发生了分片,分片后的各个数据报使用同一个序列号。 * @return */ public int getSequence(){ return byteToInt(bytes[SEQUENCE_BIT],bytes[SEQUENCE_BIT + 1]); } /** * 获取标志位 * 分片标志 * 在IP报文中第6个字节的前3位 占3/8字节 共3位 * 第一位保留,未使用。 * 第二位是DF(Don’t Fragment),如果为1,表示未发生分片;如果为0,表示发生了分片。 * 第三位是MF(More Fragment),如果为1,表示发生了分片,并且除了分片出的最后一个报文中此标志为0,其余报文中此标志均为1 * 该标志位有以下几个可能的值 * 001(十进制1):发生分片且后续还有分片 * 000(十进制0):发生分片且这是最后一个分片 * 010(十进制2):不能分片 * @return */ public int getTag(){ return (bytes[TAG_BIT] & 0xFF) >> 5 ; } /** * 是否发生分片 * 1 为未发生分片 返回false * 0 为发生了分片 返回true * DF Don't Fragment * @return */ public boolean isDF(){ if((getTag() >> 1) == 1){ return false; }else{ return true; } } /** * 是否还有分片 * 如果未发生分片直接返回false * 如果发生分片且后面还有分片返回true * 如果发生分片且后面没有分片了返回false * MF More Fragment * @return */ public boolean isMF(){ if(isDF() && (getTag() & 1) == 1){ return true; }else{ return false; } } /** * 获取片偏移 * 片偏移量 单位 8 byte * 在IP报文中第6个字节的后5位和第7个字节 占13/8字节 共13位 * 标识分片相对于原始IP数据报开始处的偏移。只在分片传输时起作用 * 链路层MTU(Maximum Transmission Unit 最大传输单元)为1500 * 如果IP报文总长度超过这个数值,就会产生IP分片 * 下面是计算公式 * 假设链路层传输时分片数量为 n * IP报头长度为20字节(实际传输时默认为20) * IP报文总长度为 m * 分片后的片偏移为y (不管有没有分片,首个IP分片的片偏移肯定是0 如果有分片n肯定是大于1的) * n = m > 1500 ? (m - m % 1500) / 1500 + 1 :1 * y = (1500 - 20) * (n - 1) / 8 * 可能会觉得懵逼 为什么还会除一个8 这是因为片偏移的单位是 8 byte 这里得到的值是最后写到报文中片偏移的值 * 我看过一个文档上面写的有个这样的描述:从0位到1479位是IP报数据位 当时不明白这个1479是怎么来的 * 后来仔细想想 这玩意是从下标读数来的 数组中读数是从0位开始到 数组长度 - 1 结束。 * 也就是 MTU - 报头长度 - 1 => 1500 - 20 - 1 = 1479 * 从0位数到1479位 正好是1500个位置。 * @return */ public int getOffsetByte(){ return byteToInt(bytes[OFFSET_BIT],bytes[OFFSET_BIT + 1]) * 8; } /** * 获取生命周期 * 生存时间 * 在IP报文中第8个字节 占1字节 共8位 * 表示生存周期,每经过一个路由值减一,防止路由环路。(windows系统下默认值为128 二进制 10000000) * 例如 路由A发数据给B B又转发给A 值依次衰减,值为0时或主动停止时停止转发 * @return */ public int getLive(){ return bytes[LIVE_BIT] & 0xFF; } /** * 获取上层协议 * 上层协议 * 在IP报文中第9个字节 占1字节 共8位 * 该值标识上层协议 * 其中常见的值有 * ICMP协议 十进制 1 二进制 00000001 * IGMP协议 十进制 2 二进制 00000010 * OSPF协议 十进制 89 二进制 01011001 * TCP协议 十进制 6 二进制 00000110 * UDP协议 十进制 17 二进制 00010001 * @return */ public int getProtocl(){ return bytes[PROTOCL_BIT] & 0xFF; } /** * 获取头部校验和 * 头部校验和 * 在IP报文中第10、11个字节 占2字节 共16位 * 该值是对整个数据包的包头进行的校验 * @return */ public int getHeaderSum(){ return byteToInt(bytes[HEADER_SUM_BIT],bytes[HEADER_SUM_BIT + 1]); } /** * 获取本地IP地址 String类型 * x.x.x.x格式 * 源IP地址 * 在IP报文中第12、13、14、15个字节 占4个字节(32位) * 表示本地IP地址(发送方MAC地址 也就是发送方的物理硬件地址) 那么地址掩码又是怎么回事? * 因为一个字节最大表示数为255 所以我们看到的IP地址通常写为x.x.x.x的格式(x为0-255的十进制数 转换为二进制数为00000000~11111111) * @return */ public String getLocalIpString(){ return (bytes[LOCAL_IP_BIT] & 0xFF) + "." + (bytes[LOCAL_IP_BIT + 1] & 0xFF) + "." + (bytes[LOCAL_IP_BIT + 2] & 0xFF) + "." + (bytes[LOCAL_IP_BIT + 3] & 0xFF); } public int getLocalIpInt(){ return byteToInt(bytes[LOCAL_IP_BIT],bytes[LOCAL_IP_BIT + 1],bytes[LOCAL_IP_BIT + 2],bytes[LOCAL_IP_BIT + 3]); } /** * 获取远程IP地址 String类型 * x.x.x.x格式 * 目的IP地址 * 在IP报文中第16、17、18、19个字节 占4个字节(32位) * 远程IP地址(接收方MAC地址 也就是接收方的物理硬件地址) * 因为一个字节最大表示数为255 所以我们看到的IP地址通常写为x.x.x.x的格式(x为0-255的十进制数 转换为二进制数为00000000~11111111) * @return */ public String getRemoteIpString(){ return (bytes[REMOTE_IP_BIT] & 0xFF) + "." + (bytes[REMOTE_IP_BIT + 1] & 0xFF) + "." + (bytes[REMOTE_IP_BIT + 2] & 0xFF) + "." + (bytes[REMOTE_IP_BIT + 3] & 0xFF); } public int getRemoteIpInt(){ return byteToInt(bytes[REMOTE_IP_BIT],bytes[REMOTE_IP_BIT + 1],bytes[REMOTE_IP_BIT + 2],bytes[REMOTE_IP_BIT + 3]); } /** * 检查头部校验和 * 很无语这个校验和的算法模式为什么会这样设计 * 直接取每个字节中表示的十进制数相加不就得了 * 还非得用16位来表示肯定会溢出的校验和 * 这个方法还没有搞明白 */ public boolean checkSum(){ return (( ~getSum() ) & 0xFFFF) == 0; } /** * 校验求和 * @return */ private int getSum(){ int headerLength = getHeaderLength(); int sum = 0; // 两字节一组依次计算16位和得到累加值 for(int i = offset; i < offset + headerLength; i += 2){ sum += byteToInt(bytes[i],bytes[i + 1]); } // 如果报头为奇数 那么最后一个字节的和也需要加上 if((offset + headerLength) % 2 > 0 ){ sum += (bytes[offset + headerLength - 1] & 0xFF) << 8; } //这里计算校验和16位之上是否有进位 若有进位则循环折叠求和 直到16位之上没有进位 while ((sum >> 16) > 0){ sum = (sum >> 16) + (sum & 0xFFFF); } return sum; } /** * 设置完参数后重新计算校验和并设置 */ public void refreshCheckSum(){ bytes[HEADER_SUM_BIT] = 0; bytes[HEADER_SUM_BIT + 1] = 0; int sum = ~getSum(); bytes[HEADER_SUM_BIT] = (byte) (sum >> 8); bytes[HEADER_SUM_BIT + 1] = (byte)(sum); } public void setLocalIp(String ip){ String[] strings = ip.split("\."); if(strings.length != 4) return; int y = 255; for(int i = 0;i < 4; i++){ try { y = Integer.valueOf(strings[0]); }catch (NumberFormatException e){} bytes[REMOTE_IP_BIT + i] = (byte) y; } } public void setLocalIp(int ip){ for(int i = 0; i < 4;i ++){ bytes[LOCAL_IP_BIT + i] = (byte) (ip >> ((3 - i) * 8)); } } public void setRemoteIp(String ip){ String[] strings = ip.split("\."); if(strings.length != 4) return; int y = 255; for(int i = 0;i < 4; i++){ try { y = Integer.valueOf(strings[0]); }catch (NumberFormatException e){} bytes[REMOTE_IP_BIT + i] = (byte) y; } } public void setRemoteIp(int ip){ for(int i = 0; i < 4;i ++){ bytes[REMOTE_IP_BIT + i] = (byte) (ip >> ((3 - i) * 8)); } } }

关于文中的Packet类的代码如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
public class Packet { // 数据报文 protected byte[] bytes; // 偏移值 protected int offset; // 有效数据长度 protected int validLength; // 下一层的包 protected Packet packet; /** * * @param bytes 收到的byte数据集合 * @param parameters 一些参数 如:数据偏移 有效长度 */ public Packet(byte[] bytes,int... parameters){ this.bytes = bytes; if(parameters != null){ if(parameters.length > 0){ offset = parameters[0]; if(parameters.length > 1){ validLength = parameters[1]; } } } if(validLength == 0){ validLength = bytes.length; }else if(validLength > bytes.length){ validLength = bytes.length; } } /** * 获取下级协议 * @return */ public Packet getPacket() { return packet; } /** * 设置下级协议 从上至下依次为 HTTP/HTTPS -> TCP/UDP -> IP -> 网路路由 * @param packet */ public void setPacket(Packet packet) { this.packet = packet; } /** * 获取数据 * @return */ public byte[] getBytes(){ return bytes; } /** * 获取数据偏移 * @return */ public int getOffset(){ return offset; } /** * 获取数据有效长度 * @return */ public int getValidLength(){ return validLength; } /** * 获取报头长度 * @return */ public int getHeaderLength(){ return 0; } public int byteToInt(byte... bytes){ int sum = 0; if(bytes == null) return 0; for (int i = 0; i < bytes.length && bytes.length <= 4; i ++){ sum |= (bytes[i] & 0xFF) << ((bytes.length - 1 - i) * 8); } return sum; } public byte intToByte(int i){ return (byte) i; } public byte[] intToBytes(int i){ byte[] bytes = new byte[4]; bytes[0] = (byte) (i >> 24); bytes[1] = (byte) (i >> 16); bytes[2] = (byte) (i >> 8); bytes[3] = (byte) i; return bytes; } }

代码中的注释很简洁,全部看完应该能理解是什么意思

总结

以上就是我对IP报文的理解,有问题请多指教。

最后

以上就是高贵钢笔最近收集整理的关于IP/TCP/UDP报文解析(1)IP报文的全部内容,更多相关IP/TCP/UDP报文解析(1)IP报文内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(80)

评论列表共有 0 条评论

立即
投稿
返回
顶部