熟悉报文结构
ICMP校验和算法:
- 报文内容,相邻两个字节拼接到一起组成一个16bit数,将这些数累加求和
- 若长度为奇数,则将剩余一个字节,也累加求和
- 得出总和之后,将和值的高16位与低16位不断求和,直到高16位为0
- 以上三步得出结果后,取反,即为验证和
我们选取实现其中的
先实现命令行部分
var (
timeout int64
size int
count int
)
func getCommandArgs() {
//通过flag.来读命令行的参数
flag.Int64Var(&timeout, "w", 1000, "请求超时时长,单位毫秒")
flag.IntVar(&size, "l", 32, "请求发送缓冲区大小,单位字节")
flag.IntVar(&count, "n", 4, "发送请求数")
flag.Parse()
}
func main() {
getCommandArgs()
fmt.Println(timeout, size, count)
}
测试显示,可以成功拿到命令行的参数
定义ICMP报文格式
type ICMP struct{
Type uint8
Code uint8
Checksum uint16
ID uint16
SequenceNum uint16
}
全部代码加注释
package main
import (
"bytes"
"encoding/binary"
"flag"
"fmt"
"log"
"net"
"os"
"time"
)
// 定义全局变量
var (
timeout int64 // 请求超时时长,单位毫秒
size int // 请求发送缓冲区大小,单位字节
count int // 发送请求数
typ uint8 = 8 // ICMP请求类型
code uint8 = 0 // ICMP请求代码
)
// ICMP结构体定义ICMP请求的数据结构
type ICMP struct {
Type uint8
Code uint8
Checksum uint16
ID uint16
SequenceNum uint16
}
func main() {
getCommandArgs() // 获取命令行参数
// 取出最后一个参数,即目标IP地址
desIp := os.Args[len(os.Args)-1]
// 建立ICMP连接
conn, err := net.DialTimeout("ip:icmp", desIp, time.Duration(timeout)*time.Millisecond)
if err != nil {
// 如果连接建立失败,直接返回
log.Fatal(err)
return
}
defer conn.Close()
// 打印Ping信息
fmt.Printf(" 正在Ping %s [%s] 具有 %d 字节的数据:\n", desIp, conn.RemoteAddr(), size)
// 发送ICMP请求并接收响应
for i := 0; i < count; i++ {
t1 := time.Now() // 记录发送时间
icmp := &ICMP{
Type: typ,
Code: code,
Checksum: 0,
ID: 1,
SequenceNum: 1,
}
// 构造ICMP请求数据
data := make([]byte, size)
var buffer bytes.Buffer
binary.Write(&buffer, binary.BigEndian, icmp)
buffer.Write(data)
data = buffer.Bytes()
// 计算校验和
checkSum := checkSum(data)
data[2] = byte(checkSum >> 8) // 高位
data[3] = byte(checkSum & 0xff)
// 设置超时时间
conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Millisecond))
// 发送ICMP请求
n, err := conn.Write(data)
if err != nil {
log.Println(err)
continue
}
// 接收ICMP响应
buf := make([]byte, 65535)
n, err = conn.Read(buf)
if err != nil {
log.Println(err)
continue
}
ts := time.Since(t1).Milliseconds() // 计算响应时间
fmt.Printf("来自 %d.%d.%d.%d 的回复: 字节=%d 时间=%dms TTL=%d\n", buf[12], buf[13], buf[14], buf[15], n-28, ts, buf[8])
time.Sleep(time.Second) // 等待1秒再次发送
}
}
// getCommandArgs函数用于解析命令行参数
func getCommandArgs() {
flag.Int64Var(&timeout, "w", 1000, "请求超时时长,单位毫秒")
flag.IntVar(&size, "l", 32, "请求发送缓冲区大小,单位字节")
flag.IntVar(&count, "n", 4, "发送请求数")
flag.Parse()
}
// checkSum函数用于计算ICMP请求的校验和
func checkSum(data []byte) uint16 {
length := len(data)
index := 0
var sum uint32 = 0
for length > 1 {
sum += uint32(data[index])<<8 + uint32(data[index+1])
length -= 2
index += 2
}
if length != 0 {
sum += uint32(data[index])
}
hi16 := (sum >> 16)
for hi16 != 0 {
sum = hi16 + uint32(uint16(sum))
hi16 = (sum >> 16)
}
return uint16(^sum)
}
好好看
记住,运行时需要以管理员身份,才能解析socket
使用
go run .\main.go -w 150 -l 32 -n 8 www.baidu.com
测试
成功!
继续优化
把累计结果加上
package main
import (
"bytes"
"encoding/binary"
"flag"
"fmt"
"log"
"math"
"net"
"os"
"time"
)
// 定义全局变量
var (
timeout int64 // 请求超时时长,单位毫秒
size int // 请求发送缓冲区大小,单位字节
count int // 发送请求数
typ uint8 = 8 // ICMP请求类型
code uint8 = 0 // ICMP请求代码
sendCount int
successCount int
failCount int
minTs int64 = math.MaxInt64
maxTs int64
totalTs int64
)
// ICMP结构体定义ICMP请求的数据结构
type ICMP struct {
Type uint8
Code uint8
Checksum uint16
ID uint16
SequenceNum uint16
}
func main() {
getCommandArgs() // 获取命令行参数
// 取出最后一个参数,即目标IP地址
desIp := os.Args[len(os.Args)-1]
// 建立ICMP连接
conn, err := net.DialTimeout("ip:icmp", desIp, time.Duration(timeout)*time.Millisecond)
if err != nil {
// 如果连接建立失败,直接返回
log.Fatal(err)
return
}
defer conn.Close()
// 打印Ping信息
fmt.Printf(" 正在Ping %s [%s] 具有 %d 字节的数据:\n", desIp, conn.RemoteAddr(), size)
// 发送ICMP请求并接收响应
for i := 0; i < count; i++ {
sendCount++
t1 := time.Now() // 记录发送时间
icmp := &ICMP{
Type: typ,
Code: code,
Checksum: 0,
ID: 1,
SequenceNum: 1,
}
// 构造ICMP请求数据
data := make([]byte, size)
var buffer bytes.Buffer
binary.Write(&buffer, binary.BigEndian, icmp)
buffer.Write(data)
data = buffer.Bytes()
// 计算校验和
checkSum := checkSum(data)
data[2] = byte(checkSum >> 8) // 高位
data[3] = byte(checkSum & 0xff)
// 设置超时时间
conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Millisecond))
// 发送ICMP请求
n, err := conn.Write(data)
if err != nil {
failCount++
log.Println(err)
continue
}
// 接收ICMP响应
buf := make([]byte, 65535)
n, err = conn.Read(buf)
if err != nil {
failCount++
log.Println(err)
continue
}
successCount++
ts := time.Since(t1).Milliseconds() // 计算响应时间
if minTs > ts {
minTs = ts
}
if maxTs < ts {
maxTs = ts
}
totalTs += ts
fmt.Printf("来自 %d.%d.%d.%d 的回复: 字节=%d 时间=%dms TTL=%d\n", buf[12], buf[13], buf[14], buf[15], n-28, ts, buf[8])
time.Sleep(time.Second) // 等待1秒再次发送
}
//统计信息
fmt.Printf("%s 的 Ping 统计信息:\n数据包: 已发送 = %d,已接收 = %d,丢失 = %d (%.2f%% 丢失),\n往返行程的估计时间(以毫秒为单位):\n最短 = %dms,最长 = %dms,平均 = %dms",
conn.RemoteAddr(), sendCount, successCount, failCount, float64(failCount)/float64(sendCount)*100, minTs, maxTs, totalTs/int64(sendCount))
}
// getCommandArgs函数用于解析命令行参数
func getCommandArgs() {
flag.Int64Var(&timeout, "w", 1000, "请求超时时长,单位毫秒")
flag.IntVar(&size, "l", 32, "请求发送缓冲区大小,单位字节")
flag.IntVar(&count, "n", 4, "发送请求数")
flag.Parse()
}
// checkSum函数用于计算ICMP请求的校验和
func checkSum(data []byte) uint16 {
length := len(data)
index := 0
var sum uint32 = 0
for length > 1 {
sum += uint32(data[index])<<8 + uint32(data[index+1])
length -= 2
index += 2
}
if length != 0 {
sum += uint32(data[index])
}
hi16 := (sum >> 16)
for hi16 != 0 {
sum = hi16 + uint32(uint16(sum))
hi16 = (sum >> 16)
}
return uint16(^sum)
}
成功