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  }