468 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			468 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								// Copyright 2020 The go-ethereum Authors
							 | 
						||
| 
								 | 
							
								// This file is part of go-ethereum.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// go-ethereum is free software: you can redistribute it and/or modify
							 | 
						||
| 
								 | 
							
								// it under the terms of the GNU General Public License as published by
							 | 
						||
| 
								 | 
							
								// the Free Software Foundation, either version 3 of the License, or
							 | 
						||
| 
								 | 
							
								// (at your option) any later version.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// go-ethereum is distributed in the hope that it will be useful,
							 | 
						||
| 
								 | 
							
								// but WITHOUT ANY WARRANTY; without even the implied warranty of
							 | 
						||
| 
								 | 
							
								// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
							 | 
						||
| 
								 | 
							
								// GNU General Public License for more details.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// You should have received a copy of the GNU General Public License
							 | 
						||
| 
								 | 
							
								// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								package v4test
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import (
							 | 
						||
| 
								 | 
							
									"bytes"
							 | 
						||
| 
								 | 
							
									"crypto/rand"
							 | 
						||
| 
								 | 
							
									"fmt"
							 | 
						||
| 
								 | 
							
									"net"
							 | 
						||
| 
								 | 
							
									"reflect"
							 | 
						||
| 
								 | 
							
									"time"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									"github.com/ethereum/go-ethereum/crypto"
							 | 
						||
| 
								 | 
							
									"github.com/ethereum/go-ethereum/internal/utesting"
							 | 
						||
| 
								 | 
							
									"github.com/ethereum/go-ethereum/p2p/discover/v4wire"
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const (
							 | 
						||
| 
								 | 
							
									expiration  = 20 * time.Second
							 | 
						||
| 
								 | 
							
									wrongPacket = 66
							 | 
						||
| 
								 | 
							
									macSize     = 256 / 8
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var (
							 | 
						||
| 
								 | 
							
									// Remote node under test
							 | 
						||
| 
								 | 
							
									Remote string
							 | 
						||
| 
								 | 
							
									// IP where the first tester is listening, port will be assigned
							 | 
						||
| 
								 | 
							
									Listen1 string = "127.0.0.1"
							 | 
						||
| 
								 | 
							
									// IP where the second tester is listening, port will be assigned
							 | 
						||
| 
								 | 
							
									// Before running the test, you may have to `sudo ifconfig lo0 add 127.0.0.2` (on MacOS at least)
							 | 
						||
| 
								 | 
							
									Listen2 string = "127.0.0.2"
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								type pingWithJunk struct {
							 | 
						||
| 
								 | 
							
									Version    uint
							 | 
						||
| 
								 | 
							
									From, To   v4wire.Endpoint
							 | 
						||
| 
								 | 
							
									Expiration uint64
							 | 
						||
| 
								 | 
							
									JunkData1  uint
							 | 
						||
| 
								 | 
							
									JunkData2  []byte
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (req *pingWithJunk) Name() string { return "PING/v4" }
							 | 
						||
| 
								 | 
							
								func (req *pingWithJunk) Kind() byte   { return v4wire.PingPacket }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								type pingWrongType struct {
							 | 
						||
| 
								 | 
							
									Version    uint
							 | 
						||
| 
								 | 
							
									From, To   v4wire.Endpoint
							 | 
						||
| 
								 | 
							
									Expiration uint64
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (req *pingWrongType) Name() string { return "WRONG/v4" }
							 | 
						||
| 
								 | 
							
								func (req *pingWrongType) Kind() byte   { return wrongPacket }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func futureExpiration() uint64 {
							 | 
						||
| 
								 | 
							
									return uint64(time.Now().Add(expiration).Unix())
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This test just sends a PING packet and expects a response.
							 | 
						||
| 
								 | 
							
								func BasicPing(t *utesting.T) {
							 | 
						||
| 
								 | 
							
									te := newTestEnv(Remote, Listen1, Listen2)
							 | 
						||
| 
								 | 
							
									defer te.close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									pingHash := te.send(te.l1, &v4wire.Ping{
							 | 
						||
| 
								 | 
							
										Version:    4,
							 | 
						||
| 
								 | 
							
										From:       te.localEndpoint(te.l1),
							 | 
						||
| 
								 | 
							
										To:         te.remoteEndpoint(),
							 | 
						||
| 
								 | 
							
										Expiration: futureExpiration(),
							 | 
						||
| 
								 | 
							
									})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									reply, _, _ := te.read(te.l1)
							 | 
						||
| 
								 | 
							
									if err := te.checkPong(reply, pingHash); err != nil {
							 | 
						||
| 
								 | 
							
										t.Fatal(err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// checkPong verifies that reply is a valid PONG matching the given ping hash.
							 | 
						||
| 
								 | 
							
								func (te *testenv) checkPong(reply v4wire.Packet, pingHash []byte) error {
							 | 
						||
| 
								 | 
							
									if reply == nil || reply.Kind() != v4wire.PongPacket {
							 | 
						||
| 
								 | 
							
										return fmt.Errorf("expected PONG reply, got %v", reply)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									pong := reply.(*v4wire.Pong)
							 | 
						||
| 
								 | 
							
									if !bytes.Equal(pong.ReplyTok, pingHash) {
							 | 
						||
| 
								 | 
							
										return fmt.Errorf("PONG reply token mismatch: got %x, want %x", pong.ReplyTok, pingHash)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									wantEndpoint := te.localEndpoint(te.l1)
							 | 
						||
| 
								 | 
							
									if !reflect.DeepEqual(pong.To, wantEndpoint) {
							 | 
						||
| 
								 | 
							
										return fmt.Errorf("PONG 'to' endpoint mismatch: got %+v, want %+v", pong.To, wantEndpoint)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if v4wire.Expired(pong.Expiration) {
							 | 
						||
| 
								 | 
							
										return fmt.Errorf("PONG is expired (%v)", pong.Expiration)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return nil
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This test sends a PING packet with wrong 'to' field and expects a PONG response.
							 | 
						||
| 
								 | 
							
								func PingWrongTo(t *utesting.T) {
							 | 
						||
| 
								 | 
							
									te := newTestEnv(Remote, Listen1, Listen2)
							 | 
						||
| 
								 | 
							
									defer te.close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")}
							 | 
						||
| 
								 | 
							
									pingHash := te.send(te.l1, &v4wire.Ping{
							 | 
						||
| 
								 | 
							
										Version:    4,
							 | 
						||
| 
								 | 
							
										From:       te.localEndpoint(te.l1),
							 | 
						||
| 
								 | 
							
										To:         wrongEndpoint,
							 | 
						||
| 
								 | 
							
										Expiration: futureExpiration(),
							 | 
						||
| 
								 | 
							
									})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									reply, _, _ := te.read(te.l1)
							 | 
						||
| 
								 | 
							
									if err := te.checkPong(reply, pingHash); err != nil {
							 | 
						||
| 
								 | 
							
										t.Fatal(err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This test sends a PING packet with wrong 'from' field and expects a PONG response.
							 | 
						||
| 
								 | 
							
								func PingWrongFrom(t *utesting.T) {
							 | 
						||
| 
								 | 
							
									te := newTestEnv(Remote, Listen1, Listen2)
							 | 
						||
| 
								 | 
							
									defer te.close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")}
							 | 
						||
| 
								 | 
							
									pingHash := te.send(te.l1, &v4wire.Ping{
							 | 
						||
| 
								 | 
							
										Version:    4,
							 | 
						||
| 
								 | 
							
										From:       wrongEndpoint,
							 | 
						||
| 
								 | 
							
										To:         te.remoteEndpoint(),
							 | 
						||
| 
								 | 
							
										Expiration: futureExpiration(),
							 | 
						||
| 
								 | 
							
									})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									reply, _, _ := te.read(te.l1)
							 | 
						||
| 
								 | 
							
									if err := te.checkPong(reply, pingHash); err != nil {
							 | 
						||
| 
								 | 
							
										t.Fatal(err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This test sends a PING packet with additional data at the end and expects a PONG
							 | 
						||
| 
								 | 
							
								// response. The remote node should respond because EIP-8 mandates ignoring additional
							 | 
						||
| 
								 | 
							
								// trailing data.
							 | 
						||
| 
								 | 
							
								func PingExtraData(t *utesting.T) {
							 | 
						||
| 
								 | 
							
									te := newTestEnv(Remote, Listen1, Listen2)
							 | 
						||
| 
								 | 
							
									defer te.close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									pingHash := te.send(te.l1, &pingWithJunk{
							 | 
						||
| 
								 | 
							
										Version:    4,
							 | 
						||
| 
								 | 
							
										From:       te.localEndpoint(te.l1),
							 | 
						||
| 
								 | 
							
										To:         te.remoteEndpoint(),
							 | 
						||
| 
								 | 
							
										Expiration: futureExpiration(),
							 | 
						||
| 
								 | 
							
										JunkData1:  42,
							 | 
						||
| 
								 | 
							
										JunkData2:  []byte{9, 8, 7, 6, 5, 4, 3, 2, 1},
							 | 
						||
| 
								 | 
							
									})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									reply, _, _ := te.read(te.l1)
							 | 
						||
| 
								 | 
							
									if err := te.checkPong(reply, pingHash); err != nil {
							 | 
						||
| 
								 | 
							
										t.Fatal(err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This test sends a PING packet with additional data and wrong 'from' field
							 | 
						||
| 
								 | 
							
								// and expects a PONG response.
							 | 
						||
| 
								 | 
							
								func PingExtraDataWrongFrom(t *utesting.T) {
							 | 
						||
| 
								 | 
							
									te := newTestEnv(Remote, Listen1, Listen2)
							 | 
						||
| 
								 | 
							
									defer te.close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")}
							 | 
						||
| 
								 | 
							
									req := pingWithJunk{
							 | 
						||
| 
								 | 
							
										Version:    4,
							 | 
						||
| 
								 | 
							
										From:       wrongEndpoint,
							 | 
						||
| 
								 | 
							
										To:         te.remoteEndpoint(),
							 | 
						||
| 
								 | 
							
										Expiration: futureExpiration(),
							 | 
						||
| 
								 | 
							
										JunkData1:  42,
							 | 
						||
| 
								 | 
							
										JunkData2:  []byte{9, 8, 7, 6, 5, 4, 3, 2, 1},
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									pingHash := te.send(te.l1, &req)
							 | 
						||
| 
								 | 
							
									reply, _, _ := te.read(te.l1)
							 | 
						||
| 
								 | 
							
									if err := te.checkPong(reply, pingHash); err != nil {
							 | 
						||
| 
								 | 
							
										t.Fatal(err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This test sends a PING packet with an expiration in the past.
							 | 
						||
| 
								 | 
							
								// The remote node should not respond.
							 | 
						||
| 
								 | 
							
								func PingPastExpiration(t *utesting.T) {
							 | 
						||
| 
								 | 
							
									te := newTestEnv(Remote, Listen1, Listen2)
							 | 
						||
| 
								 | 
							
									defer te.close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									te.send(te.l1, &v4wire.Ping{
							 | 
						||
| 
								 | 
							
										Version:    4,
							 | 
						||
| 
								 | 
							
										From:       te.localEndpoint(te.l1),
							 | 
						||
| 
								 | 
							
										To:         te.remoteEndpoint(),
							 | 
						||
| 
								 | 
							
										Expiration: -futureExpiration(),
							 | 
						||
| 
								 | 
							
									})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									reply, _, _ := te.read(te.l1)
							 | 
						||
| 
								 | 
							
									if reply != nil {
							 | 
						||
| 
								 | 
							
										t.Fatal("Expected no reply, got", reply)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This test sends an invalid packet. The remote node should not respond.
							 | 
						||
| 
								 | 
							
								func WrongPacketType(t *utesting.T) {
							 | 
						||
| 
								 | 
							
									te := newTestEnv(Remote, Listen1, Listen2)
							 | 
						||
| 
								 | 
							
									defer te.close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									te.send(te.l1, &pingWrongType{
							 | 
						||
| 
								 | 
							
										Version:    4,
							 | 
						||
| 
								 | 
							
										From:       te.localEndpoint(te.l1),
							 | 
						||
| 
								 | 
							
										To:         te.remoteEndpoint(),
							 | 
						||
| 
								 | 
							
										Expiration: futureExpiration(),
							 | 
						||
| 
								 | 
							
									})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									reply, _, _ := te.read(te.l1)
							 | 
						||
| 
								 | 
							
									if reply != nil {
							 | 
						||
| 
								 | 
							
										t.Fatal("Expected no reply, got", reply)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This test verifies that the default behaviour of ignoring 'from' fields is unaffected by
							 | 
						||
| 
								 | 
							
								// the bonding process. After bonding, it pings the target with a different from endpoint.
							 | 
						||
| 
								 | 
							
								func BondThenPingWithWrongFrom(t *utesting.T) {
							 | 
						||
| 
								 | 
							
									te := newTestEnv(Remote, Listen1, Listen2)
							 | 
						||
| 
								 | 
							
									defer te.close()
							 | 
						||
| 
								 | 
							
									bond(t, te)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")}
							 | 
						||
| 
								 | 
							
									pingHash := te.send(te.l1, &v4wire.Ping{
							 | 
						||
| 
								 | 
							
										Version:    4,
							 | 
						||
| 
								 | 
							
										From:       wrongEndpoint,
							 | 
						||
| 
								 | 
							
										To:         te.remoteEndpoint(),
							 | 
						||
| 
								 | 
							
										Expiration: futureExpiration(),
							 | 
						||
| 
								 | 
							
									})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									reply, _, _ := te.read(te.l1)
							 | 
						||
| 
								 | 
							
									if err := te.checkPong(reply, pingHash); err != nil {
							 | 
						||
| 
								 | 
							
										t.Fatal(err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This test just sends FINDNODE. The remote node should not reply
							 | 
						||
| 
								 | 
							
								// because the endpoint proof has not completed.
							 | 
						||
| 
								 | 
							
								func FindnodeWithoutEndpointProof(t *utesting.T) {
							 | 
						||
| 
								 | 
							
									te := newTestEnv(Remote, Listen1, Listen2)
							 | 
						||
| 
								 | 
							
									defer te.close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									req := v4wire.Findnode{Expiration: futureExpiration()}
							 | 
						||
| 
								 | 
							
									rand.Read(req.Target[:])
							 | 
						||
| 
								 | 
							
									te.send(te.l1, &req)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									reply, _, _ := te.read(te.l1)
							 | 
						||
| 
								 | 
							
									if reply != nil {
							 | 
						||
| 
								 | 
							
										t.Fatal("Expected no response, got", reply)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// BasicFindnode sends a FINDNODE request after performing the endpoint
							 | 
						||
| 
								 | 
							
								// proof. The remote node should respond.
							 | 
						||
| 
								 | 
							
								func BasicFindnode(t *utesting.T) {
							 | 
						||
| 
								 | 
							
									te := newTestEnv(Remote, Listen1, Listen2)
							 | 
						||
| 
								 | 
							
									defer te.close()
							 | 
						||
| 
								 | 
							
									bond(t, te)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									findnode := v4wire.Findnode{Expiration: futureExpiration()}
							 | 
						||
| 
								 | 
							
									rand.Read(findnode.Target[:])
							 | 
						||
| 
								 | 
							
									te.send(te.l1, &findnode)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									reply, _, err := te.read(te.l1)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										t.Fatal("read find nodes", err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if reply.Kind() != v4wire.NeighborsPacket {
							 | 
						||
| 
								 | 
							
										t.Fatal("Expected neighbors, got", reply.Name())
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This test sends an unsolicited NEIGHBORS packet after the endpoint proof, then sends
							 | 
						||
| 
								 | 
							
								// FINDNODE to read the remote table. The remote node should not return the node contained
							 | 
						||
| 
								 | 
							
								// in the unsolicited NEIGHBORS packet.
							 | 
						||
| 
								 | 
							
								func UnsolicitedNeighbors(t *utesting.T) {
							 | 
						||
| 
								 | 
							
									te := newTestEnv(Remote, Listen1, Listen2)
							 | 
						||
| 
								 | 
							
									defer te.close()
							 | 
						||
| 
								 | 
							
									bond(t, te)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Send unsolicited NEIGHBORS response.
							 | 
						||
| 
								 | 
							
									fakeKey, _ := crypto.GenerateKey()
							 | 
						||
| 
								 | 
							
									encFakeKey := v4wire.EncodePubkey(&fakeKey.PublicKey)
							 | 
						||
| 
								 | 
							
									neighbors := v4wire.Neighbors{
							 | 
						||
| 
								 | 
							
										Expiration: futureExpiration(),
							 | 
						||
| 
								 | 
							
										Nodes: []v4wire.Node{{
							 | 
						||
| 
								 | 
							
											ID:  encFakeKey,
							 | 
						||
| 
								 | 
							
											IP:  net.IP{1, 2, 3, 4},
							 | 
						||
| 
								 | 
							
											UDP: 30303,
							 | 
						||
| 
								 | 
							
											TCP: 30303,
							 | 
						||
| 
								 | 
							
										}},
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									te.send(te.l1, &neighbors)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Check if the remote node included the fake node.
							 | 
						||
| 
								 | 
							
									te.send(te.l1, &v4wire.Findnode{
							 | 
						||
| 
								 | 
							
										Expiration: futureExpiration(),
							 | 
						||
| 
								 | 
							
										Target:     encFakeKey,
							 | 
						||
| 
								 | 
							
									})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									reply, _, err := te.read(te.l1)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										t.Fatal("read find nodes", err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if reply.Kind() != v4wire.NeighborsPacket {
							 | 
						||
| 
								 | 
							
										t.Fatal("Expected neighbors, got", reply.Name())
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									nodes := reply.(*v4wire.Neighbors).Nodes
							 | 
						||
| 
								 | 
							
									if contains(nodes, encFakeKey) {
							 | 
						||
| 
								 | 
							
										t.Fatal("neighbors response contains node from earlier unsolicited neighbors response")
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This test sends FINDNODE with an expiration timestamp in the past.
							 | 
						||
| 
								 | 
							
								// The remote node should not respond.
							 | 
						||
| 
								 | 
							
								func FindnodePastExpiration(t *utesting.T) {
							 | 
						||
| 
								 | 
							
									te := newTestEnv(Remote, Listen1, Listen2)
							 | 
						||
| 
								 | 
							
									defer te.close()
							 | 
						||
| 
								 | 
							
									bond(t, te)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									findnode := v4wire.Findnode{Expiration: -futureExpiration()}
							 | 
						||
| 
								 | 
							
									rand.Read(findnode.Target[:])
							 | 
						||
| 
								 | 
							
									te.send(te.l1, &findnode)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									for {
							 | 
						||
| 
								 | 
							
										reply, _, _ := te.read(te.l1)
							 | 
						||
| 
								 | 
							
										if reply == nil {
							 | 
						||
| 
								 | 
							
											return
							 | 
						||
| 
								 | 
							
										} else if reply.Kind() == v4wire.NeighborsPacket {
							 | 
						||
| 
								 | 
							
											t.Fatal("Unexpected NEIGHBORS response for expired FINDNODE request")
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// bond performs the endpoint proof with the remote node.
							 | 
						||
| 
								 | 
							
								func bond(t *utesting.T, te *testenv) {
							 | 
						||
| 
								 | 
							
									te.send(te.l1, &v4wire.Ping{
							 | 
						||
| 
								 | 
							
										Version:    4,
							 | 
						||
| 
								 | 
							
										From:       te.localEndpoint(te.l1),
							 | 
						||
| 
								 | 
							
										To:         te.remoteEndpoint(),
							 | 
						||
| 
								 | 
							
										Expiration: futureExpiration(),
							 | 
						||
| 
								 | 
							
									})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									var gotPing, gotPong bool
							 | 
						||
| 
								 | 
							
									for !gotPing || !gotPong {
							 | 
						||
| 
								 | 
							
										req, hash, err := te.read(te.l1)
							 | 
						||
| 
								 | 
							
										if err != nil {
							 | 
						||
| 
								 | 
							
											t.Fatal(err)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										switch req.(type) {
							 | 
						||
| 
								 | 
							
										case *v4wire.Ping:
							 | 
						||
| 
								 | 
							
											te.send(te.l1, &v4wire.Pong{
							 | 
						||
| 
								 | 
							
												To:         te.remoteEndpoint(),
							 | 
						||
| 
								 | 
							
												ReplyTok:   hash,
							 | 
						||
| 
								 | 
							
												Expiration: futureExpiration(),
							 | 
						||
| 
								 | 
							
											})
							 | 
						||
| 
								 | 
							
											gotPing = true
							 | 
						||
| 
								 | 
							
										case *v4wire.Pong:
							 | 
						||
| 
								 | 
							
											// TODO: maybe verify pong data here
							 | 
						||
| 
								 | 
							
											gotPong = true
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This test attempts to perform a traffic amplification attack against a
							 | 
						||
| 
								 | 
							
								// 'victim' endpoint using FINDNODE. In this attack scenario, the attacker
							 | 
						||
| 
								 | 
							
								// attempts to complete the endpoint proof non-interactively by sending a PONG
							 | 
						||
| 
								 | 
							
								// with mismatching reply token from the 'victim' endpoint. The attack works if
							 | 
						||
| 
								 | 
							
								// the remote node does not verify the PONG reply token field correctly. The
							 | 
						||
| 
								 | 
							
								// attacker could then perform traffic amplification by sending many FINDNODE
							 | 
						||
| 
								 | 
							
								// requests to the discovery node, which would reply to the 'victim' address.
							 | 
						||
| 
								 | 
							
								func FindnodeAmplificationInvalidPongHash(t *utesting.T) {
							 | 
						||
| 
								 | 
							
									te := newTestEnv(Remote, Listen1, Listen2)
							 | 
						||
| 
								 | 
							
									defer te.close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Send PING to start endpoint verification.
							 | 
						||
| 
								 | 
							
									te.send(te.l1, &v4wire.Ping{
							 | 
						||
| 
								 | 
							
										Version:    4,
							 | 
						||
| 
								 | 
							
										From:       te.localEndpoint(te.l1),
							 | 
						||
| 
								 | 
							
										To:         te.remoteEndpoint(),
							 | 
						||
| 
								 | 
							
										Expiration: futureExpiration(),
							 | 
						||
| 
								 | 
							
									})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									var gotPing, gotPong bool
							 | 
						||
| 
								 | 
							
									for !gotPing || !gotPong {
							 | 
						||
| 
								 | 
							
										req, _, err := te.read(te.l1)
							 | 
						||
| 
								 | 
							
										if err != nil {
							 | 
						||
| 
								 | 
							
											t.Fatal(err)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										switch req.(type) {
							 | 
						||
| 
								 | 
							
										case *v4wire.Ping:
							 | 
						||
| 
								 | 
							
											// Send PONG from this node ID, but with invalid ReplyTok.
							 | 
						||
| 
								 | 
							
											te.send(te.l1, &v4wire.Pong{
							 | 
						||
| 
								 | 
							
												To:         te.remoteEndpoint(),
							 | 
						||
| 
								 | 
							
												ReplyTok:   make([]byte, macSize),
							 | 
						||
| 
								 | 
							
												Expiration: futureExpiration(),
							 | 
						||
| 
								 | 
							
											})
							 | 
						||
| 
								 | 
							
											gotPing = true
							 | 
						||
| 
								 | 
							
										case *v4wire.Pong:
							 | 
						||
| 
								 | 
							
											gotPong = true
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Now send FINDNODE. The remote node should not respond because our
							 | 
						||
| 
								 | 
							
									// PONG did not reference the PING hash.
							 | 
						||
| 
								 | 
							
									findnode := v4wire.Findnode{Expiration: futureExpiration()}
							 | 
						||
| 
								 | 
							
									rand.Read(findnode.Target[:])
							 | 
						||
| 
								 | 
							
									te.send(te.l1, &findnode)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// If we receive a NEIGHBORS response, the attack worked and the test fails.
							 | 
						||
| 
								 | 
							
									reply, _, _ := te.read(te.l1)
							 | 
						||
| 
								 | 
							
									if reply != nil && reply.Kind() == v4wire.NeighborsPacket {
							 | 
						||
| 
								 | 
							
										t.Error("Got neighbors")
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This test attempts to perform a traffic amplification attack using FINDNODE.
							 | 
						||
| 
								 | 
							
								// The attack works if the remote node does not verify the IP address of FINDNODE
							 | 
						||
| 
								 | 
							
								// against the endpoint verification proof done by PING/PONG.
							 | 
						||
| 
								 | 
							
								func FindnodeAmplificationWrongIP(t *utesting.T) {
							 | 
						||
| 
								 | 
							
									te := newTestEnv(Remote, Listen1, Listen2)
							 | 
						||
| 
								 | 
							
									defer te.close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Do the endpoint proof from the l1 IP.
							 | 
						||
| 
								 | 
							
									bond(t, te)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Now send FINDNODE from the same node ID, but different IP address.
							 | 
						||
| 
								 | 
							
									// The remote node should not respond.
							 | 
						||
| 
								 | 
							
									findnode := v4wire.Findnode{Expiration: futureExpiration()}
							 | 
						||
| 
								 | 
							
									rand.Read(findnode.Target[:])
							 | 
						||
| 
								 | 
							
									te.send(te.l2, &findnode)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// If we receive a NEIGHBORS response, the attack worked and the test fails.
							 | 
						||
| 
								 | 
							
									reply, _, _ := te.read(te.l2)
							 | 
						||
| 
								 | 
							
									if reply != nil {
							 | 
						||
| 
								 | 
							
										t.Error("Got NEIGHORS response for FINDNODE from wrong IP")
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var AllTests = []utesting.Test{
							 | 
						||
| 
								 | 
							
									{Name: "Ping/Basic", Fn: BasicPing},
							 | 
						||
| 
								 | 
							
									{Name: "Ping/WrongTo", Fn: PingWrongTo},
							 | 
						||
| 
								 | 
							
									{Name: "Ping/WrongFrom", Fn: PingWrongFrom},
							 | 
						||
| 
								 | 
							
									{Name: "Ping/ExtraData", Fn: PingExtraData},
							 | 
						||
| 
								 | 
							
									{Name: "Ping/ExtraDataWrongFrom", Fn: PingExtraDataWrongFrom},
							 | 
						||
| 
								 | 
							
									{Name: "Ping/PastExpiration", Fn: PingPastExpiration},
							 | 
						||
| 
								 | 
							
									{Name: "Ping/WrongPacketType", Fn: WrongPacketType},
							 | 
						||
| 
								 | 
							
									{Name: "Ping/BondThenPingWithWrongFrom", Fn: BondThenPingWithWrongFrom},
							 | 
						||
| 
								 | 
							
									{Name: "Findnode/WithoutEndpointProof", Fn: FindnodeWithoutEndpointProof},
							 | 
						||
| 
								 | 
							
									{Name: "Findnode/BasicFindnode", Fn: BasicFindnode},
							 | 
						||
| 
								 | 
							
									{Name: "Findnode/UnsolicitedNeighbors", Fn: UnsolicitedNeighbors},
							 | 
						||
| 
								 | 
							
									{Name: "Findnode/PastExpiration", Fn: FindnodePastExpiration},
							 | 
						||
| 
								 | 
							
									{Name: "Amplification/InvalidPongHash", Fn: FindnodeAmplificationInvalidPongHash},
							 | 
						||
| 
								 | 
							
									{Name: "Amplification/WrongIP", Fn: FindnodeAmplificationWrongIP},
							 | 
						||
| 
								 | 
							
								}
							 |