gitee.com/liu-zhao234568/cntest@v1.0.0/cmd/devp2p/internal/v4test/discv4tests.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 v4test
    18  
    19  import (
    20  	"bytes"
    21  	"crypto/rand"
    22  	"fmt"
    23  	"net"
    24  	"time"
    25  
    26  	"gitee.com/liu-zhao234568/cntest/crypto"
    27  	"gitee.com/liu-zhao234568/cntest/internal/utesting"
    28  	"gitee.com/liu-zhao234568/cntest/p2p/discover/v4wire"
    29  )
    30  
    31  const (
    32  	expiration  = 20 * time.Second
    33  	wrongPacket = 66
    34  	macSize     = 256 / 8
    35  )
    36  
    37  var (
    38  	// Remote node under test
    39  	Remote string
    40  	// IP where the first tester is listening, port will be assigned
    41  	Listen1 string = "127.0.0.1"
    42  	// IP where the second tester is listening, port will be assigned
    43  	// Before running the test, you may have to `sudo ifconfig lo0 add 127.0.0.2` (on MacOS at least)
    44  	Listen2 string = "127.0.0.2"
    45  )
    46  
    47  type pingWithJunk struct {
    48  	Version    uint
    49  	From, To   v4wire.Endpoint
    50  	Expiration uint64
    51  	JunkData1  uint
    52  	JunkData2  []byte
    53  }
    54  
    55  func (req *pingWithJunk) Name() string { return "PING/v4" }
    56  func (req *pingWithJunk) Kind() byte   { return v4wire.PingPacket }
    57  
    58  type pingWrongType struct {
    59  	Version    uint
    60  	From, To   v4wire.Endpoint
    61  	Expiration uint64
    62  }
    63  
    64  func (req *pingWrongType) Name() string { return "WRONG/v4" }
    65  func (req *pingWrongType) Kind() byte   { return wrongPacket }
    66  
    67  func futureExpiration() uint64 {
    68  	return uint64(time.Now().Add(expiration).Unix())
    69  }
    70  
    71  // This test just sends a PING packet and expects a response.
    72  func BasicPing(t *utesting.T) {
    73  	te := newTestEnv(Remote, Listen1, Listen2)
    74  	defer te.close()
    75  
    76  	pingHash := te.send(te.l1, &v4wire.Ping{
    77  		Version:    4,
    78  		From:       te.localEndpoint(te.l1),
    79  		To:         te.remoteEndpoint(),
    80  		Expiration: futureExpiration(),
    81  	})
    82  
    83  	reply, _, _ := te.read(te.l1)
    84  	if err := te.checkPong(reply, pingHash); err != nil {
    85  		t.Fatal(err)
    86  	}
    87  }
    88  
    89  // checkPong verifies that reply is a valid PONG matching the given ping hash.
    90  func (te *testenv) checkPong(reply v4wire.Packet, pingHash []byte) error {
    91  	if reply == nil {
    92  		return fmt.Errorf("expected PONG reply, got nil")
    93  	}
    94  	if reply.Kind() != v4wire.PongPacket {
    95  		return fmt.Errorf("expected PONG reply, got %v %v", reply.Name(), reply)
    96  	}
    97  	pong := reply.(*v4wire.Pong)
    98  	if !bytes.Equal(pong.ReplyTok, pingHash) {
    99  		return fmt.Errorf("PONG reply token mismatch: got %x, want %x", pong.ReplyTok, pingHash)
   100  	}
   101  	if want := te.localEndpoint(te.l1); !want.IP.Equal(pong.To.IP) || want.UDP != pong.To.UDP {
   102  		return fmt.Errorf("PONG 'to' endpoint mismatch: got %+v, want %+v", pong.To, want)
   103  	}
   104  	if v4wire.Expired(pong.Expiration) {
   105  		return fmt.Errorf("PONG is expired (%v)", pong.Expiration)
   106  	}
   107  	return nil
   108  }
   109  
   110  // This test sends a PING packet with wrong 'to' field and expects a PONG response.
   111  func PingWrongTo(t *utesting.T) {
   112  	te := newTestEnv(Remote, Listen1, Listen2)
   113  	defer te.close()
   114  
   115  	wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")}
   116  	pingHash := te.send(te.l1, &v4wire.Ping{
   117  		Version:    4,
   118  		From:       te.localEndpoint(te.l1),
   119  		To:         wrongEndpoint,
   120  		Expiration: futureExpiration(),
   121  	})
   122  
   123  	reply, _, _ := te.read(te.l1)
   124  	if err := te.checkPong(reply, pingHash); err != nil {
   125  		t.Fatal(err)
   126  	}
   127  }
   128  
   129  // This test sends a PING packet with wrong 'from' field and expects a PONG response.
   130  func PingWrongFrom(t *utesting.T) {
   131  	te := newTestEnv(Remote, Listen1, Listen2)
   132  	defer te.close()
   133  
   134  	wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")}
   135  	pingHash := te.send(te.l1, &v4wire.Ping{
   136  		Version:    4,
   137  		From:       wrongEndpoint,
   138  		To:         te.remoteEndpoint(),
   139  		Expiration: futureExpiration(),
   140  	})
   141  
   142  	reply, _, _ := te.read(te.l1)
   143  	if err := te.checkPong(reply, pingHash); err != nil {
   144  		t.Fatal(err)
   145  	}
   146  }
   147  
   148  // This test sends a PING packet with additional data at the end and expects a PONG
   149  // response. The remote node should respond because EIP-8 mandates ignoring additional
   150  // trailing data.
   151  func PingExtraData(t *utesting.T) {
   152  	te := newTestEnv(Remote, Listen1, Listen2)
   153  	defer te.close()
   154  
   155  	pingHash := te.send(te.l1, &pingWithJunk{
   156  		Version:    4,
   157  		From:       te.localEndpoint(te.l1),
   158  		To:         te.remoteEndpoint(),
   159  		Expiration: futureExpiration(),
   160  		JunkData1:  42,
   161  		JunkData2:  []byte{9, 8, 7, 6, 5, 4, 3, 2, 1},
   162  	})
   163  
   164  	reply, _, _ := te.read(te.l1)
   165  	if err := te.checkPong(reply, pingHash); err != nil {
   166  		t.Fatal(err)
   167  	}
   168  }
   169  
   170  // This test sends a PING packet with additional data and wrong 'from' field
   171  // and expects a PONG response.
   172  func PingExtraDataWrongFrom(t *utesting.T) {
   173  	te := newTestEnv(Remote, Listen1, Listen2)
   174  	defer te.close()
   175  
   176  	wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")}
   177  	req := pingWithJunk{
   178  		Version:    4,
   179  		From:       wrongEndpoint,
   180  		To:         te.remoteEndpoint(),
   181  		Expiration: futureExpiration(),
   182  		JunkData1:  42,
   183  		JunkData2:  []byte{9, 8, 7, 6, 5, 4, 3, 2, 1},
   184  	}
   185  	pingHash := te.send(te.l1, &req)
   186  	reply, _, _ := te.read(te.l1)
   187  	if err := te.checkPong(reply, pingHash); err != nil {
   188  		t.Fatal(err)
   189  	}
   190  }
   191  
   192  // This test sends a PING packet with an expiration in the past.
   193  // The remote node should not respond.
   194  func PingPastExpiration(t *utesting.T) {
   195  	te := newTestEnv(Remote, Listen1, Listen2)
   196  	defer te.close()
   197  
   198  	te.send(te.l1, &v4wire.Ping{
   199  		Version:    4,
   200  		From:       te.localEndpoint(te.l1),
   201  		To:         te.remoteEndpoint(),
   202  		Expiration: -futureExpiration(),
   203  	})
   204  
   205  	reply, _, _ := te.read(te.l1)
   206  	if reply != nil {
   207  		t.Fatal("Expected no reply, got", reply)
   208  	}
   209  }
   210  
   211  // This test sends an invalid packet. The remote node should not respond.
   212  func WrongPacketType(t *utesting.T) {
   213  	te := newTestEnv(Remote, Listen1, Listen2)
   214  	defer te.close()
   215  
   216  	te.send(te.l1, &pingWrongType{
   217  		Version:    4,
   218  		From:       te.localEndpoint(te.l1),
   219  		To:         te.remoteEndpoint(),
   220  		Expiration: futureExpiration(),
   221  	})
   222  
   223  	reply, _, _ := te.read(te.l1)
   224  	if reply != nil {
   225  		t.Fatal("Expected no reply, got", reply)
   226  	}
   227  }
   228  
   229  // This test verifies that the default behaviour of ignoring 'from' fields is unaffected by
   230  // the bonding process. After bonding, it pings the target with a different from endpoint.
   231  func BondThenPingWithWrongFrom(t *utesting.T) {
   232  	te := newTestEnv(Remote, Listen1, Listen2)
   233  	defer te.close()
   234  	bond(t, te)
   235  
   236  	wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")}
   237  	pingHash := te.send(te.l1, &v4wire.Ping{
   238  		Version:    4,
   239  		From:       wrongEndpoint,
   240  		To:         te.remoteEndpoint(),
   241  		Expiration: futureExpiration(),
   242  	})
   243  
   244  	reply, _, _ := te.read(te.l1)
   245  	if err := te.checkPong(reply, pingHash); err != nil {
   246  		t.Fatal(err)
   247  	}
   248  }
   249  
   250  // This test just sends FINDNODE. The remote node should not reply
   251  // because the endpoint proof has not completed.
   252  func FindnodeWithoutEndpointProof(t *utesting.T) {
   253  	te := newTestEnv(Remote, Listen1, Listen2)
   254  	defer te.close()
   255  
   256  	req := v4wire.Findnode{Expiration: futureExpiration()}
   257  	rand.Read(req.Target[:])
   258  	te.send(te.l1, &req)
   259  
   260  	reply, _, _ := te.read(te.l1)
   261  	if reply != nil {
   262  		t.Fatal("Expected no response, got", reply)
   263  	}
   264  }
   265  
   266  // BasicFindnode sends a FINDNODE request after performing the endpoint
   267  // proof. The remote node should respond.
   268  func BasicFindnode(t *utesting.T) {
   269  	te := newTestEnv(Remote, Listen1, Listen2)
   270  	defer te.close()
   271  	bond(t, te)
   272  
   273  	findnode := v4wire.Findnode{Expiration: futureExpiration()}
   274  	rand.Read(findnode.Target[:])
   275  	te.send(te.l1, &findnode)
   276  
   277  	reply, _, err := te.read(te.l1)
   278  	if err != nil {
   279  		t.Fatal("read find nodes", err)
   280  	}
   281  	if reply.Kind() != v4wire.NeighborsPacket {
   282  		t.Fatal("Expected neighbors, got", reply.Name())
   283  	}
   284  }
   285  
   286  // This test sends an unsolicited NEIGHBORS packet after the endpoint proof, then sends
   287  // FINDNODE to read the remote table. The remote node should not return the node contained
   288  // in the unsolicited NEIGHBORS packet.
   289  func UnsolicitedNeighbors(t *utesting.T) {
   290  	te := newTestEnv(Remote, Listen1, Listen2)
   291  	defer te.close()
   292  	bond(t, te)
   293  
   294  	// Send unsolicited NEIGHBORS response.
   295  	fakeKey, _ := crypto.GenerateKey()
   296  	encFakeKey := v4wire.EncodePubkey(&fakeKey.PublicKey)
   297  	neighbors := v4wire.Neighbors{
   298  		Expiration: futureExpiration(),
   299  		Nodes: []v4wire.Node{{
   300  			ID:  encFakeKey,
   301  			IP:  net.IP{1, 2, 3, 4},
   302  			UDP: 30303,
   303  			TCP: 30303,
   304  		}},
   305  	}
   306  	te.send(te.l1, &neighbors)
   307  
   308  	// Check if the remote node included the fake node.
   309  	te.send(te.l1, &v4wire.Findnode{
   310  		Expiration: futureExpiration(),
   311  		Target:     encFakeKey,
   312  	})
   313  
   314  	reply, _, err := te.read(te.l1)
   315  	if err != nil {
   316  		t.Fatal("read find nodes", err)
   317  	}
   318  	if reply.Kind() != v4wire.NeighborsPacket {
   319  		t.Fatal("Expected neighbors, got", reply.Name())
   320  	}
   321  	nodes := reply.(*v4wire.Neighbors).Nodes
   322  	if contains(nodes, encFakeKey) {
   323  		t.Fatal("neighbors response contains node from earlier unsolicited neighbors response")
   324  	}
   325  }
   326  
   327  // This test sends FINDNODE with an expiration timestamp in the past.
   328  // The remote node should not respond.
   329  func FindnodePastExpiration(t *utesting.T) {
   330  	te := newTestEnv(Remote, Listen1, Listen2)
   331  	defer te.close()
   332  	bond(t, te)
   333  
   334  	findnode := v4wire.Findnode{Expiration: -futureExpiration()}
   335  	rand.Read(findnode.Target[:])
   336  	te.send(te.l1, &findnode)
   337  
   338  	for {
   339  		reply, _, _ := te.read(te.l1)
   340  		if reply == nil {
   341  			return
   342  		} else if reply.Kind() == v4wire.NeighborsPacket {
   343  			t.Fatal("Unexpected NEIGHBORS response for expired FINDNODE request")
   344  		}
   345  	}
   346  }
   347  
   348  // bond performs the endpoint proof with the remote node.
   349  func bond(t *utesting.T, te *testenv) {
   350  	te.send(te.l1, &v4wire.Ping{
   351  		Version:    4,
   352  		From:       te.localEndpoint(te.l1),
   353  		To:         te.remoteEndpoint(),
   354  		Expiration: futureExpiration(),
   355  	})
   356  
   357  	var gotPing, gotPong bool
   358  	for !gotPing || !gotPong {
   359  		req, hash, err := te.read(te.l1)
   360  		if err != nil {
   361  			t.Fatal(err)
   362  		}
   363  		switch req.(type) {
   364  		case *v4wire.Ping:
   365  			te.send(te.l1, &v4wire.Pong{
   366  				To:         te.remoteEndpoint(),
   367  				ReplyTok:   hash,
   368  				Expiration: futureExpiration(),
   369  			})
   370  			gotPing = true
   371  		case *v4wire.Pong:
   372  			// TODO: maybe verify pong data here
   373  			gotPong = true
   374  		}
   375  	}
   376  }
   377  
   378  // This test attempts to perform a traffic amplification attack against a
   379  // 'victim' endpoint using FINDNODE. In this attack scenario, the attacker
   380  // attempts to complete the endpoint proof non-interactively by sending a PONG
   381  // with mismatching reply token from the 'victim' endpoint. The attack works if
   382  // the remote node does not verify the PONG reply token field correctly. The
   383  // attacker could then perform traffic amplification by sending many FINDNODE
   384  // requests to the discovery node, which would reply to the 'victim' address.
   385  func FindnodeAmplificationInvalidPongHash(t *utesting.T) {
   386  	te := newTestEnv(Remote, Listen1, Listen2)
   387  	defer te.close()
   388  
   389  	// Send PING to start endpoint verification.
   390  	te.send(te.l1, &v4wire.Ping{
   391  		Version:    4,
   392  		From:       te.localEndpoint(te.l1),
   393  		To:         te.remoteEndpoint(),
   394  		Expiration: futureExpiration(),
   395  	})
   396  
   397  	var gotPing, gotPong bool
   398  	for !gotPing || !gotPong {
   399  		req, _, err := te.read(te.l1)
   400  		if err != nil {
   401  			t.Fatal(err)
   402  		}
   403  		switch req.(type) {
   404  		case *v4wire.Ping:
   405  			// Send PONG from this node ID, but with invalid ReplyTok.
   406  			te.send(te.l1, &v4wire.Pong{
   407  				To:         te.remoteEndpoint(),
   408  				ReplyTok:   make([]byte, macSize),
   409  				Expiration: futureExpiration(),
   410  			})
   411  			gotPing = true
   412  		case *v4wire.Pong:
   413  			gotPong = true
   414  		}
   415  	}
   416  
   417  	// Now send FINDNODE. The remote node should not respond because our
   418  	// PONG did not reference the PING hash.
   419  	findnode := v4wire.Findnode{Expiration: futureExpiration()}
   420  	rand.Read(findnode.Target[:])
   421  	te.send(te.l1, &findnode)
   422  
   423  	// If we receive a NEIGHBORS response, the attack worked and the test fails.
   424  	reply, _, _ := te.read(te.l1)
   425  	if reply != nil && reply.Kind() == v4wire.NeighborsPacket {
   426  		t.Error("Got neighbors")
   427  	}
   428  }
   429  
   430  // This test attempts to perform a traffic amplification attack using FINDNODE.
   431  // The attack works if the remote node does not verify the IP address of FINDNODE
   432  // against the endpoint verification proof done by PING/PONG.
   433  func FindnodeAmplificationWrongIP(t *utesting.T) {
   434  	te := newTestEnv(Remote, Listen1, Listen2)
   435  	defer te.close()
   436  
   437  	// Do the endpoint proof from the l1 IP.
   438  	bond(t, te)
   439  
   440  	// Now send FINDNODE from the same node ID, but different IP address.
   441  	// The remote node should not respond.
   442  	findnode := v4wire.Findnode{Expiration: futureExpiration()}
   443  	rand.Read(findnode.Target[:])
   444  	te.send(te.l2, &findnode)
   445  
   446  	// If we receive a NEIGHBORS response, the attack worked and the test fails.
   447  	reply, _, _ := te.read(te.l2)
   448  	if reply != nil {
   449  		t.Error("Got NEIGHORS response for FINDNODE from wrong IP")
   450  	}
   451  }
   452  
   453  var AllTests = []utesting.Test{
   454  	{Name: "Ping/Basic", Fn: BasicPing},
   455  	{Name: "Ping/WrongTo", Fn: PingWrongTo},
   456  	{Name: "Ping/WrongFrom", Fn: PingWrongFrom},
   457  	{Name: "Ping/ExtraData", Fn: PingExtraData},
   458  	{Name: "Ping/ExtraDataWrongFrom", Fn: PingExtraDataWrongFrom},
   459  	{Name: "Ping/PastExpiration", Fn: PingPastExpiration},
   460  	{Name: "Ping/WrongPacketType", Fn: WrongPacketType},
   461  	{Name: "Ping/BondThenPingWithWrongFrom", Fn: BondThenPingWithWrongFrom},
   462  	{Name: "Findnode/WithoutEndpointProof", Fn: FindnodeWithoutEndpointProof},
   463  	{Name: "Findnode/BasicFindnode", Fn: BasicFindnode},
   464  	{Name: "Findnode/UnsolicitedNeighbors", Fn: UnsolicitedNeighbors},
   465  	{Name: "Findnode/PastExpiration", Fn: FindnodePastExpiration},
   466  	{Name: "Amplification/InvalidPongHash", Fn: FindnodeAmplificationInvalidPongHash},
   467  	{Name: "Amplification/WrongIP", Fn: FindnodeAmplificationWrongIP},
   468  }