我正在制作一个 ping 库,主要是为了好玩。
我最近在我的实现中发现了一个错误,我没有检查收到的打包的 seq。如果发生超时,我现在通过丢弃数据包来修复它。
但是今天,我看到 ping 实用程序会打印收到的回复数据包,即使它们超时了。
Request timeout for icmp_seq 2
Request timeout for icmp_seq 3
64 bytes from 80.67.169.18: icmp_seq=2 ttl=58 time=2216.104 ms
64 bytes from 80.67.169.18: icmp_seq=3 ttl=58 time=1216.559 ms
我不知道在我的图书馆做什么。我应该保持实际行为,还是需要将其调整为“旧”的 ping 方式?
/*
Package libping provide the ability to send ICMP packets easily.
*/
package libping
import (
"bytes"
"net"
"os"
"time"
)
const (
ICMP_ECHO_REQUEST = 8
ICMP_ECHO_REPLY = 0
)
// The struct Response is the data returned by Pinguntil.
type Response struct {
Delay time.Duration
Error error
Destination string
Seq int
Readsize int
Writesize int
}
func makePingRequest(id, seq, pktlen int, filler []byte) []byte {
p := make([]byte, pktlen)
copy(p[8:], bytes.Repeat(filler, (pktlen-8)/len(filler)+1))
p[0] = ICMP_ECHO_REQUEST // type
p[1] = 0 // code
p[2] = 0 // cksum
p[3] = 0 // cksum
p[4] = uint8(id >> 8) // id
p[5] = uint8(id & 0xff) // id
p[6] = uint8(seq >> 8) // sequence
p[7] = uint8(seq & 0xff) // sequence
// calculate icmp checksum
cklen := len(p)
s := uint32(0)
for i := 0; i < (cklen - 1); i += 2 {
s += uint32(p[i+1])<<8 | uint32(p[i])
}
if cklen&1 == 1 {
s += uint32(p[cklen-1])
}
s = (s >> 16) + (s & 0xffff)
s = s + (s >> 16)
// place checksum back in header; using ^= avoids the
// assumption the checksum bytes are zero
p[2] ^= uint8(^s & 0xff)
p[3] ^= uint8(^s >> 8)
return p
}
func parsePingReply(p []byte) (id, seq, code int) {
id = int(p[24])<<8 | int(p[25])
seq = int(p[26])<<8 | int(p[27])
code = int(p[21])
return
}
// Pingonce send one ICMP echo packet to the destination, and return the latency.
// The function is made to be simple. Simple request, simple reply.
func Pingonce(destination string) (time.Duration, error) {
response := make(chan Response)
go Pinguntil(destination, 1, response, time.Second)
answer := <-response
return answer.Delay, answer.Error
}
// Pinguntil will send ICMP echo packets to the destination until the counter is reached, or forever if the counter is set to 0.
// The replies are given in the Response format.
// You can also adjust the delay between two ICMP echo packets with the variable delay.
func Pinguntil(destination string, count int, response chan Response, delay time.Duration) {
raddr, err := net.ResolveIPAddr("ip", destination)
if err != nil {
response <- Response{Delay: 0, Error: err, Destination: destination, Seq: 0}
close(response)
return
}
ipconn, err := net.Dial("ip:icmp", raddr.IP.String())
if err != nil {
response <- Response{Delay: 0, Error: err, Destination: raddr.IP.String(), Seq: 0}
close(response)
return
}
sendid := os.Getpid() & 0xffff
pingpktlen := 64
seq := 0
var elapsed time.Duration = 0
for ; seq < count || count == 0; seq++ {
elapsed = 0
if seq > 65535 { // The two bytes for seq. Don't overflow!
seq = 0
}
sendpkt := makePingRequest(sendid, seq, pingpktlen, []byte("Go Ping"))
start := time.Now()
writesize, err := ipconn.Write(sendpkt)
if err != nil || writesize != pingpktlen {
response <- Response{Delay: 0, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: 0}
time.Sleep(delay)
continue
}
ipconn.SetReadDeadline(time.Now().Add(time.Second * 1)) // 1 second
resp := make([]byte, 1024)
for {
readsize, err := ipconn.Read(resp)
elapsed = time.Now().Sub(start)
rid, rseq, rcode := parsePingReply(resp)
if err != nil {
response <- Response{Delay: 0, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: readsize}
break
} else if rcode != ICMP_ECHO_REPLY || rseq != seq || rid != sendid {
continue
} else {
response <- Response{Delay: elapsed, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: readsize}
break
}
}
time.Sleep(delay - elapsed)
}
close(response)
}
该库不是为特定用途而设计的。我将用它来做一些项目,但我想知道每个选择的论点。
正如我所看到的,实施第二个选项将更加困难。
谢谢!(如果我的帖子不清楚,请不要犹豫让我澄清,已经晚了。)
如果你想查看项目地址:这里