This commit is contained in:
Csaba Kiraly 2026-05-21 21:54:14 -07:00 committed by GitHub
commit 7d7ff5fdd4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/internal/utesting"
"github.com/ethereum/go-ethereum/p2p/discover/v5wire" "github.com/ethereum/go-ethereum/p2p/discover/v5wire"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/p2p/netutil"
) )
@ -54,8 +55,11 @@ func (s *Suite) AllTests() []utesting.Test {
{Name: "PingMultiIP", Fn: s.TestPingMultiIP}, {Name: "PingMultiIP", Fn: s.TestPingMultiIP},
{Name: "HandshakeResend", Fn: s.TestHandshakeResend}, {Name: "HandshakeResend", Fn: s.TestHandshakeResend},
{Name: "TalkRequest", Fn: s.TestTalkRequest}, {Name: "TalkRequest", Fn: s.TestTalkRequest},
{Name: "FindnodeWrongIP", Fn: s.TestFindnodeWrongIP},
{Name: "FindnodeHandshake", Fn: s.TestFindnodeHandshake},
{Name: "FindnodeZeroDistance", Fn: s.TestFindnodeZeroDistance}, {Name: "FindnodeZeroDistance", Fn: s.TestFindnodeZeroDistance},
{Name: "FindnodeResults", Fn: s.TestFindnodeResults}, {Name: "FindnodeResults", Fn: s.TestFindnodeResults},
{Name: "UnsolicitedNodes", Fn: s.TestUnsolicitedNodes},
} }
} }
@ -232,6 +236,72 @@ and expects an empty TALKRESP response.`)
} }
} }
func (s *Suite) TestFindnodeWrongIP(t *utesting.T) {
t.Log(`This test establishes a session on one IP, then sends FINDNODE from another IP.
The remote node should challenge the second endpoint with WHOAREYOU instead of returning NODES.`)
conn, l1, l2 := s.listen2(t)
defer conn.close()
ping := &v5wire.Ping{ReqID: conn.nextReqID()}
if resp := conn.reqresp(l1, ping); resp.Kind() != v5wire.PongMsg {
t.Fatal("expected PONG, got", resp)
}
req := &v5wire.Findnode{ReqID: conn.nextReqID(), Distances: []uint{0}}
conn.write(l2, req, nil)
switch resp := conn.read(l2).(type) {
case *v5wire.Whoareyou:
t.Log("got WHOAREYOU for FINDNODE on wrong IP as expected")
case *v5wire.Nodes:
t.Fatalf("unexpected NODES response on wrong IP: %+v", resp)
default:
t.Fatal("expected WHOAREYOU, got", resp)
}
}
func (s *Suite) TestFindnodeHandshake(t *utesting.T) {
t.Log(`This test checks that the remote answers a FINDNODE request only after completing the WHOAREYOU handshake.`)
conn, l1 := s.listen1(t)
defer conn.close()
req := &v5wire.Findnode{ReqID: conn.nextReqID(), Distances: []uint{0}}
nonce := conn.write(l1, req, nil)
resp, from := conn.readFrom(l1)
challenge, ok := resp.(*v5wire.Whoareyou)
if !ok {
t.Fatalf("expected WHOAREYOU before NODES, got %T (%v) from %v", resp, resp, from)
}
if challenge.Nonce != nonce {
t.Fatalf("wrong nonce %x in WHOAREYOU (want %x)", challenge.Nonce[:], nonce[:])
}
challenge.Node = conn.remote
conn.writeTo(l1, req, challenge, from)
for {
resp, from := conn.readFrom(l1)
switch resp := resp.(type) {
case *v5wire.Ping:
conn.writeTo(l1, &v5wire.Pong{
ReqID: resp.ReqID,
ENRSeq: conn.localNode.Seq(),
ToIP: from.IP,
ToPort: uint16(from.Port),
}, nil, from)
case *v5wire.Nodes:
if !bytes.Equal(resp.ReqID, req.ReqID) {
t.Fatalf("wrong request ID %x in NODES, want %x", resp.ReqID, req.ReqID)
}
return
default:
t.Fatalf("expected NODES after completing handshake, got %T (%v) from %v", resp, resp, from)
}
}
}
func (s *Suite) TestFindnodeZeroDistance(t *utesting.T) { func (s *Suite) TestFindnodeZeroDistance(t *utesting.T) {
t.Log(`This test checks that the remote node returns itself for FINDNODE with distance zero.`) t.Log(`This test checks that the remote node returns itself for FINDNODE with distance zero.`)
@ -250,6 +320,70 @@ func (s *Suite) TestFindnodeZeroDistance(t *utesting.T) {
} }
} }
func (s *Suite) TestUnsolicitedNodes(t *utesting.T) {
t.Log(`This test sends an unsolicited NODES response advertising a fake node.
The remote node should neither contact the injected node nor return it from later FINDNODE queries.`)
injectConn, injectL := s.listen1(t)
defer injectConn.close()
// Establish a session for the injection path so the unsolicited packet is
// well-formed and authenticated.
ping := &v5wire.Ping{ReqID: injectConn.nextReqID()}
if resp := injectConn.reqresp(injectL, ping); resp.Kind() != v5wire.PongMsg {
t.Fatal("expected PONG, got", resp)
}
fakeConn, fakeL := s.listen1(t)
defer fakeConn.close()
fakeConn.setEndpoint(fakeL)
unsolicited := &v5wire.Nodes{
ReqID: injectConn.nextReqID(),
RespCount: 1,
Nodes: []*enr.Record{fakeConn.localNode.Node().Record()},
}
t.Log("sending unsolicited NODES response with injected node")
injectConn.write(injectL, unsolicited, nil)
checkNoContact := func(phase string) {
buf := make([]byte, 1280)
if err := fakeL.SetReadDeadline(time.Now()); err != nil {
t.Fatal(err)
}
for {
n, from, err := fakeL.ReadFrom(buf)
if err == nil {
t.Fatalf("%s: remote contacted injected node: %d bytes from %v", phase, n, from)
}
if netutil.IsTimeout(err) {
return
}
t.Fatalf("%s: checking for unexpected contact failed: %v", phase, err)
}
}
checkNoContact("after unsolicited NODES")
probeConn, probeL := s.listen1(t)
defer probeConn.close()
dist := uint(enode.LogDist(fakeConn.localNode.ID(), s.Dest.ID()))
const maxAttempts = 3
for attempt := 1; attempt <= maxAttempts; attempt++ {
results, err := probeConn.findnode(probeL, []uint{dist})
if err != nil {
t.Fatal(err)
}
checkNoContact("during FINDNODE probes")
for _, n := range results {
if n.ID() == fakeConn.localNode.ID() {
t.Fatalf("attempt %d: FINDNODE result contains node from unsolicited NODES response", attempt)
}
}
t.Logf("attempt %d: injected node not returned in FINDNODE results", attempt)
}
}
func (s *Suite) TestFindnodeResults(t *utesting.T) { func (s *Suite) TestFindnodeResults(t *utesting.T) {
t.Log(`This test pings the node under test from multiple other endpoints and node identities t.Log(`This test pings the node under test from multiple other endpoints and node identities
(the 'bystanders'). After waiting for them to be accepted into the remote table, the test checks (the 'bystanders'). After waiting for them to be accepted into the remote table, the test checks