github.com/core-coin/go-core/v2@v2.1.9/cmd/devp2p/internal/v5test/discv5tests.go (about)

     1  // Copyright 2020 by the Authors
     2  // This file is part of go-core.
     3  //
     4  // go-core 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-core 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-core. 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/core-coin/go-core/v2/internal/utesting"
    26  	"github.com/core-coin/go-core/v2/p2p/discover/v5wire"
    27  	"github.com/core-coin/go-core/v2/p2p/enode"
    28  	"github.com/core-coin/go-core/v2/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  }