From 38182647687148eb7c963dc57dc62456b12aa0ae Mon Sep 17 00:00:00 2001 From: Ben Kochie Date: Tue, 16 Mar 2021 14:26:55 +0100 Subject: [PATCH] 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 --- cmd/ping/ping.go | 6 ++-- go.mod | 5 +-- go.sum | 20 ++++++------ ping.go | 80 +++++++++++++++++++++++++++++++++--------------- ping_test.go | 68 ++++++++++++++++++++++++++++------------ 5 files changed, 120 insertions(+), 59 deletions(-) diff --git a/cmd/ping/ping.go b/cmd/ping/ping.go index ef7f7b6..73fb2a8 100644 --- a/cmd/ping/ping.go +++ b/cmd/ping/ping.go @@ -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) } } diff --git a/go.mod b/go.mod index 9a6710d..4caa5d7 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index ab1260f..32aed6c 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/ping.go b/ping.go index cbaede8..8e78952 100644 --- a/ping.go +++ b/ping.go @@ -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 diff --git a/ping_test.go b/ping_test.go index cfda052..3cefb66 100644 --- a/ping_test.go +++ b/ping_test.go @@ -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)