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