Use a UUID for packet tracking

Replace the random 8 byte tracker with a 16 byte UUID.
* Implement as a slice so that it can be extended later.
* Deprecate the exported `Pinger.Tracker` value.

https://github.com/go-ping/ping/issues/142

Signed-off-by: Ben Kochie <superq@gmail.com>
This commit is contained in:
Ben Kochie 2021-03-16 14:26:55 +01:00 committed by Charlie Jonas
parent ff8be33200
commit 3818264768
5 changed files with 120 additions and 59 deletions

View File

@ -40,7 +40,7 @@ func main() {
timeout := flag.Duration("t", time.Second*100000, "")
interval := flag.Duration("i", time.Second, "")
count := flag.Int("c", -1, "")
size := flag.Int("s", 16, "")
size := flag.Int("s", 24, "")
privileged := flag.Bool("privileged", false, "")
flag.Usage = func() {
fmt.Print(usage)
@ -55,7 +55,7 @@ func main() {
host := flag.Arg(0)
pinger, err := ping.NewPinger(host)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
fmt.Println("ERROR:", err)
return
}
@ -93,6 +93,6 @@ func main() {
fmt.Printf("PING %s (%s):\n", pinger.Addr(), pinger.IPAddr())
err = pinger.Run()
if err != nil {
fmt.Printf("Failed to ping target host: %s", err)
fmt.Println("Failed to ping target host:", err)
}
}

5
go.mod
View File

@ -3,6 +3,7 @@ module github.com/go-ping/ping
go 1.14
require (
golang.org/x/net v0.0.0-20200904194848-62affa334b73
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
github.com/google/uuid v1.2.0
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
)

20
go.sum
View File

@ -1,12 +1,12 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005 h1:pDMpM2zh2MT0kHy037cKlSby2nEhD50SYqwQk76Nm40=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

80
ping.go
View File

@ -54,7 +54,6 @@ package ping
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"log"
@ -66,6 +65,7 @@ import (
"syscall"
"time"
"github.com/google/uuid"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
@ -74,7 +74,7 @@ import (
const (
timeSliceLength = 8
trackerLength = 8
trackerLength = len(uuid.UUID{})
protocolICMP = 1
protocolIPv6ICMP = 58
)
@ -87,22 +87,25 @@ var (
// New returns a new Pinger struct pointer.
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{})
return &Pinger{
Count: -1,
Interval: time.Second,
RecordRtts: true,
Size: timeSliceLength + trackerLength,
Timeout: time.Duration(math.MaxInt64),
Tracker: r.Uint64(),
addr: addr,
done: make(chan interface{}),
id: r.Intn(math.MaxUint16),
trackerUUIDs: []uuid.UUID{firstUUID},
ipaddr: nil,
ipv4: false,
network: "ip",
protocol: "udp",
awaitingSequences: map[int]struct{}{},
awaitingSequences: firstSequence,
logger: StdLogger{Logger: log.New(log.Writer(), log.Prefix(), log.Flags())},
}
}
@ -172,7 +175,7 @@ type Pinger struct {
// Size of packet being sent
Size int
// Tracker: Used to uniquely identify packets
// Tracker: Used to uniquely identify packets - Deprecated
Tracker uint64
// Source is the source IP address
@ -185,11 +188,14 @@ type Pinger struct {
ipaddr *net.IPAddr
addr string
// trackerUUIDs is the list of UUIDs being used for sending packets.
trackerUUIDs []uuid.UUID
ipv4 bool
id int
sequence int
// awaitingSequences are in-flight sequence numbers we keep track of to help remove duplicate receipts
awaitingSequences map[int]struct{}
awaitingSequences map[uuid.UUID]map[int]struct{}
// network is one of "ip", "ip4", or "ip6".
network string
// protocol is "icmp" or "udp".
@ -382,6 +388,9 @@ func (p *Pinger) SetLogger(logger Logger) {
func (p *Pinger) Run() error {
var conn packetConn
var err error
if p.Size < timeSliceLength+trackerLength {
return fmt.Errorf("size %d is less than minimum required size %d", p.Size, timeSliceLength+trackerLength)
}
if p.ipaddr == nil {
err = p.Resolve()
}
@ -582,6 +591,27 @@ func (p *Pinger) recvICMP(
}
}
// getPacketUUID scans the tracking slice for matches.
func (p *Pinger) getPacketUUID(pkt []byte) (*uuid.UUID, error) {
var packetUUID uuid.UUID
err := packetUUID.UnmarshalBinary(pkt[timeSliceLength : timeSliceLength+trackerLength])
if err != nil {
return nil, fmt.Errorf("error decoding tracking UUID: %w", err)
}
for _, item := range p.trackerUUIDs {
if item == packetUUID {
return &packetUUID, nil
}
}
return nil, nil
}
// getCurrentTrackerUUID grabs the latest tracker UUID.
func (p *Pinger) getCurrentTrackerUUID() uuid.UUID {
return p.trackerUUIDs[len(p.trackerUUIDs)-1]
}
func (p *Pinger) processPacket(recv *packet) error {
receivedAt := time.Now()
var proto int
@ -620,17 +650,16 @@ func (p *Pinger) processPacket(recv *packet) error {
len(pkt.Data), pkt.Data)
}
tracker := bytesToUint(pkt.Data[timeSliceLength:])
timestamp := bytesToTime(pkt.Data[:timeSliceLength])
if tracker != p.Tracker {
return nil
pktUUID, err := p.getPacketUUID(pkt.Data)
if err != nil || pktUUID == nil {
return err
}
timestamp := bytesToTime(pkt.Data[:timeSliceLength])
inPkt.Rtt = receivedAt.Sub(timestamp)
inPkt.Seq = pkt.Seq
// If we've already received this sequence, ignore it.
if _, inflight := p.awaitingSequences[pkt.Seq]; !inflight {
if _, inflight := p.awaitingSequences[*pktUUID][pkt.Seq]; !inflight {
p.PacketsRecvDuplicates++
if p.OnDuplicateRecv != nil {
p.OnDuplicateRecv(inPkt)
@ -638,7 +667,7 @@ func (p *Pinger) processPacket(recv *packet) error {
return nil
}
// remove it from the list of sequences we're waiting for so we don't get duplicates.
delete(p.awaitingSequences, pkt.Seq)
delete(p.awaitingSequences[*pktUUID], pkt.Seq)
p.updateStatistics(inPkt)
default:
// Very bad, not sure how this can happen
@ -659,7 +688,12 @@ func (p *Pinger) sendICMP(conn packetConn) error {
dst = &net.UDPAddr{IP: p.ipaddr.IP, Zone: p.ipaddr.Zone}
}
t := append(timeToBytes(time.Now()), uintToBytes(p.Tracker)...)
currentUUID := p.getCurrentTrackerUUID()
uuidEncoded, err := currentUUID.MarshalBinary()
if err != nil {
return fmt.Errorf("unable to marshal UUID binary: %w", err)
}
t := append(timeToBytes(time.Now()), uuidEncoded...)
if remainSize := p.Size - timeSliceLength - trackerLength; remainSize > 0 {
t = append(t, bytes.Repeat([]byte{1}, remainSize)...)
}
@ -701,9 +735,15 @@ func (p *Pinger) sendICMP(conn packetConn) error {
handler(outPkt)
}
// mark this sequence as in-flight
p.awaitingSequences[p.sequence] = struct{}{}
p.awaitingSequences[currentUUID][p.sequence] = struct{}{}
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.sequence = 0
}
break
}
@ -754,16 +794,6 @@ func timeToBytes(t time.Time) []byte {
return b
}
func bytesToUint(b []byte) uint64 {
return uint64(binary.BigEndian.Uint64(b))
}
func uintToBytes(tracker uint64) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, tracker)
return b
}
var seed int64 = time.Now().UnixNano()
// getSeed returns a goroutine-safe unique seed

View File

@ -3,12 +3,14 @@ package ping
import (
"bytes"
"errors"
"fmt"
"net"
"runtime/debug"
"sync/atomic"
"testing"
"time"
"github.com/google/uuid"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
@ -21,7 +23,12 @@ func TestProcessPacket(t *testing.T) {
shouldBe1++
}
data := append(timeToBytes(time.Now()), uintToBytes(pinger.Tracker)...)
currentUUID := pinger.getCurrentTrackerUUID()
uuidEncoded, err := currentUUID.MarshalBinary()
if err != nil {
t.Fatal(fmt.Sprintf("unable to marshal UUID binary: %s", err))
}
data := append(timeToBytes(time.Now()), uuidEncoded...)
if remainSize := pinger.Size - timeSliceLength - trackerLength; remainSize > 0 {
data = append(data, bytes.Repeat([]byte{1}, remainSize)...)
}
@ -31,7 +38,7 @@ func TestProcessPacket(t *testing.T) {
Seq: pinger.sequence,
Data: data,
}
pinger.awaitingSequences[pinger.sequence] = struct{}{}
pinger.awaitingSequences[currentUUID][pinger.sequence] = struct{}{}
msg := &icmp.Message{
Type: ipv4.ICMPTypeEchoReply,
@ -47,7 +54,7 @@ func TestProcessPacket(t *testing.T) {
ttl: 24,
}
err := pinger.processPacket(&pkt)
err = pinger.processPacket(&pkt)
AssertNoError(t, err)
AssertTrue(t, shouldBe1 == 1)
}
@ -60,7 +67,11 @@ func TestProcessPacket_IgnoreNonEchoReplies(t *testing.T) {
shouldBe0++
}
data := append(timeToBytes(time.Now()), uintToBytes(pinger.Tracker)...)
currentUUID, err := pinger.getCurrentTrackerUUID().MarshalBinary()
if err != nil {
t.Fatal(fmt.Sprintf("unable to marshal UUID binary: %s", err))
}
data := append(timeToBytes(time.Now()), currentUUID...)
if remainSize := pinger.Size - timeSliceLength - trackerLength; remainSize > 0 {
data = append(data, bytes.Repeat([]byte{1}, remainSize)...)
}
@ -85,7 +96,7 @@ func TestProcessPacket_IgnoreNonEchoReplies(t *testing.T) {
ttl: 24,
}
err := pinger.processPacket(&pkt)
err = pinger.processPacket(&pkt)
AssertNoError(t, err)
AssertTrue(t, shouldBe0 == 0)
}
@ -99,7 +110,11 @@ func TestProcessPacket_IDMismatch(t *testing.T) {
shouldBe0++
}
data := append(timeToBytes(time.Now()), uintToBytes(pinger.Tracker)...)
currentUUID, err := pinger.getCurrentTrackerUUID().MarshalBinary()
if err != nil {
t.Fatal(fmt.Sprintf("unable to marshal UUID binary: %s", err))
}
data := append(timeToBytes(time.Now()), currentUUID...)
if remainSize := pinger.Size - timeSliceLength - trackerLength; remainSize > 0 {
data = append(data, bytes.Repeat([]byte{1}, remainSize)...)
}
@ -124,7 +139,7 @@ func TestProcessPacket_IDMismatch(t *testing.T) {
ttl: 24,
}
err := pinger.processPacket(&pkt)
err = pinger.processPacket(&pkt)
AssertNoError(t, err)
AssertTrue(t, shouldBe0 == 0)
}
@ -137,7 +152,11 @@ func TestProcessPacket_TrackerMismatch(t *testing.T) {
shouldBe0++
}
data := append(timeToBytes(time.Now()), uintToBytes(999)...)
testUUID, err := uuid.New().MarshalBinary()
if err != nil {
t.Fatal(fmt.Sprintf("unable to marshal UUID binary: %s", err))
}
data := append(timeToBytes(time.Now()), testUUID...)
if remainSize := pinger.Size - timeSliceLength - trackerLength; remainSize > 0 {
data = append(data, bytes.Repeat([]byte{1}, remainSize)...)
}
@ -162,7 +181,7 @@ func TestProcessPacket_TrackerMismatch(t *testing.T) {
ttl: 24,
}
err := pinger.processPacket(&pkt)
err = pinger.processPacket(&pkt)
AssertNoError(t, err)
AssertTrue(t, shouldBe0 == 0)
}
@ -171,7 +190,11 @@ func TestProcessPacket_LargePacket(t *testing.T) {
pinger := makeTestPinger()
pinger.Size = 4096
data := append(timeToBytes(time.Now()), uintToBytes(pinger.Tracker)...)
currentUUID, err := pinger.getCurrentTrackerUUID().MarshalBinary()
if err != nil {
t.Fatal(fmt.Sprintf("unable to marshal UUID binary: %s", err))
}
data := append(timeToBytes(time.Now()), currentUUID...)
if remainSize := pinger.Size - timeSliceLength - trackerLength; remainSize > 0 {
data = append(data, bytes.Repeat([]byte{1}, remainSize)...)
}
@ -196,7 +219,7 @@ func TestProcessPacket_LargePacket(t *testing.T) {
ttl: 24,
}
err := pinger.processPacket(&pkt)
err = pinger.processPacket(&pkt)
AssertNoError(t, err)
}
@ -461,7 +484,6 @@ func makeTestPinger() *Pinger {
pinger.addr = "127.0.0.1"
pinger.protocol = "icmp"
pinger.id = 123
pinger.Tracker = 456
pinger.Size = 0
return pinger
@ -520,17 +542,20 @@ func BenchmarkProcessPacket(b *testing.B) {
pinger.addr = "127.0.0.1"
pinger.protocol = "ip4:icmp"
pinger.id = 123
pinger.Tracker = 456
t := append(timeToBytes(time.Now()), uintToBytes(pinger.Tracker)...)
currentUUID, err := pinger.getCurrentTrackerUUID().MarshalBinary()
if err != nil {
b.Fatal(fmt.Sprintf("unable to marshal UUID binary: %s", err))
}
data := append(timeToBytes(time.Now()), currentUUID...)
if remainSize := pinger.Size - timeSliceLength - trackerLength; remainSize > 0 {
t = append(t, bytes.Repeat([]byte{1}, remainSize)...)
data = append(data, bytes.Repeat([]byte{1}, remainSize)...)
}
body := &icmp.Echo{
ID: pinger.id,
Seq: pinger.sequence,
Data: t,
Data: data,
}
msg := &icmp.Message{
@ -567,7 +592,12 @@ func TestProcessPacket_IgnoresDuplicateSequence(t *testing.T) {
dups++
}
data := append(timeToBytes(time.Now()), uintToBytes(pinger.Tracker)...)
currentUUID := pinger.getCurrentTrackerUUID()
uuidEncoded, err := currentUUID.MarshalBinary()
if err != nil {
t.Fatal(fmt.Sprintf("unable to marshal UUID binary: %s", err))
}
data := append(timeToBytes(time.Now()), uuidEncoded...)
if remainSize := pinger.Size - timeSliceLength - trackerLength; remainSize > 0 {
data = append(data, bytes.Repeat([]byte{1}, remainSize)...)
}
@ -578,7 +608,7 @@ func TestProcessPacket_IgnoresDuplicateSequence(t *testing.T) {
Data: data,
}
// register the sequence as sent
pinger.awaitingSequences[0] = struct{}{}
pinger.awaitingSequences[currentUUID][0] = struct{}{}
msg := &icmp.Message{
Type: ipv4.ICMPTypeEchoReply,
@ -594,7 +624,7 @@ func TestProcessPacket_IgnoresDuplicateSequence(t *testing.T) {
ttl: 24,
}
err := pinger.processPacket(&pkt)
err = pinger.processPacket(&pkt)
AssertNoError(t, err)
// receive a duplicate
err = pinger.processPacket(&pkt)