Files
zogo/nettest/ping.go
T
4566704 8a038d4df3 feat(nettest): 自 go-hua 迁入网络连通性探测
- TCP 端口探测 + 原始 ICMP Ping, 持续统计成功率与延迟
- 附测试、examples/nettest 例程与包 README
2026-09-20 12:25:37 +08:00

154 lines
3.4 KiB
Go

package nettest
import (
"bytes"
"encoding/binary"
"log"
"net"
"strconv"
"time"
)
// ICMP ICMP 报文头
type ICMP struct {
Type uint8
Code uint8
Checksum uint16
Identifier uint16
SequenceNum uint16
}
// Ping 单次探测器:port 为 0 时走 ICMP,否则走 TCP 端口探测
type Ping struct {
addr string
port int
timeout int64
icmp ICMP
size int
}
// NewPing 创建探测器,timeout 为超时毫秒数
func NewPing(addr string, port int, timeout int64) *Ping {
p := new(Ping)
p.addr = addr
p.port = port
p.timeout = timeout
p.size = 32
// icmp
p.icmp.Type = 8
p.icmp.Code = 0
p.icmp.Checksum = 0
p.icmp.Identifier = 1
p.icmp.SequenceNum = 0
return p
}
// Set 修改探测目标与超时
func (p *Ping) Set(addr string, port int, timeout int64) {
p.addr = addr
p.port = port
p.timeout = timeout
}
// Test 执行一次探测,返回耗时毫秒数;失败返回 -1
func (p *Ping) Test() int {
et := 0
if p.port == 0 {
et = p.icmpEcho()
} else {
et = p.portCheck()
}
return et
}
// icmpEcho 发送 ICMP Echo 并等待回包,返回耗时毫秒数(需管理员权限)
func (p *Ping) icmpEcho() int {
conn, err := net.DialTimeout("ip:icmp", p.addr, time.Duration(p.timeout)*time.Millisecond)
if err != nil {
log.Println(err)
return -1
}
defer conn.Close()
var buffer bytes.Buffer
binary.Write(&buffer, binary.BigEndian, p.icmp) // 以大端模式写入
data := make([]byte, p.size) //
buffer.Write(data)
data = buffer.Bytes()
p.icmp.SequenceNum++
// 检验和设为0
data[2] = byte(0)
data[3] = byte(0)
data[6] = byte(p.icmp.SequenceNum >> 8)
data[7] = byte(p.icmp.SequenceNum)
p.icmp.Checksum = CheckSum(data)
data[2] = byte(p.icmp.Checksum >> 8)
data[3] = byte(p.icmp.Checksum)
// 开始时间
t1 := time.Now()
err = conn.SetDeadline(t1.Add(time.Duration(time.Duration(p.timeout) * time.Millisecond)))
if err != nil {
//log.Println(err)
return -1
}
_, err = conn.Write(data)
if err != nil {
//log.Println(err)
return -1
}
buf := make([]byte, 65535)
_, err = conn.Read(buf)
if err != nil {
//fmt.Println("请求超时。")
return -1
}
et := int(time.Since(t1) / 1000000)
return et
}
// CheckSum 计算 ICMP 校验和
func CheckSum(data []byte) uint16 {
var sum uint32
var length = len(data)
var index int
for length > 1 { // 溢出部分直接去除
sum += uint32(data[index])<<8 + uint32(data[index+1])
index += 2
length -= 2
}
if length == 1 {
sum += uint32(data[index])
}
// CheckSum的值是16位,计算是将高16位加低16位,得到的结果进行重复以该方式进行计算,直到高16位为0
/*
sum的最大情况是:ffffffff
第一次高16位+低16位:ffff + ffff = 1fffe
第二次高16位+低16位:0001 + fffe = ffff
即推出一个结论,只要第一次高16位+低16位的结果,再进行之前的计算结果用到高16位+低16位,即可处理溢出情况
*/
sum = uint32(sum>>16) + uint32(sum)
sum = uint32(sum>>16) + uint32(sum)
return uint16(^sum)
}
// portCheck TCP 端口探测,返回连接耗时毫秒数;失败返回 -1
func (p *Ping) portCheck() int {
// 开始时间
t1 := time.Now()
conn, err := net.DialTimeout("tcp", p.addr+":"+strconv.Itoa(p.port), time.Duration(p.timeout)*time.Millisecond)
et := int(time.Since(t1) / 1000000)
if err != nil {
//log.Println(err)
//fmt.Println("请求超时。")
return -1
}
defer conn.Close()
return et
}