ICMP

ICMP报文结构

image-20230110200620314

ICMP校验和算法

  1. 报文内容,相邻两个字节拼接到一起组成一个16bit数,将这些数累加求和
  2. 若长度为奇数,则将剩余的1个字节,也累加到求和
  3. 得出总和之后,将和值的高16位与低16位不断求和,直到高16位为
  4. 以上三步得出结果后,取反,即为校验和

例如 :根据ICMP的报文可以得出以下的格式

1
8 | 0 | 0 0 | 0 1 | 0 1 | 1 1 1 1 1 1 1 1

相邻两个字节拼接到一起组成一个16bit数

1
80 + 00 + 01 + 01 + 11 + 11 + 11 + 11 

求和后是一个int32的数,将高16位和低16位求和,直到高位为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
package main

import (
"bytes"
"encoding/binary"
"flag"
"fmt"
"log"
"math"
"net"
"os"
"time"
)

var (
timeout int64
size int
count int
typ uint8 = 8
code uint8 = 0
sendCount int
successCount int
failCount int
minTx int64 = math.MaxInt64
maxTx int64 = math.MinInt32
totalTx int64 = 0
)

type ICMP struct {
Type uint8
Code uint8
CheckSum uint16
ID uint16
SequenceNum uint16
}

func main() {
parseCommandArgs()
desIp := os.Args[len(os.Args)-1]
conn, err := net.DialTimeout("ip:icmp", desIp, time.Duration(timeout)*time.Millisecond)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
fmt.Printf("正在 Ping %s [%s] 具有 %d 字节的数据:\n", desIp, conn.RemoteAddr(), size)
for i := 0; i < count; i++ {
sendCount++
startTime := time.Now()
icmp := &ICMP{
Type: typ,
Code: code,
CheckSum: 0,
ID: 1,
SequenceNum: 1,
}
data := make([]byte, size) //所有元素为0
var buffer bytes.Buffer
// 大端写入
binary.Write(&buffer, binary.BigEndian, icmp)
// icmp写入buffer
buffer.Write(data)
data = buffer.Bytes()
checkSum := checkSum(data)
data[2] = byte(checkSum >> 8) // 高8位
data[3] = byte(checkSum) // 低8位

// 读写
conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Millisecond))
n, err := conn.Write(data)
if err != nil {
failCount++
log.Println("write err:", err)
continue
}
buf := make([]byte, 65535)
n, err = conn.Read(buf)
if err != nil {
failCount++
log.Println("read err:", err)
continue
}
ts := time.Since(startTime).Milliseconds()
successCount++
totalTx += ts
if minTx > ts {
minTx = ts
}
if maxTx < ts {
maxTx = ts
}
fmt.Printf("来自 %d.%d.%d.%d 的回复: 字节=%d 时间=%d ms TTL=%d\n",
buf[12], buf[13], buf[14], buf[15], n-28, ts, buf[8])
time.Sleep(time.Second)

}
fmt.Printf("%s 的 Ping 统计信息:\n 数据包: 已发送 = %d,已接收 = %d,丢失 = %d (%.2f%% 丢失),\n往返行程的估计时间(以毫秒为单位):\n 最短 = %dms,最长 = %dms,平均 = %dms",
conn.RemoteAddr(), sendCount, successCount, failCount, float64(failCount)/float64(sendCount), minTx, maxTx, totalTx/int64(sendCount))

}

func parseCommandArgs() {
flag.Int64Var(&timeout, "w", 1000, "请求超时时长,单位ms")
flag.IntVar(&size, "l", 32, "请求缓冲区大小,单位字节")
flag.IntVar(&count, "n", 4, "发送请求数")
flag.Parse()
}

func checkSum(data []byte) uint16 {
length := len(data)
index := 0
var sum uint32
for length > 1 {
sum += uint32(data[index])<<8 + uint32(data[index+1])
length -= 2
index += 2
}
// 奇数
if length != 0 {
sum += uint32(data[index])
}
h16 := sum >> 16
for h16 != 0 {
sum = h16 + uint32(uint16(sum))
h16 = sum >> 16
}
return uint16(^sum)
}