github.com/ethw3/go-ethereuma@v0.0.0-20221013053120-c14602a4c23c/cmd/devp2p/internal/v5test/framework.go (about) 1 // Copyright 2020 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // go-ethereum is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 package v5test 18 19 import ( 20 "bytes" 21 "crypto/ecdsa" 22 "encoding/binary" 23 "fmt" 24 "net" 25 "time" 26 27 "github.com/ethw3/go-ethereuma/common/mclock" 28 "github.com/ethw3/go-ethereuma/crypto" 29 "github.com/ethw3/go-ethereuma/p2p/discover/v5wire" 30 "github.com/ethw3/go-ethereuma/p2p/enode" 31 "github.com/ethw3/go-ethereuma/p2p/enr" 32 ) 33 34 // readError represents an error during packet reading. 35 // This exists to facilitate type-switching on the result of conn.read. 36 type readError struct { 37 err error 38 } 39 40 func (p *readError) Kind() byte { return 99 } 41 func (p *readError) Name() string { return fmt.Sprintf("error: %v", p.err) } 42 func (p *readError) Error() string { return p.err.Error() } 43 func (p *readError) Unwrap() error { return p.err } 44 func (p *readError) RequestID() []byte { return nil } 45 func (p *readError) SetRequestID([]byte) {} 46 47 // readErrorf creates a readError with the given text. 48 func readErrorf(format string, args ...interface{}) *readError { 49 return &readError{fmt.Errorf(format, args...)} 50 } 51 52 // This is the response timeout used in tests. 53 const waitTime = 300 * time.Millisecond 54 55 // conn is a connection to the node under test. 56 type conn struct { 57 localNode *enode.LocalNode 58 localKey *ecdsa.PrivateKey 59 remote *enode.Node 60 remoteAddr *net.UDPAddr 61 listeners []net.PacketConn 62 63 log logger 64 codec *v5wire.Codec 65 idCounter uint32 66 } 67 68 type logger interface { 69 Logf(string, ...interface{}) 70 } 71 72 // newConn sets up a connection to the given node. 73 func newConn(dest *enode.Node, log logger) *conn { 74 key, err := crypto.GenerateKey() 75 if err != nil { 76 panic(err) 77 } 78 db, err := enode.OpenDB("") 79 if err != nil { 80 panic(err) 81 } 82 ln := enode.NewLocalNode(db, key) 83 84 return &conn{ 85 localKey: key, 86 localNode: ln, 87 remote: dest, 88 remoteAddr: &net.UDPAddr{IP: dest.IP(), Port: dest.UDP()}, 89 codec: v5wire.NewCodec(ln, key, mclock.System{}), 90 log: log, 91 } 92 } 93 94 func (tc *conn) setEndpoint(c net.PacketConn) { 95 tc.localNode.SetStaticIP(laddr(c).IP) 96 tc.localNode.SetFallbackUDP(laddr(c).Port) 97 } 98 99 func (tc *conn) listen(ip string) net.PacketConn { 100 l, err := net.ListenPacket("udp", fmt.Sprintf("%v:0", ip)) 101 if err != nil { 102 panic(err) 103 } 104 tc.listeners = append(tc.listeners, l) 105 return l 106 } 107 108 // close shuts down all listeners and the local node. 109 func (tc *conn) close() { 110 for _, l := range tc.listeners { 111 l.Close() 112 } 113 tc.localNode.Database().Close() 114 } 115 116 // nextReqID creates a request id. 117 func (tc *conn) nextReqID() []byte { 118 id := make([]byte, 4) 119 tc.idCounter++ 120 binary.BigEndian.PutUint32(id, tc.idCounter) 121 return id 122 } 123 124 // reqresp performs a request/response interaction on the given connection. 125 // The request is retried if a handshake is requested. 126 func (tc *conn) reqresp(c net.PacketConn, req v5wire.Packet) v5wire.Packet { 127 reqnonce := tc.write(c, req, nil) 128 switch resp := tc.read(c).(type) { 129 case *v5wire.Whoareyou: 130 if resp.Nonce != reqnonce { 131 return readErrorf("wrong nonce %x in WHOAREYOU (want %x)", resp.Nonce[:], reqnonce[:]) 132 } 133 resp.Node = tc.remote 134 tc.write(c, req, resp) 135 return tc.read(c) 136 default: 137 return resp 138 } 139 } 140 141 // findnode sends a FINDNODE request and waits for its responses. 142 func (tc *conn) findnode(c net.PacketConn, dists []uint) ([]*enode.Node, error) { 143 var ( 144 findnode = &v5wire.Findnode{ReqID: tc.nextReqID(), Distances: dists} 145 reqnonce = tc.write(c, findnode, nil) 146 first = true 147 total uint8 148 results []*enode.Node 149 ) 150 for n := 1; n > 0; { 151 switch resp := tc.read(c).(type) { 152 case *v5wire.Whoareyou: 153 // Handle handshake. 154 if resp.Nonce == reqnonce { 155 resp.Node = tc.remote 156 tc.write(c, findnode, resp) 157 } else { 158 return nil, fmt.Errorf("unexpected WHOAREYOU (nonce %x), waiting for NODES", resp.Nonce[:]) 159 } 160 case *v5wire.Ping: 161 // Handle ping from remote. 162 tc.write(c, &v5wire.Pong{ 163 ReqID: resp.ReqID, 164 ENRSeq: tc.localNode.Seq(), 165 }, nil) 166 case *v5wire.Nodes: 167 // Got NODES! Check request ID. 168 if !bytes.Equal(resp.ReqID, findnode.ReqID) { 169 return nil, fmt.Errorf("NODES response has wrong request id %x", resp.ReqID) 170 } 171 // Check total count. It should be greater than one 172 // and needs to be the same across all responses. 173 if first { 174 if resp.Total == 0 || resp.Total > 6 { 175 return nil, fmt.Errorf("invalid NODES response 'total' %d (not in (0,7))", resp.Total) 176 } 177 total = resp.Total 178 n = int(total) - 1 179 first = false 180 } else { 181 n-- 182 if resp.Total != total { 183 return nil, fmt.Errorf("invalid NODES response 'total' %d (!= %d)", resp.Total, total) 184 } 185 } 186 // Check nodes. 187 nodes, err := checkRecords(resp.Nodes) 188 if err != nil { 189 return nil, fmt.Errorf("invalid node in NODES response: %v", err) 190 } 191 results = append(results, nodes...) 192 default: 193 return nil, fmt.Errorf("expected NODES, got %v", resp) 194 } 195 } 196 return results, nil 197 } 198 199 // write sends a packet on the given connection. 200 func (tc *conn) write(c net.PacketConn, p v5wire.Packet, challenge *v5wire.Whoareyou) v5wire.Nonce { 201 packet, nonce, err := tc.codec.Encode(tc.remote.ID(), tc.remoteAddr.String(), p, challenge) 202 if err != nil { 203 panic(fmt.Errorf("can't encode %v packet: %v", p.Name(), err)) 204 } 205 if _, err := c.WriteTo(packet, tc.remoteAddr); err != nil { 206 tc.logf("Can't send %s: %v", p.Name(), err) 207 } else { 208 tc.logf(">> %s", p.Name()) 209 } 210 return nonce 211 } 212 213 // read waits for an incoming packet on the given connection. 214 func (tc *conn) read(c net.PacketConn) v5wire.Packet { 215 buf := make([]byte, 1280) 216 if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil { 217 return &readError{err} 218 } 219 n, fromAddr, err := c.ReadFrom(buf) 220 if err != nil { 221 return &readError{err} 222 } 223 _, _, p, err := tc.codec.Decode(buf[:n], fromAddr.String()) 224 if err != nil { 225 return &readError{err} 226 } 227 tc.logf("<< %s", p.Name()) 228 return p 229 } 230 231 // logf prints to the test log. 232 func (tc *conn) logf(format string, args ...interface{}) { 233 if tc.log != nil { 234 tc.log.Logf("(%s) %s", tc.localNode.ID().TerminalString(), fmt.Sprintf(format, args...)) 235 } 236 } 237 238 func laddr(c net.PacketConn) *net.UDPAddr { 239 return c.LocalAddr().(*net.UDPAddr) 240 } 241 242 func checkRecords(records []*enr.Record) ([]*enode.Node, error) { 243 nodes := make([]*enode.Node, len(records)) 244 for i := range records { 245 n, err := enode.New(enode.ValidSchemes, records[i]) 246 if err != nil { 247 return nil, err 248 } 249 nodes[i] = n 250 } 251 return nodes, nil 252 } 253 254 func containsUint(ints []uint, x uint) bool { 255 for i := range ints { 256 if ints[i] == x { 257 return true 258 } 259 } 260 return false 261 }