github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/cmd/devp2p/internal/v5test/discv5tests.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 "net" 22 "slices" 23 "sync" 24 "time" 25 26 "github.com/ethereum/go-ethereum/internal/utesting" 27 "github.com/ethereum/go-ethereum/p2p/discover/v5wire" 28 "github.com/ethereum/go-ethereum/p2p/enode" 29 "github.com/ethereum/go-ethereum/p2p/netutil" 30 ) 31 32 // Suite is the discv5 test suite. 33 type Suite struct { 34 Dest *enode.Node 35 Listen1, Listen2 string // listening addresses 36 } 37 38 func (s *Suite) listen1(log logger) (*conn, net.PacketConn) { 39 c := newConn(s.Dest, log) 40 l := c.listen(s.Listen1) 41 return c, l 42 } 43 44 func (s *Suite) listen2(log logger) (*conn, net.PacketConn, net.PacketConn) { 45 c := newConn(s.Dest, log) 46 l1, l2 := c.listen(s.Listen1), c.listen(s.Listen2) 47 return c, l1, l2 48 } 49 50 func (s *Suite) AllTests() []utesting.Test { 51 return []utesting.Test{ 52 {Name: "Ping", Fn: s.TestPing}, 53 {Name: "PingLargeRequestID", Fn: s.TestPingLargeRequestID}, 54 {Name: "PingMultiIP", Fn: s.TestPingMultiIP}, 55 {Name: "PingHandshakeInterrupted", Fn: s.TestPingHandshakeInterrupted}, 56 {Name: "TalkRequest", Fn: s.TestTalkRequest}, 57 {Name: "FindnodeZeroDistance", Fn: s.TestFindnodeZeroDistance}, 58 {Name: "FindnodeResults", Fn: s.TestFindnodeResults}, 59 } 60 } 61 62 // TestPing sends PING and expects a PONG response. 63 func (s *Suite) TestPing(t *utesting.T) { 64 conn, l1 := s.listen1(t) 65 defer conn.close() 66 67 ping := &v5wire.Ping{ReqID: conn.nextReqID()} 68 switch resp := conn.reqresp(l1, ping).(type) { 69 case *v5wire.Pong: 70 checkPong(t, resp, ping, l1) 71 default: 72 t.Fatal("expected PONG, got", resp.Name()) 73 } 74 } 75 76 func checkPong(t *utesting.T, pong *v5wire.Pong, ping *v5wire.Ping, c net.PacketConn) { 77 if !bytes.Equal(pong.ReqID, ping.ReqID) { 78 t.Fatalf("wrong request ID %x in PONG, want %x", pong.ReqID, ping.ReqID) 79 } 80 if !pong.ToIP.Equal(laddr(c).IP) { 81 t.Fatalf("wrong destination IP %v in PONG, want %v", pong.ToIP, laddr(c).IP) 82 } 83 if int(pong.ToPort) != laddr(c).Port { 84 t.Fatalf("wrong destination port %v in PONG, want %v", pong.ToPort, laddr(c).Port) 85 } 86 } 87 88 // TestPingLargeRequestID sends PING with a 9-byte request ID, which isn't allowed by the spec. 89 // The remote node should not respond. 90 func (s *Suite) TestPingLargeRequestID(t *utesting.T) { 91 conn, l1 := s.listen1(t) 92 defer conn.close() 93 94 ping := &v5wire.Ping{ReqID: make([]byte, 9)} 95 switch resp := conn.reqresp(l1, ping).(type) { 96 case *v5wire.Pong: 97 t.Errorf("PONG response with unknown request ID %x", resp.ReqID) 98 case *readError: 99 if resp.err == v5wire.ErrInvalidReqID { 100 t.Error("response with oversized request ID") 101 } else if !netutil.IsTimeout(resp.err) { 102 t.Error(resp) 103 } 104 } 105 } 106 107 // TestPingMultiIP establishes a session from one IP as usual. The session is then reused 108 // on another IP, which shouldn't work. The remote node should respond with WHOAREYOU for 109 // the attempt from a different IP. 110 func (s *Suite) TestPingMultiIP(t *utesting.T) { 111 conn, l1, l2 := s.listen2(t) 112 defer conn.close() 113 114 // Create the session on l1. 115 ping := &v5wire.Ping{ReqID: conn.nextReqID()} 116 resp := conn.reqresp(l1, ping) 117 if resp.Kind() != v5wire.PongMsg { 118 t.Fatal("expected PONG, got", resp) 119 } 120 checkPong(t, resp.(*v5wire.Pong), ping, l1) 121 122 // Send on l2. This reuses the session because there is only one codec. 123 ping2 := &v5wire.Ping{ReqID: conn.nextReqID()} 124 conn.write(l2, ping2, nil) 125 switch resp := conn.read(l2).(type) { 126 case *v5wire.Pong: 127 t.Fatalf("remote responded to PING from %v for session on IP %v", laddr(l2).IP, laddr(l1).IP) 128 case *v5wire.Whoareyou: 129 t.Logf("got WHOAREYOU for new session as expected") 130 resp.Node = s.Dest 131 conn.write(l2, ping2, resp) 132 default: 133 t.Fatal("expected WHOAREYOU, got", resp) 134 } 135 136 // Catch the PONG on l2. 137 switch resp := conn.read(l2).(type) { 138 case *v5wire.Pong: 139 checkPong(t, resp, ping2, l2) 140 default: 141 t.Fatal("expected PONG, got", resp) 142 } 143 144 // Try on l1 again. 145 ping3 := &v5wire.Ping{ReqID: conn.nextReqID()} 146 conn.write(l1, ping3, nil) 147 switch resp := conn.read(l1).(type) { 148 case *v5wire.Pong: 149 t.Fatalf("remote responded to PING from %v for session on IP %v", laddr(l1).IP, laddr(l2).IP) 150 case *v5wire.Whoareyou: 151 t.Logf("got WHOAREYOU for new session as expected") 152 default: 153 t.Fatal("expected WHOAREYOU, got", resp) 154 } 155 } 156 157 // TestPingHandshakeInterrupted starts a handshake, but doesn't finish it and sends a second ordinary message 158 // packet instead of a handshake message packet. The remote node should respond with 159 // another WHOAREYOU challenge for the second packet. 160 func (s *Suite) TestPingHandshakeInterrupted(t *utesting.T) { 161 conn, l1 := s.listen1(t) 162 defer conn.close() 163 164 // First PING triggers challenge. 165 ping := &v5wire.Ping{ReqID: conn.nextReqID()} 166 conn.write(l1, ping, nil) 167 switch resp := conn.read(l1).(type) { 168 case *v5wire.Whoareyou: 169 t.Logf("got WHOAREYOU for PING") 170 default: 171 t.Fatal("expected WHOAREYOU, got", resp) 172 } 173 174 // Send second PING. 175 ping2 := &v5wire.Ping{ReqID: conn.nextReqID()} 176 switch resp := conn.reqresp(l1, ping2).(type) { 177 case *v5wire.Pong: 178 checkPong(t, resp, ping2, l1) 179 default: 180 t.Fatal("expected WHOAREYOU, got", resp) 181 } 182 } 183 184 // TestTalkRequest sends TALKREQ and expects an empty TALKRESP response. 185 func (s *Suite) TestTalkRequest(t *utesting.T) { 186 conn, l1 := s.listen1(t) 187 defer conn.close() 188 189 // Non-empty request ID. 190 id := conn.nextReqID() 191 resp := conn.reqresp(l1, &v5wire.TalkRequest{ReqID: id, Protocol: "test-protocol"}) 192 switch resp := resp.(type) { 193 case *v5wire.TalkResponse: 194 if !bytes.Equal(resp.ReqID, id) { 195 t.Fatalf("wrong request ID %x in TALKRESP, want %x", resp.ReqID, id) 196 } 197 if len(resp.Message) > 0 { 198 t.Fatalf("non-empty message %x in TALKRESP", resp.Message) 199 } 200 default: 201 t.Fatal("expected TALKRESP, got", resp.Name()) 202 } 203 204 // Empty request ID. 205 resp = conn.reqresp(l1, &v5wire.TalkRequest{Protocol: "test-protocol"}) 206 switch resp := resp.(type) { 207 case *v5wire.TalkResponse: 208 if len(resp.ReqID) > 0 { 209 t.Fatalf("wrong request ID %x in TALKRESP, want empty byte array", resp.ReqID) 210 } 211 if len(resp.Message) > 0 { 212 t.Fatalf("non-empty message %x in TALKRESP", resp.Message) 213 } 214 default: 215 t.Fatal("expected TALKRESP, got", resp.Name()) 216 } 217 } 218 219 // TestFindnodeZeroDistance checks that the remote node returns itself for FINDNODE with distance zero. 220 func (s *Suite) TestFindnodeZeroDistance(t *utesting.T) { 221 conn, l1 := s.listen1(t) 222 defer conn.close() 223 224 nodes, err := conn.findnode(l1, []uint{0}) 225 if err != nil { 226 t.Fatal(err) 227 } 228 if len(nodes) != 1 { 229 t.Fatalf("remote returned more than one node for FINDNODE [0]") 230 } 231 if nodes[0].ID() != conn.remote.ID() { 232 t.Errorf("ID of response node is %v, want %v", nodes[0].ID(), conn.remote.ID()) 233 } 234 } 235 236 // TestFindnodeResults pings the node under test from multiple nodes. After waiting for them to be 237 // accepted into the remote table, the test checks that they are returned by FINDNODE. 238 func (s *Suite) TestFindnodeResults(t *utesting.T) { 239 // Create bystanders. 240 nodes := make([]*bystander, 5) 241 added := make(chan enode.ID, len(nodes)) 242 for i := range nodes { 243 nodes[i] = newBystander(t, s, added) 244 defer nodes[i].close() 245 } 246 247 // Get them added to the remote table. 248 timeout := 60 * time.Second 249 timeoutCh := time.After(timeout) 250 for count := 0; count < len(nodes); { 251 select { 252 case id := <-added: 253 t.Logf("bystander node %v added to remote table", id) 254 count++ 255 case <-timeoutCh: 256 t.Errorf("remote added %d bystander nodes in %v, need %d to continue", count, timeout, len(nodes)) 257 t.Logf("this can happen if the node has a non-empty table from previous runs") 258 return 259 } 260 } 261 t.Logf("all %d bystander nodes were added", len(nodes)) 262 263 // Collect our nodes by distance. 264 var dists []uint 265 expect := make(map[enode.ID]*enode.Node) 266 for _, bn := range nodes { 267 n := bn.conn.localNode.Node() 268 expect[n.ID()] = n 269 d := uint(enode.LogDist(n.ID(), s.Dest.ID())) 270 if !slices.Contains(dists, d) { 271 dists = append(dists, d) 272 } 273 } 274 275 // Send FINDNODE for all distances. 276 conn, l1 := s.listen1(t) 277 defer conn.close() 278 foundNodes, err := conn.findnode(l1, dists) 279 if err != nil { 280 t.Fatal(err) 281 } 282 t.Logf("remote returned %d nodes for distance list %v", len(foundNodes), dists) 283 for _, n := range foundNodes { 284 delete(expect, n.ID()) 285 } 286 if len(expect) > 0 { 287 t.Errorf("missing %d nodes in FINDNODE result", len(expect)) 288 t.Logf("this can happen if the test is run multiple times in quick succession") 289 t.Logf("and the remote node hasn't removed dead nodes from previous runs yet") 290 } else { 291 t.Logf("all %d expected nodes were returned", len(nodes)) 292 } 293 } 294 295 // A bystander is a node whose only purpose is filling a spot in the remote table. 296 type bystander struct { 297 dest *enode.Node 298 conn *conn 299 l net.PacketConn 300 301 addedCh chan enode.ID 302 done sync.WaitGroup 303 } 304 305 func newBystander(t *utesting.T, s *Suite, added chan enode.ID) *bystander { 306 conn, l := s.listen1(t) 307 conn.setEndpoint(l) // bystander nodes need IP/port to get pinged 308 bn := &bystander{ 309 conn: conn, 310 l: l, 311 dest: s.Dest, 312 addedCh: added, 313 } 314 bn.done.Add(1) 315 go bn.loop() 316 return bn 317 } 318 319 // id returns the node ID of the bystander. 320 func (bn *bystander) id() enode.ID { 321 return bn.conn.localNode.ID() 322 } 323 324 // close shuts down loop. 325 func (bn *bystander) close() { 326 bn.conn.close() 327 bn.done.Wait() 328 } 329 330 // loop answers packets from the remote node until quit. 331 func (bn *bystander) loop() { 332 defer bn.done.Done() 333 334 var ( 335 lastPing time.Time 336 wasAdded bool 337 ) 338 for { 339 // Ping the remote node. 340 if !wasAdded && time.Since(lastPing) > 10*time.Second { 341 bn.conn.reqresp(bn.l, &v5wire.Ping{ 342 ReqID: bn.conn.nextReqID(), 343 ENRSeq: bn.dest.Seq(), 344 }) 345 lastPing = time.Now() 346 } 347 // Answer packets. 348 switch p := bn.conn.read(bn.l).(type) { 349 case *v5wire.Ping: 350 bn.conn.write(bn.l, &v5wire.Pong{ 351 ReqID: p.ReqID, 352 ENRSeq: bn.conn.localNode.Seq(), 353 ToIP: bn.dest.IP(), 354 ToPort: uint16(bn.dest.UDP()), 355 }, nil) 356 wasAdded = true 357 bn.notifyAdded() 358 case *v5wire.Findnode: 359 bn.conn.write(bn.l, &v5wire.Nodes{ReqID: p.ReqID, RespCount: 1}, nil) 360 wasAdded = true 361 bn.notifyAdded() 362 case *v5wire.TalkRequest: 363 bn.conn.write(bn.l, &v5wire.TalkResponse{ReqID: p.ReqID}, nil) 364 case *readError: 365 if !netutil.IsTemporaryError(p.err) { 366 bn.conn.logf("shutting down: %v", p.err) 367 return 368 } 369 } 370 } 371 } 372 373 func (bn *bystander) notifyAdded() { 374 if bn.addedCh != nil { 375 bn.addedCh <- bn.id() 376 bn.addedCh = nil 377 } 378 }