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