154 lines
3.4 KiB
Go
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
|
|
}
|