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:
Franck Lizaga 2022-11-25 11:46:27 +01:00
parent 434362500e
commit abdf2de029
3 changed files with 54 additions and 9 deletions

View File

@ -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
View File

@ -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

View File

@ -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,