mirror of
https://github.com/go-ping/ping.git
synced 2025-09-01 05:17:02 +00:00
Flexible ping interval option (allows to send next ping packet as soon as the previous packet echo answer is received)
Signed-off-by: Evgeniy Makeev <evgeniym@fb.com>
This commit is contained in:
58
ping.go
58
ping.go
@@ -131,6 +131,9 @@ type Pinger struct {
|
|||||||
// interrupted.
|
// interrupted.
|
||||||
Count int
|
Count int
|
||||||
|
|
||||||
|
// FlexibleInterval flag tells pinger to send next packet as soon as it
|
||||||
|
// receives echo response to a previous packet
|
||||||
|
FlexibleInterval bool
|
||||||
// Debug runs in debug mode
|
// Debug runs in debug mode
|
||||||
Debug bool
|
Debug bool
|
||||||
|
|
||||||
@@ -466,7 +469,8 @@ func (p *Pinger) runLoop(
|
|||||||
timeout.Stop()
|
timeout.Stop()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := p.sendICMP(conn); err != nil {
|
err := p.sendICMP(conn)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,21 +483,32 @@ func (p *Pinger) runLoop(
|
|||||||
return nil
|
return nil
|
||||||
|
|
||||||
case r := <-recvCh:
|
case r := <-recvCh:
|
||||||
err := p.processPacket(r)
|
err = p.processPacket(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// FIXME: this logs as FATAL but continues
|
if nce, nonCritical := err.(*NonCriticalError); !nonCritical {
|
||||||
logger.Fatalf("processing received packet: %s", err)
|
logger.Errorf("processing received packet: %v", err)
|
||||||
|
} else {
|
||||||
|
logger.Debugf("%v for address: %s", nce, p.ipaddr)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if p.FlexibleInterval {
|
||||||
|
interval.Reset(p.Interval)
|
||||||
|
if p.Count <= 0 || p.PacketsSent < p.Count {
|
||||||
|
err = p.sendICMP(conn)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("sending packet: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-interval.C:
|
case <-interval.C:
|
||||||
if p.Count > 0 && p.PacketsSent >= p.Count {
|
if p.Count > 0 && p.PacketsSent >= p.Count {
|
||||||
interval.Stop()
|
interval.Stop()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err := p.sendICMP(conn)
|
err = p.sendICMP(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// FIXME: this logs as FATAL but continues
|
logger.Errorf("sending packet: %v", err)
|
||||||
logger.Fatalf("sending packet: %s", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if p.Count > 0 && p.PacketsRecv >= p.Count {
|
if p.Count > 0 && p.PacketsRecv >= p.Count {
|
||||||
@@ -642,6 +657,24 @@ func (p *Pinger) getCurrentTrackerUUID() uuid.UUID {
|
|||||||
return p.trackerUUIDs[len(p.trackerUUIDs)-1]
|
return p.trackerUUIDs[len(p.trackerUUIDs)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NonCriticalError is a class of recoverable/non-critical errors
|
||||||
|
type NonCriticalError struct {
|
||||||
|
string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *NonCriticalError) Error() string {
|
||||||
|
if e == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
return e.string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
notEchoPacket = &NonCriticalError{"not an echo packet"}
|
||||||
|
mismatchedEchoPacketId = &NonCriticalError{"mismatched echo packet ID"}
|
||||||
|
duplicateEchoPacket = &NonCriticalError{"duplicate echo packet ID"}
|
||||||
|
)
|
||||||
|
|
||||||
func (p *Pinger) processPacket(recv *packet) error {
|
func (p *Pinger) processPacket(recv *packet) error {
|
||||||
receivedAt := time.Now()
|
receivedAt := time.Now()
|
||||||
var proto int
|
var proto int
|
||||||
@@ -659,7 +692,7 @@ func (p *Pinger) processPacket(recv *packet) error {
|
|||||||
|
|
||||||
if m.Type != ipv4.ICMPTypeEchoReply && m.Type != ipv6.ICMPTypeEchoReply {
|
if m.Type != ipv4.ICMPTypeEchoReply && m.Type != ipv6.ICMPTypeEchoReply {
|
||||||
// Not an echo reply, ignore it
|
// Not an echo reply, ignore it
|
||||||
return nil
|
return notEchoPacket
|
||||||
}
|
}
|
||||||
|
|
||||||
inPkt := &Packet{
|
inPkt := &Packet{
|
||||||
@@ -673,7 +706,7 @@ func (p *Pinger) processPacket(recv *packet) error {
|
|||||||
switch pkt := m.Body.(type) {
|
switch pkt := m.Body.(type) {
|
||||||
case *icmp.Echo:
|
case *icmp.Echo:
|
||||||
if !p.matchID(pkt.ID) {
|
if !p.matchID(pkt.ID) {
|
||||||
return nil
|
return mismatchedEchoPacketId
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(pkt.Data) < timeSliceLength+trackerLength {
|
if len(pkt.Data) < timeSliceLength+trackerLength {
|
||||||
@@ -691,11 +724,14 @@ func (p *Pinger) processPacket(recv *packet) error {
|
|||||||
inPkt.Seq = pkt.Seq
|
inPkt.Seq = pkt.Seq
|
||||||
// If we've already received this sequence, ignore it.
|
// If we've already received this sequence, ignore it.
|
||||||
if _, inflight := p.awaitingSequences[*pktUUID][pkt.Seq]; !inflight {
|
if _, inflight := p.awaitingSequences[*pktUUID][pkt.Seq]; !inflight {
|
||||||
|
p.statsMu.Lock()
|
||||||
p.PacketsRecvDuplicates++
|
p.PacketsRecvDuplicates++
|
||||||
|
p.statsMu.Unlock()
|
||||||
|
|
||||||
if p.OnDuplicateRecv != nil {
|
if p.OnDuplicateRecv != nil {
|
||||||
p.OnDuplicateRecv(inPkt)
|
p.OnDuplicateRecv(inPkt)
|
||||||
}
|
}
|
||||||
return nil
|
return duplicateEchoPacket
|
||||||
}
|
}
|
||||||
// remove it from the list of sequences we're waiting for so we don't get duplicates.
|
// remove it from the list of sequences we're waiting for so we don't get duplicates.
|
||||||
delete(p.awaitingSequences[*pktUUID], pkt.Seq)
|
delete(p.awaitingSequences[*pktUUID], pkt.Seq)
|
||||||
|
96
ping_test.go
96
ping_test.go
@@ -97,7 +97,7 @@ func TestProcessPacket_IgnoreNonEchoReplies(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = pinger.processPacket(&pkt)
|
err = pinger.processPacket(&pkt)
|
||||||
AssertNoError(t, err)
|
AssertTrue(t, err == notEchoPacket)
|
||||||
AssertTrue(t, shouldBe0 == 0)
|
AssertTrue(t, shouldBe0 == 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ func TestProcessPacket_IDMismatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = pinger.processPacket(&pkt)
|
err = pinger.processPacket(&pkt)
|
||||||
AssertNoError(t, err)
|
AssertTrue(t, err == mismatchedEchoPacketId)
|
||||||
AssertTrue(t, shouldBe0 == 0)
|
AssertTrue(t, shouldBe0 == 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +220,7 @@ func TestProcessPacket_LargePacket(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = pinger.processPacket(&pkt)
|
err = pinger.processPacket(&pkt)
|
||||||
AssertNoError(t, err)
|
AssertTrue(t, err == duplicateEchoPacket)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessPacket_PacketTooSmall(t *testing.T) {
|
func TestProcessPacket_PacketTooSmall(t *testing.T) {
|
||||||
@@ -628,7 +628,7 @@ func TestProcessPacket_IgnoresDuplicateSequence(t *testing.T) {
|
|||||||
AssertNoError(t, err)
|
AssertNoError(t, err)
|
||||||
// receive a duplicate
|
// receive a duplicate
|
||||||
err = pinger.processPacket(&pkt)
|
err = pinger.processPacket(&pkt)
|
||||||
AssertNoError(t, err)
|
AssertTrue(t, err == duplicateEchoPacket)
|
||||||
|
|
||||||
AssertTrue(t, shouldBe0 == 1)
|
AssertTrue(t, shouldBe0 == 1)
|
||||||
AssertTrue(t, dups == 1)
|
AssertTrue(t, dups == 1)
|
||||||
@@ -763,3 +763,91 @@ func TestRunOK(t *testing.T) {
|
|||||||
AssertTrue(t, stats.MinRtt >= 10*time.Millisecond)
|
AssertTrue(t, stats.MinRtt >= 10*time.Millisecond)
|
||||||
AssertTrue(t, stats.MinRtt <= 12*time.Millisecond)
|
AssertTrue(t, stats.MinRtt <= 12*time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type testPacketConnNoDelay struct {
|
||||||
|
testPacketConn
|
||||||
|
writeDone int32
|
||||||
|
buf []byte
|
||||||
|
dst net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testPacketConnNoDelay) WriteTo(b []byte, dst net.Addr) (int, error) {
|
||||||
|
c.buf = make([]byte, len(b))
|
||||||
|
c.dst = dst
|
||||||
|
n := copy(c.buf, b)
|
||||||
|
atomic.StoreInt32(&c.writeDone, 1)
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testPacketConnNoDelay) ReadFrom(b []byte) (n int, ttl int, src net.Addr, err error) {
|
||||||
|
if atomic.LoadInt32(&c.writeDone) == 0 {
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
return 0, 0, nil, nil
|
||||||
|
}
|
||||||
|
atomic.StoreInt32(&c.writeDone, 0)
|
||||||
|
msg, err := icmp.ParseMessage(ipv4.ICMPTypeEcho.Protocol(), c.buf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, nil, err
|
||||||
|
}
|
||||||
|
msg.Type = ipv4.ICMPTypeEchoReply
|
||||||
|
buf, err := msg.Marshal(nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, nil, err
|
||||||
|
}
|
||||||
|
n = copy(b, buf)
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
atomic.StoreInt32(&c.writeDone, 0)
|
||||||
|
return n, 64, c.dst, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlexibleInterval(t *testing.T) {
|
||||||
|
// with flexible interval
|
||||||
|
pinger := New("10.20.30.40")
|
||||||
|
pinger.Count = 3
|
||||||
|
pinger.FlexibleInterval = true
|
||||||
|
pinger.Interval = 10 * time.Millisecond
|
||||||
|
err := pinger.Resolve()
|
||||||
|
AssertNoError(t, err)
|
||||||
|
|
||||||
|
conn := new(testPacketConnNoDelay)
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
err = pinger.run(conn)
|
||||||
|
runDuration := time.Now().Sub(start)
|
||||||
|
AssertTrue(t, err == nil)
|
||||||
|
|
||||||
|
stats := pinger.Statistics()
|
||||||
|
AssertTrue(t, stats != nil)
|
||||||
|
if stats == nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
AssertTrue(t, stats.PacketsSent == 3)
|
||||||
|
AssertTrue(t, stats.PacketsRecv == 3)
|
||||||
|
AssertTrue(t, stats.MinRtt > 0)
|
||||||
|
AssertTrue(t, runDuration < 20*time.Millisecond)
|
||||||
|
|
||||||
|
// without flexible interval
|
||||||
|
pinger = New("10.21.33.44")
|
||||||
|
pinger.Count = 3
|
||||||
|
pinger.Interval = 10 * time.Millisecond
|
||||||
|
err = pinger.Resolve()
|
||||||
|
AssertNoError(t, err)
|
||||||
|
|
||||||
|
conn = new(testPacketConnNoDelay)
|
||||||
|
|
||||||
|
start = time.Now()
|
||||||
|
err = pinger.run(conn)
|
||||||
|
runDuration = time.Now().Sub(start)
|
||||||
|
|
||||||
|
AssertTrue(t, err == nil)
|
||||||
|
|
||||||
|
stats = pinger.Statistics()
|
||||||
|
AssertTrue(t, stats != nil)
|
||||||
|
if stats == nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
AssertTrue(t, stats.PacketsSent == 3)
|
||||||
|
AssertTrue(t, stats.PacketsRecv == 3)
|
||||||
|
AssertTrue(t, runDuration >= 20*time.Millisecond)
|
||||||
|
AssertTrue(t, runDuration < 40*time.Millisecond)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user