diff --git a/cmd/ping/ping.go b/cmd/ping/ping.go index 0bed3fc..5de5c34 100644 --- a/cmd/ping/ping.go +++ b/cmd/ping/ping.go @@ -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) diff --git a/ping.go b/ping.go index 623797b..27d54e1 100644 --- a/ping.go +++ b/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 diff --git a/ping_test.go b/ping_test.go index 319ff22..3eb9c22 100644 --- a/ping_test.go +++ b/ping_test.go @@ -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,