diff --git a/discover/common.go b/discover/common.go index 44976be..d8d866a 100644 --- a/discover/common.go +++ b/discover/common.go @@ -37,7 +37,7 @@ type UDPConn interface { } type V5Config struct { - ProtocolID [6]byte + ProtocolID *[6]byte } // Config holds settings for the discovery listener. @@ -51,9 +51,8 @@ type Config struct { Unhandled chan<- ReadPacket // unhandled packets are sent on this channel Log log.Logger // if set, log messages go here ValidSchemes enr.IdentityScheme // allowed identity schemes + V5Config V5Config // DiscV5 settings Clock mclock.Clock - ValidNodeFn func(enode.Node) bool // function to validate a node before it's added to routing tables - V5Config V5Config // DiscV5 settings } func (cfg Config) withDefaults() Config { @@ -66,8 +65,8 @@ func (cfg Config) withDefaults() Config { if cfg.Clock == nil { cfg.Clock = mclock.System{} } - if len(cfg.V5Config.ProtocolID) == 0 { - cfg.V5Config.ProtocolID = v5wire.DefaultProtocolID + if cfg.V5Config.ProtocolID == nil { + cfg.V5Config.ProtocolID = &v5wire.DefaultProtocolID } return cfg } diff --git a/discover/lookup.go b/discover/lookup.go index 9ab4a71..b8d97b4 100644 --- a/discover/lookup.go +++ b/discover/lookup.go @@ -18,6 +18,7 @@ package discover import ( "context" + "errors" "time" "github.com/ethereum/go-ethereum/p2p/enode" @@ -141,7 +142,7 @@ func (it *lookup) slowdown() { func (it *lookup) query(n *node, reply chan<- []*node) { fails := it.tab.db.FindFails(n.ID(), n.IP()) r, err := it.queryfunc(n) - if err == errClosed { + if errors.Is(err, errClosed) { // Avoid recording failures on shutdown. reply <- nil return diff --git a/discover/ntp.go b/discover/ntp.go index 1bb5239..48ceffe 100644 --- a/discover/ntp.go +++ b/discover/ntp.go @@ -108,7 +108,7 @@ func sntpDrift(measurements int) (time.Duration, error) { // Calculate the drift based on an assumed answer time of RRT/2 drifts = append(drifts, sent.Sub(t)+elapsed/2) } - // Calculate average drif (drop two extremities to avoid outliers) + // Calculate average drift (drop two extremities to avoid outliers) sort.Sort(durationSlice(drifts)) drift := time.Duration(0) diff --git a/discover/table.go b/discover/table.go index f721bcb..c91b8b8 100644 --- a/discover/table.go +++ b/discover/table.go @@ -458,7 +458,6 @@ func (tab *Table) bucketAtDistance(d int) *bucket { return tab.buckets[d-bucketMinDistance-1] } -///////////////////////////////////////////////////////////////////////////////////////////////////////// // addSeenNode adds a node which may or may not be live to the end of a bucket. If the // bucket has space available, adding the node succeeds immediately. Otherwise, the node is // added to the replacements list. @@ -498,7 +497,6 @@ func (tab *Table) addSeenNode(n *node) { } } -///////////////////////////////////////////////////////////// // addVerifiedNode adds a node whose existence has been verified recently to the front of a // bucket. If the node is already in the bucket, it is moved to the front. If the bucket // has no space, the node is added to the replacements list. diff --git a/discover/table_util_test.go b/discover/table_util_test.go index 81d654c..cdef6cd 100644 --- a/discover/table_util_test.go +++ b/discover/table_util_test.go @@ -134,8 +134,8 @@ func newPingRecorder() *pingRecorder { } } -// setRecord updates a node record. Future calls to ping and -// requestENR will return this record. +// updateRecord updates a node record. Future calls to ping and +// RequestENR will return this record. func (t *pingRecorder) updateRecord(n *enode.Node) { t.mu.Lock() defer t.mu.Unlock() @@ -162,7 +162,7 @@ func (t *pingRecorder) ping(n *enode.Node) (seq uint64, err error) { return seq, nil } -// requestENR simulates an ENR request. +// RequestENR simulates an ENR request. func (t *pingRecorder) RequestENR(n *enode.Node) (*enode.Node, error) { t.mu.Lock() defer t.mu.Unlock() diff --git a/discover/v4_udp.go b/discover/v4_udp.go index 095006c..97d7e45 100644 --- a/discover/v4_udp.go +++ b/discover/v4_udp.go @@ -328,13 +328,13 @@ func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target v4wire.Pubke // enough nodes the reply matcher will time out waiting for the second reply, but // there's no need for an error in that case. err := <-rm.errc - if err == errTimeout && rm.reply != nil { + if errors.Is(err, errTimeout) && rm.reply != nil { err = nil } return nodes, err } -// RequestENR sends enrRequest to the given node and waits for a response. +// RequestENR sends ENRRequest to the given node and waits for a response. func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) { addr := &net.UDPAddr{IP: n.IP(), Port: n.UDP()} t.ensureBond(n.ID(), addr) @@ -525,8 +525,8 @@ func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) { t.log.Debug("Temporary UDP read error", "err", err) continue } else if err != nil { - // Shut down the loop for permament errors. - if err != io.EOF { + // Shut down the loop for permanent errors. + if !errors.Is(err, io.EOF) { t.log.Debug("UDP read error", "err", err) } return diff --git a/discover/v4_udp_test.go b/discover/v4_udp_test.go index e480fac..1c05712 100644 --- a/discover/v4_udp_test.go +++ b/discover/v4_udp_test.go @@ -284,6 +284,7 @@ func TestUDPv4_findnode(t *testing.T) { test.waitPacketOut(func(p *v4wire.Neighbors, to *net.UDPAddr, hash []byte) { if len(p.Nodes) != len(want) { t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), bucketSize) + return } for i, n := range p.Nodes { if n.ID.ID() != want[i].ID() { @@ -312,7 +313,7 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) { test.table.db.UpdateLastPingReceived(rid, test.remoteaddr.IP, time.Now()) // queue a pending findnode request - resultc, errc := make(chan []*node), make(chan error) + resultc, errc := make(chan []*node, 1), make(chan error, 1) go func() { rid := encodePubkey(&test.remotekey.PublicKey).id() ns, err := test.udp.findnode(rid, test.remoteaddr, testTarget) @@ -489,7 +490,7 @@ func TestUDPv4_EIP868(t *testing.T) { t.Fatalf("invalid record: %v", err) } if !reflect.DeepEqual(n, wantNode) { - t.Fatalf("wrong node in enrResponse: %v", n) + t.Fatalf("wrong node in ENRResponse: %v", n) } }) } diff --git a/discover/v4wire/v4wire.go b/discover/v4wire/v4wire.go index bc537a4..3935068 100644 --- a/discover/v4wire/v4wire.go +++ b/discover/v4wire/v4wire.go @@ -1,4 +1,4 @@ -// Copyright 2019 The go-ethereum Authors +// Copyright 2020 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -60,7 +60,7 @@ type ( Pong struct { // This field should mirror the UDP envelope address // of the ping packet, which provides a way to discover the - // the external address (after NAT). + // external address (after NAT). To Endpoint ReplyTok []byte // This contains the hash of the ping packet. Expiration uint64 // Absolute timestamp at which the packet becomes invalid. @@ -86,23 +86,23 @@ type ( Rest []rlp.RawValue `rlp:"tail"` } - // enrRequest queries for the remote node's record. + // ENRRequest queries for the remote node's record. ENRRequest struct { Expiration uint64 // Ignore additional fields (for forward compatibility). Rest []rlp.RawValue `rlp:"tail"` } - // enrResponse is the reply to enrRequest. + // ENRResponse is the reply to ENRRequest. ENRResponse struct { - ReplyTok []byte // Hash of the enrRequest packet. + ReplyTok []byte // Hash of the ENRRequest packet. Record enr.Record // Ignore additional fields (for forward compatibility). Rest []rlp.RawValue `rlp:"tail"` } ) -// This number is the maximum number of neighbor nodes in a Neighbors packet. +// MaxNeighbors is the maximum number of neighbor nodes in a Neighbors packet. const MaxNeighbors = 12 // This code computes the MaxNeighbors constant value. @@ -161,8 +161,9 @@ func NewEndpoint(addr *net.UDPAddr, tcpPort uint16) Endpoint { } type Packet interface { - // packet name and type for logging purposes. + // Name is the name of the package, for logging purposes. Name() string + // Kind is the packet type, for logging purposes. Kind() byte } diff --git a/discover/v4wire/v4wire_test.go b/discover/v4wire/v4wire_test.go index 3b41619..38820f3 100644 --- a/discover/v4wire/v4wire_test.go +++ b/discover/v4wire/v4wire_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 The go-ethereum Authors +// Copyright 2020 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify diff --git a/discover/v5_udp.go b/discover/v5_udp.go index 167d4de..2408066 100644 --- a/discover/v5_udp.go +++ b/discover/v5_udp.go @@ -1,4 +1,4 @@ -// Copyright 2019 The go-ethereum Authors +// Copyright 2020 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -54,7 +54,7 @@ type codecV5 interface { // Encode encodes a packet. Encode(enode.ID, string, v5wire.Packet, *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error) - // decode decodes a packet. It returns a *v5wire.Unknown packet if decryption fails. + // Decode decodes a packet. It returns a *v5wire.Unknown packet if decryption fails. // The *enode.Node return value is non-nil when the input contains a handshake response. Decode([]byte, string) (enode.ID, *enode.Node, v5wire.Packet, error) } @@ -156,7 +156,7 @@ func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) { callDoneCh: make(chan *callV5), respTimeoutCh: make(chan *callTimeout), // state of dispatch - codec: v5wire.NewCodec(ln, cfg.PrivateKey, cfg.Clock, cfg.V5Config.ProtocolID), + codec: v5wire.NewCodec(ln, cfg.PrivateKey, cfg.Clock, *cfg.V5Config.ProtocolID), activeCallByNode: make(map[enode.ID]*callV5), activeCallByAuth: make(map[v5wire.Nonce]*callV5), callQueue: make(map[enode.ID][]*callV5), @@ -305,7 +305,7 @@ func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) { ) var r []*enode.Node r, err = t.findnode(unwrapNode(destNode), dists) - if err == errClosed { + if errors.Is(err, errClosed) { return nil, err } for _, n := range r { @@ -347,7 +347,7 @@ func (t *UDPv5) ping(n *enode.Node) (uint64, error) { } } -// requestENR requests n's record. +// RequestENR requests n's record. func (t *UDPv5) RequestENR(n *enode.Node) (*enode.Node, error) { nodes, err := t.findnode(n, []uint{0}) if err != nil { @@ -407,6 +407,9 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, s if err := netutil.CheckRelayIP(c.node.IP(), node.IP()); err != nil { return nil, err } + if t.netrestrict != nil && !t.netrestrict.Contains(node.IP()) { + return nil, errors.New("not contained in netrestrict list") + } if c.node.UDP() <= 1024 { return nil, errLowPort } @@ -622,8 +625,8 @@ func (t *UDPv5) readLoop() { t.log.Debug("Temporary UDP read error", "err", err) continue } else if err != nil { - // Shut down the loop for permament errors. - if err != io.EOF { + // Shut down the loop for permanent errors. + if !errors.Is(err, io.EOF) { t.log.Debug("UDP read error", "err", err) } return diff --git a/discover/v5_udp_test.go b/discover/v5_udp_test.go index 02516b2..1e62ce6 100644 --- a/discover/v5_udp_test.go +++ b/discover/v5_udp_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 The go-ethereum Authors +// Copyright 2020 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify diff --git a/discover/v5wire/encoding.go b/discover/v5wire/encoding.go index ea89492..47e8dcd 100644 --- a/discover/v5wire/encoding.go +++ b/discover/v5wire/encoding.go @@ -1,4 +1,4 @@ -// Copyright 2019 The go-ethereum Authors +// Copyright 2020 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -90,6 +90,10 @@ const ( minVersion = 1 sizeofMaskingIV = 16 + // The minimum size of any Discovery v5 packet is 63 bytes. + // Should reject packets smaller than minPacketSize. + minPacketSize = 63 + minMessageSize = 48 // this refers to data after static headers randomPacketMsgSize = 20 ) @@ -114,6 +118,7 @@ var ( // Public errors. var ( + // ErrInvalidReqID represents error when the ID is invalid. ErrInvalidReqID = errors.New("request ID larger than 8 bytes") ) @@ -129,11 +134,10 @@ var ( // Codec encodes and decodes Discovery v5 packets. // This type is not safe for concurrent use. type Codec struct { - sha256 hash.Hash - localnode *enode.LocalNode - privkey *ecdsa.PrivateKey - sc *SessionCache - + sha256 hash.Hash + localnode *enode.LocalNode + privkey *ecdsa.PrivateKey + sc *SessionCache protocolID [6]byte // encoder buffers @@ -303,7 +307,7 @@ func (c *Codec) encodeWhoareyou(toID enode.ID, packet *Whoareyou) (Header, error return head, nil } -// encodeHandshakeMessage encodes the handshake message packet header. +// encodeHandshakeHeader encodes the handshake message packet header. func (c *Codec) encodeHandshakeHeader(toID enode.ID, addr string, challenge *Whoareyou) (Header, *session, error) { // Ensure calling code sets challenge.node. if challenge.Node == nil { @@ -340,7 +344,7 @@ func (c *Codec) encodeHandshakeHeader(toID enode.ID, addr string, challenge *Who return head, session, err } -// encodeAuthHeader creates the auth header on a request packet following WHOAREYOU. +// makeHandshakeAuth creates the auth header on a request packet following WHOAREYOU. func (c *Codec) makeHandshakeAuth(toID enode.ID, addr string, challenge *Whoareyou) (*handshakeAuthData, *session, error) { auth := new(handshakeAuthData) auth.h.SrcID = c.localnode.ID() @@ -382,7 +386,7 @@ func (c *Codec) makeHandshakeAuth(toID enode.ID, addr string, challenge *Whoarey return auth, sec, err } -// encodeMessage encodes an encrypted message packet. +// encodeMessageHeader encodes an encrypted message packet. func (c *Codec) encodeMessageHeader(toID enode.ID, s *session) (Header, error) { head := c.makeHeader(toID, flagMessage, 0) @@ -418,10 +422,10 @@ func (c *Codec) encryptMessage(s *session, p Packet, head *Header, headerData [] // Decode decodes a discovery packet. func (c *Codec) Decode(input []byte, addr string) (src enode.ID, n *enode.Node, p Packet, err error) { - // Unmask the static header. - if len(input) < sizeofStaticPacketData { + if len(input) < minPacketSize { return enode.ID{}, nil, nil, errTooShort } + // Unmask the static header. var head Header copy(head.IV[:], input[:sizeofMaskingIV]) mask := head.mask(c.localnode.ID()) @@ -599,7 +603,7 @@ func (c *Codec) decodeMessage(fromAddr string, head *Header, headerData, msgData // Try decrypting the message. key := c.sc.readKey(auth.SrcID, fromAddr) msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, key) - if err == errMessageDecrypt { + if errors.Is(err, errMessageDecrypt) { // It didn't work. Start the handshake since this is an ordinary message packet. return &Unknown{Nonce: head.Nonce}, nil } @@ -635,7 +639,7 @@ func (h *StaticHeader) checkValid(packetLen int, protocolID [6]byte) error { return nil } -// headerMask returns a cipher for 'masking' / 'unmasking' packet headers. +// mask returns a cipher for 'masking' / 'unmasking' packet headers. func (h *Header) mask(destID enode.ID) cipher.Stream { block, err := aes.NewCipher(destID[:16]) if err != nil { diff --git a/discover/v5wire/encoding_test.go b/discover/v5wire/encoding_test.go index 154f8d6..b62165f 100644 --- a/discover/v5wire/encoding_test.go +++ b/discover/v5wire/encoding_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 The go-ethereum Authors +// Copyright 2020 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -23,7 +23,6 @@ import ( "errors" "flag" "fmt" - "io/ioutil" "net" "os" "path/filepath" @@ -39,8 +38,7 @@ import ( // To regenerate discv5 test vectors, run // -// go test -run TestVectors -write-test-vectors -// +// go test -run TestVectors -write-test-vectors var writeTestVectorsFlag = flag.Bool("write-test-vectors", false, "Overwrite discv5 test vectors in testdata/") var ( @@ -276,7 +274,15 @@ func TestDecodeErrorsV5(t *testing.T) { net := newHandshakeTest() defer net.close() - net.nodeA.expectDecodeErr(t, errTooShort, []byte{}) + b := make([]byte, 0) + net.nodeA.expectDecodeErr(t, errTooShort, b) + + b = make([]byte, 62) + net.nodeA.expectDecodeErr(t, errTooShort, b) + + b = make([]byte, 63) + net.nodeA.expectDecodeErr(t, errInvalidHeader, b) + // TODO some more tests would be nice :) // - check invalid authdata sizes // - check invalid handshake data sizes @@ -498,8 +504,8 @@ type handshakeTestNode struct { func newHandshakeTest() *handshakeTest { t := new(handshakeTest) - t.nodeA.init(testKeyA, net.IP{127, 0, 0, 1}, &t.clock, [6]byte{'d', 'i', 's', 'c', 'v', '5'}) - t.nodeB.init(testKeyB, net.IP{127, 0, 0, 1}, &t.clock, [6]byte{'d', 'i', 's', 'c', 'v', '5'}) + t.nodeA.init(testKeyA, net.IP{127, 0, 0, 1}, &t.clock, DefaultProtocolID) + t.nodeB.init(testKeyB, net.IP{127, 0, 0, 1}, &t.clock, DefaultProtocolID) return t } @@ -580,7 +586,7 @@ func (n *handshakeTestNode) id() enode.ID { // hexFile reads the given file and decodes the hex data contained in it. // Whitespace and any lines beginning with the # character are ignored. func hexFile(file string) []byte { - fileContent, err := ioutil.ReadFile(file) + fileContent, err := os.ReadFile(file) if err != nil { panic(err) } diff --git a/discover/v5wire/msg.go b/discover/v5wire/msg.go index c049668..1316598 100644 --- a/discover/v5wire/msg.go +++ b/discover/v5wire/msg.go @@ -1,4 +1,4 @@ -// Copyright 2019 The go-ethereum Authors +// Copyright 2020 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -59,7 +59,7 @@ type ( Nonce Nonce } - // WHOAREYOU contains the handshake challenge. + // Whoareyou contains the handshake challenge. Whoareyou struct { ChallengeData []byte // Encoded challenge Nonce Nonce // Nonce of request packet @@ -73,13 +73,13 @@ type ( sent mclock.AbsTime // for handshake GC. } - // PING is sent during liveness checks. + // Ping is sent during liveness checks. Ping struct { ReqID []byte ENRSeq uint64 } - // PONG is the reply to PING. + // Pong is the reply to Ping. Pong struct { ReqID []byte ENRSeq uint64 @@ -87,58 +87,58 @@ type ( ToPort uint16 // packet, which provides a way to discover the external address (after NAT). } - // FINDNODE is a query for nodes in the given bucket. + // Findnode is a query for nodes in the given bucket. Findnode struct { ReqID []byte Distances []uint } - // NODES is the reply to FINDNODE and TOPICQUERY. + // Nodes is the reply to Findnode and Topicquery. Nodes struct { ReqID []byte Total uint8 Nodes []*enr.Record } - // TALKREQ is an application-level request. + // TalkRequest is an application-level request. TalkRequest struct { ReqID []byte Protocol string Message []byte } - // TALKRESP is the reply to TALKREQ. + // TalkResponse is the reply to TalkRequest. TalkResponse struct { ReqID []byte Message []byte } - // REQUESTTICKET requests a ticket for a topic queue. + // RequestTicket requests a ticket for a topic queue. RequestTicket struct { ReqID []byte Topic []byte } - // TICKET is the response to REQUESTTICKET. + // Ticket is the response to RequestTicket. Ticket struct { ReqID []byte Ticket []byte } - // REGTOPIC registers the sender in a topic queue using a ticket. + // Regtopic registers the sender in a topic queue using a ticket. Regtopic struct { ReqID []byte Ticket []byte ENR *enr.Record } - // REGCONFIRMATION is the reply to REGTOPIC. + // Regconfirmation is the reply to Regtopic. Regconfirmation struct { ReqID []byte Registered bool } - // TOPICQUERY asks for nodes with the given topic. + // TopicQuery asks for nodes with the given topic. TopicQuery struct { ReqID []byte Topic []byte