mirror of
https://github.com/go-ping/ping.git
synced 2025-07-05 18:26:18 +00:00
Add a packet timeout option :
In order to to this we will store the sent time in the awaitingSequences struct. As a bonus, it will give us a better accuracy as we can store the sent time just before sending the packet instead of just before building the packet content. So we gain some CPU cycles.
This commit is contained in:
parent
434362500e
commit
abdf2de029
@ -38,6 +38,7 @@ Examples:
|
||||
|
||||
func main() {
|
||||
timeout := flag.Duration("t", time.Second*100000, "")
|
||||
packettimeout := flag.Duration("p", time.Second*15, "")
|
||||
interval := flag.Duration("i", time.Second, "")
|
||||
count := flag.Int("c", -1, "")
|
||||
size := flag.Int("s", 24, "")
|
||||
@ -77,6 +78,17 @@ func main() {
|
||||
fmt.Printf("%d bytes from %s: icmp_seq=%d time=%v ttl=%v (DUP!)\n",
|
||||
pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt, pkt.Ttl)
|
||||
}
|
||||
pinger.OnTimeout = func(pid int, pseq int) {
|
||||
inPkt := ping.Packet{
|
||||
Rtt: *packettimeout,
|
||||
Ttl: -1,
|
||||
ID: pid,
|
||||
Seq: pseq,
|
||||
IPAddr: pinger.IPAddr(),
|
||||
Addr: pinger.Addr(),
|
||||
}
|
||||
fmt.Printf("Timeout! : icmp_seq=%d ttl=%v time=>%v\n", inPkt.Seq, inPkt.Ttl, inPkt.Rtt)
|
||||
}
|
||||
pinger.OnFinish = func(stats *ping.Statistics) {
|
||||
fmt.Printf("\n--- %s ping statistics ---\n", stats.Addr)
|
||||
fmt.Printf("%d packets transmitted, %d packets received, %d duplicates, %v%% packet loss\n",
|
||||
@ -89,6 +101,7 @@ func main() {
|
||||
pinger.Size = *size
|
||||
pinger.Interval = *interval
|
||||
pinger.Timeout = *timeout
|
||||
pinger.PktTimeout = *packettimeout
|
||||
pinger.TTL = *ttl
|
||||
pinger.SetPrivileged(*privileged)
|
||||
|
||||
|
46
ping.go
46
ping.go
@ -88,14 +88,15 @@ var (
|
||||
func New(addr string) *Pinger {
|
||||
r := rand.New(rand.NewSource(getSeed()))
|
||||
firstUUID := uuid.New()
|
||||
var firstSequence = map[uuid.UUID]map[int]struct{}{}
|
||||
firstSequence[firstUUID] = make(map[int]struct{})
|
||||
var firstSequence = map[uuid.UUID]map[int]uint64{}
|
||||
firstSequence[firstUUID] = make(map[int]uint64)
|
||||
return &Pinger{
|
||||
Count: -1,
|
||||
Interval: time.Second,
|
||||
RecordRtts: true,
|
||||
Size: timeSliceLength + trackerLength,
|
||||
Timeout: time.Duration(math.MaxInt64),
|
||||
PktTimeout: time.Duration(math.MaxInt64),
|
||||
|
||||
addr: addr,
|
||||
done: make(chan interface{}),
|
||||
@ -126,6 +127,10 @@ type Pinger struct {
|
||||
// packets have been received.
|
||||
Timeout time.Duration
|
||||
|
||||
// PktTimeout specifies a timeout of a single ping
|
||||
// packets have been received.
|
||||
PktTimeout time.Duration
|
||||
|
||||
// Count tells pinger to stop after sending (and receiving) Count echo
|
||||
// packets. If this option is not specified, pinger will operate until
|
||||
// interrupted.
|
||||
@ -167,6 +172,9 @@ type Pinger struct {
|
||||
// OnRecv is called when Pinger receives and processes a packet
|
||||
OnRecv func(*Packet)
|
||||
|
||||
// OnTimeout is called when a single packet ends up on timeout
|
||||
OnTimeout func(int, int)
|
||||
|
||||
// OnFinish is called when Pinger exits
|
||||
OnFinish func(*Statistics)
|
||||
|
||||
@ -196,7 +204,7 @@ type Pinger struct {
|
||||
id int
|
||||
sequence int
|
||||
// awaitingSequences are in-flight sequence numbers we keep track of to help remove duplicate receipts
|
||||
awaitingSequences map[uuid.UUID]map[int]struct{}
|
||||
awaitingSequences map[uuid.UUID]map[int]uint64
|
||||
// network is one of "ip", "ip4", or "ip6".
|
||||
network string
|
||||
// protocol is "icmp" or "udp".
|
||||
@ -460,6 +468,7 @@ func (p *Pinger) runLoop(
|
||||
}
|
||||
|
||||
timeout := time.NewTicker(p.Timeout)
|
||||
ptimeout := time.NewTicker(p.PktTimeout)
|
||||
interval := time.NewTicker(p.Interval)
|
||||
defer func() {
|
||||
interval.Stop()
|
||||
@ -478,6 +487,16 @@ func (p *Pinger) runLoop(
|
||||
case <-timeout.C:
|
||||
return nil
|
||||
|
||||
case <-ptimeout.C:
|
||||
for ukey, uval := range p.awaitingSequences {
|
||||
for skey, sval := range uval {
|
||||
if currentTimestamp()-sval > uint64(p.PktTimeout.Nanoseconds()) {
|
||||
p.PacketTimeout(p.ID(), skey)
|
||||
delete(p.awaitingSequences[ukey], skey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case r := <-recvCh:
|
||||
err := p.processPacket(r)
|
||||
if err != nil {
|
||||
@ -517,6 +536,13 @@ func (p *Pinger) Stop() {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pinger) PacketTimeout(puuid int, pseq int) {
|
||||
handler := p.OnTimeout
|
||||
if handler != nil {
|
||||
handler(puuid, pseq)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pinger) finish() {
|
||||
handler := p.OnFinish
|
||||
if handler != nil {
|
||||
@ -674,15 +700,20 @@ func (p *Pinger) processPacket(recv *packet) error {
|
||||
}
|
||||
|
||||
timestamp := BytesToTimestamp(pkt.Data[:timeSliceLength])
|
||||
inPkt.Rtt, _ = time.ParseDuration(strconv.FormatUint(receivedAt-timestamp, 10) + "ns")
|
||||
inPkt.Seq = pkt.Seq
|
||||
// If we've already received this sequence, ignore it.
|
||||
if _, inflight := p.awaitingSequences[*pktUUID][pkt.Seq]; !inflight {
|
||||
if timereceived, inflight := p.awaitingSequences[*pktUUID][pkt.Seq]; !inflight {
|
||||
// As the send time is not available in the awaitingSequences table, we will
|
||||
// use the time stored in the packet data
|
||||
inPkt.Rtt, _ = time.ParseDuration(strconv.FormatUint(receivedAt-timestamp, 10) + "ns")
|
||||
p.PacketsRecvDuplicates++
|
||||
if p.OnDuplicateRecv != nil {
|
||||
p.OnDuplicateRecv(inPkt)
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
// We use the time stored in awaitingSequences to get the Rtt, instead of the one stored in packet
|
||||
inPkt.Rtt, _ = time.ParseDuration(strconv.FormatUint(receivedAt-timereceived, 10) + "ns")
|
||||
}
|
||||
// remove it from the list of sequences we're waiting for so we don't get duplicates.
|
||||
delete(p.awaitingSequences[*pktUUID], pkt.Seq)
|
||||
@ -734,6 +765,7 @@ func (p *Pinger) sendICMP(conn packetConn) error {
|
||||
}
|
||||
|
||||
for {
|
||||
timesent := currentTimestamp()
|
||||
if _, err := conn.WriteTo(msgBytes, dst); err != nil {
|
||||
if neterr, ok := err.(*net.OpError); ok {
|
||||
if neterr.Err == syscall.ENOBUFS {
|
||||
@ -754,13 +786,13 @@ func (p *Pinger) sendICMP(conn packetConn) error {
|
||||
handler(outPkt)
|
||||
}
|
||||
// mark this sequence as in-flight
|
||||
p.awaitingSequences[currentUUID][p.sequence] = struct{}{}
|
||||
p.awaitingSequences[currentUUID][p.sequence] = timesent
|
||||
p.PacketsSent++
|
||||
p.sequence++
|
||||
if p.sequence > 65535 {
|
||||
newUUID := uuid.New()
|
||||
p.trackerUUIDs = append(p.trackerUUIDs, newUUID)
|
||||
p.awaitingSequences[newUUID] = make(map[int]struct{})
|
||||
p.awaitingSequences[newUUID] = make(map[int]uint64)
|
||||
p.sequence = 0
|
||||
}
|
||||
break
|
||||
|
@ -38,7 +38,7 @@ func TestProcessPacket(t *testing.T) {
|
||||
Seq: pinger.sequence,
|
||||
Data: data,
|
||||
}
|
||||
pinger.awaitingSequences[currentUUID][pinger.sequence] = struct{}{}
|
||||
pinger.awaitingSequences[currentUUID][pinger.sequence] = currentTimestamp()
|
||||
|
||||
msg := &icmp.Message{
|
||||
Type: ipv4.ICMPTypeEchoReply,
|
||||
@ -608,7 +608,7 @@ func TestProcessPacket_IgnoresDuplicateSequence(t *testing.T) {
|
||||
Data: data,
|
||||
}
|
||||
// register the sequence as sent
|
||||
pinger.awaitingSequences[currentUUID][0] = struct{}{}
|
||||
pinger.awaitingSequences[currentUUID][0] = currentTimestamp()
|
||||
|
||||
msg := &icmp.Message{
|
||||
Type: ipv4.ICMPTypeEchoReply,
|
||||
|
Loading…
Reference in New Issue
Block a user