Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

p2p: Transmit ENR in RLPX handshake #19220

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions p2p/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package p2p

import (
"encoding/hex"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -429,6 +430,7 @@ type PeerInfo struct {
ID string `json:"id"` // Unique node identifier
Name string `json:"name"` // Name of the node, including client type, version, OS, custom data
Caps []string `json:"caps"` // Protocols advertised by this peer
ENR string `json:"enr"` // Ethereum Node Record
Network struct {
LocalAddress string `json:"localAddress"` // Local endpoint of the TCP data connection
RemoteAddress string `json:"remoteAddress"` // Remote endpoint of the TCP data connection
Expand All @@ -454,6 +456,9 @@ func (p *Peer) Info() *PeerInfo {
Caps: caps,
Protocols: make(map[string]interface{}),
}
if enc, err := rlp.EncodeToBytes(p.Node().Record()); err == nil {
info.ENR = "0x" + hex.EncodeToString(enc)
}
info.Network.LocalAddress = p.LocalAddr().String()
info.Network.RemoteAddress = p.RemoteAddr().String()
info.Network.Inbound = p.rw.is(inboundConn)
Expand Down
70 changes: 56 additions & 14 deletions p2p/rlpx.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/ecies"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/rlp"
"github.com/golang/snappy"
"golang.org/x/crypto/sha3"
Expand Down Expand Up @@ -77,15 +79,16 @@ var errPlainMessageTooLarge = errors.New("message length >= 16MB")
// rlpx is the transport protocol used by actual (non-test) connections.
// It wraps the frame encoder with locks and read/write deadlines.
type rlpx struct {
fd net.Conn
self *enode.Node
fd net.Conn

rmu, wmu sync.Mutex
rw *rlpxFrameRW
}

func newRLPX(fd net.Conn) transport {
func newRLPX(self *enode.Node, fd net.Conn) transport {
fd.SetDeadline(time.Now().Add(handshakeTimeout))
return &rlpx{fd: fd}
return &rlpx{self: self, fd: fd}
}

func (t *rlpx) ReadMsg() (Msg, error) {
Expand Down Expand Up @@ -175,23 +178,23 @@ func readProtocolHandshake(rw MsgReader, our *protoHandshake) (*protoHandshake,
// messages. the protocol handshake is the first authenticated message
// and also verifies whether the encryption handshake 'worked' and the
// remote side actually provided the right public key.
func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *ecdsa.PublicKey) (*ecdsa.PublicKey, error) {
func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *ecdsa.PublicKey) (*ecdsa.PublicKey, *enode.Node, error) {
var (
sec secrets
err error
)
if dial == nil {
sec, err = receiverEncHandshake(t.fd, prv)
sec, err = receiverEncHandshake(t.fd, prv, t.self)
} else {
sec, err = initiatorEncHandshake(t.fd, prv, dial)
sec, err = initiatorEncHandshake(t.fd, prv, t.self, dial)
}
if err != nil {
return nil, err
return nil, nil, err
}
t.wmu.Lock()
t.rw = newRLPXFrameRW(t.fd, sec)
t.wmu.Unlock()
return sec.Remote.ExportECDSA(), nil
return sec.Remote.ExportECDSA(), sec.RemoteNode, nil
}

// encHandshake contains the state of the encryption handshake.
Expand All @@ -201,6 +204,7 @@ type encHandshake struct {
initNonce, respNonce []byte // nonce
randomPrivKey *ecies.PrivateKey // ecdhe-random
remoteRandomPub *ecies.PublicKey // ecdhe-random-pubk
remoteNode *enr.Record
}

// secrets represents the connection secrets
Expand All @@ -210,6 +214,7 @@ type secrets struct {
AES, MAC []byte
EgressMAC, IngressMAC hash.Hash
Token []byte
RemoteNode *enode.Node
}

// RLPx v4 handshake auth (defined in EIP-8).
Expand All @@ -223,6 +228,7 @@ type authMsgV4 struct {

// Ignore additional fields (forward-compatibility)
Rest []rlp.RawValue `rlp:"tail"`
// Record *enr.Record -- EIP-XXX RLPx ENR Extension
}

// RLPx v4 handshake response (defined in EIP-8).
Expand All @@ -233,6 +239,7 @@ type authRespV4 struct {

// Ignore additional fields (forward-compatibility)
Rest []rlp.RawValue `rlp:"tail"`
// Record *enr.Record -- EIP-XXX RLPx ENR Extension
}

// secrets is called after the handshake is completed.
Expand Down Expand Up @@ -265,6 +272,14 @@ func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) {
s.EgressMAC, s.IngressMAC = mac2, mac1
}

// Verify ENR.
if h.remoteNode != nil {
rn, err := enode.New(enode.ValidSchemes, h.remoteNode)
if err != nil {
return s, fmt.Errorf("invalid node record: %v", err)
}
s.RemoteNode = rn
}
return s, nil
}

Expand All @@ -278,9 +293,9 @@ func (h *encHandshake) staticSharedSecret(prv *ecdsa.PrivateKey) ([]byte, error)
// it should be called on the dialing side of the connection.
//
// prv is the local client's private key.
func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ecdsa.PublicKey) (s secrets, err error) {
func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, self *enode.Node, remote *ecdsa.PublicKey) (s secrets, err error) {
h := &encHandshake{initiator: true, remote: ecies.ImportECDSAPublic(remote)}
authMsg, err := h.makeAuthMsg(prv)
authMsg, err := h.makeAuthMsg(prv, self)
if err != nil {
return s, err
}
Expand All @@ -304,7 +319,7 @@ func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ec
}

// makeAuthMsg creates the initiator handshake message.
func (h *encHandshake) makeAuthMsg(prv *ecdsa.PrivateKey) (*authMsgV4, error) {
func (h *encHandshake) makeAuthMsg(prv *ecdsa.PrivateKey, self *enode.Node) (*authMsgV4, error) {
// Generate random initiator nonce.
h.initNonce = make([]byte, shaLen)
_, err := rand.Read(h.initNonce)
Expand Down Expand Up @@ -333,20 +348,28 @@ func (h *encHandshake) makeAuthMsg(prv *ecdsa.PrivateKey) (*authMsgV4, error) {
copy(msg.InitiatorPubkey[:], crypto.FromECDSAPub(&prv.PublicKey)[1:])
copy(msg.Nonce[:], h.initNonce)
msg.Version = 4

// Encode node record.
record, err := rlp.EncodeToBytes(self.Record())
if err != nil {
return nil, err
}
msg.Rest = []rlp.RawValue{record}
return msg, nil
}

func (h *encHandshake) handleAuthResp(msg *authRespV4) (err error) {
h.respNonce = msg.Nonce[:]
h.remoteRandomPub, err = importPublicKey(msg.RandomPubkey[:])
h.remoteNode = recordFromTail(msg.Rest)
return err
}

// receiverEncHandshake negotiates a session token on conn.
// it should be called on the listening side of the connection.
//
// prv is the local client's private key.
func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s secrets, err error) {
func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, self *enode.Node) (s secrets, err error) {
authMsg := new(authMsgV4)
authPacket, err := readHandshakeMsg(authMsg, encAuthMsgLen, prv, conn)
if err != nil {
Expand All @@ -357,7 +380,7 @@ func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s secrets,
return s, err
}

authRespMsg, err := h.makeAuthResp()
authRespMsg, err := h.makeAuthResp(self)
if err != nil {
return s, err
}
Expand Down Expand Up @@ -405,10 +428,11 @@ func (h *encHandshake) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) erro
return err
}
h.remoteRandomPub, _ = importPublicKey(remoteRandomPub)
h.remoteNode = recordFromTail(msg.Rest)
return nil
}

func (h *encHandshake) makeAuthResp() (msg *authRespV4, err error) {
func (h *encHandshake) makeAuthResp(self *enode.Node) (msg *authRespV4, err error) {
// Generate random nonce.
h.respNonce = make([]byte, shaLen)
if _, err = rand.Read(h.respNonce); err != nil {
Expand All @@ -419,6 +443,13 @@ func (h *encHandshake) makeAuthResp() (msg *authRespV4, err error) {
copy(msg.Nonce[:], h.respNonce)
copy(msg.RandomPubkey[:], exportPubkey(&h.randomPrivKey.PublicKey))
msg.Version = 4

// Encode node record.
record, err := rlp.EncodeToBytes(self.Record())
if err != nil {
return nil, err
}
msg.Rest = []rlp.RawValue{record}
return msg, nil
}

Expand Down Expand Up @@ -454,6 +485,17 @@ func (msg *authRespV4) decodePlain(input []byte) {
msg.Version = 4
}

func recordFromTail(tail []rlp.RawValue) *enr.Record {
if len(tail) == 0 {
return nil
}
var r enr.Record
if err := rlp.DecodeBytes(tail[0], &r); err != nil {
return nil
}
return &r
}

var padSpace = make([]byte, 300)

func sealEIP8(msg interface{}, h *encHandshake) ([]byte, error) {
Expand Down
63 changes: 54 additions & 9 deletions p2p/rlpx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/ecies"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/p2p/simulations/pipes"
"github.com/ethereum/go-ethereum/rlp"
"golang.org/x/crypto/sha3"
Expand Down Expand Up @@ -82,13 +84,36 @@ func testEncHandshake(token []byte) error {
type result struct {
side string
pubkey *ecdsa.PublicKey
enode *enode.Node
err error
}
var (
prv0, _ = crypto.GenerateKey()
prv1, _ = crypto.GenerateKey()
prv0, _ = crypto.GenerateKey()
prv1, _ = crypto.GenerateKey()
)

var rec0 enr.Record
err := enode.SignV4(&rec0, prv0)
if err != nil {
return err
}
enode0, err := enode.New(enode.V4ID{}, &rec0)
if err != nil {
return err
}
var rec1 enr.Record
err = enode.SignV4(&rec1, prv1)
if err != nil {
return err
}
enode1, err := enode.New(enode.V4ID{}, &rec1)
if err != nil {
return err
}

var (
fd0, fd1 = net.Pipe()
c0, c1 = newRLPX(fd0).(*rlpx), newRLPX(fd1).(*rlpx)
c0, c1 = newRLPX(enode0, fd0).(*rlpx), newRLPX(enode1, fd1).(*rlpx)
output = make(chan result)
)

Expand All @@ -97,7 +122,7 @@ func testEncHandshake(token []byte) error {
defer func() { output <- r }()
defer fd0.Close()

r.pubkey, r.err = c0.doEncHandshake(prv0, &prv1.PublicKey)
r.pubkey, r.enode, r.err = c0.doEncHandshake(prv0, &prv1.PublicKey)
if r.err != nil {
return
}
Expand All @@ -110,7 +135,7 @@ func testEncHandshake(token []byte) error {
defer func() { output <- r }()
defer fd1.Close()

r.pubkey, r.err = c1.doEncHandshake(prv1, nil)
r.pubkey, r.enode, r.err = c1.doEncHandshake(prv1, nil)
if r.err != nil {
return
}
Expand Down Expand Up @@ -157,6 +182,26 @@ func TestProtocolHandshake(t *testing.T) {
wg sync.WaitGroup
)

var rec0 enr.Record
err := enode.SignV4(&rec0, prv0)
if err != nil {
t.Fatal(err)
}
enode0, err := enode.New(enode.V4ID{}, &rec0)
if err != nil {
t.Fatal(err)
}
var rec1 enr.Record
err = enode.SignV4(&rec1, prv1)
if err != nil {
t.Fatal(err)
}

enode1, err := enode.New(enode.V4ID{}, &rec1)
if err != nil {
t.Fatal(err)
}

fd0, fd1, err := pipes.TCPPipe()
if err != nil {
t.Fatal(err)
Expand All @@ -166,8 +211,8 @@ func TestProtocolHandshake(t *testing.T) {
go func() {
defer wg.Done()
defer fd0.Close()
rlpx := newRLPX(fd0)
rpubkey, err := rlpx.doEncHandshake(prv0, &prv1.PublicKey)
rlpx := newRLPX(enode0, fd0)
rpubkey, _, err := rlpx.doEncHandshake(prv0, &prv1.PublicKey)
if err != nil {
t.Errorf("dial side enc handshake failed: %v", err)
return
Expand All @@ -192,8 +237,8 @@ func TestProtocolHandshake(t *testing.T) {
go func() {
defer wg.Done()
defer fd1.Close()
rlpx := newRLPX(fd1)
rpubkey, err := rlpx.doEncHandshake(prv1, nil)
rlpx := newRLPX(enode1, fd1)
rpubkey, _, err := rlpx.doEncHandshake(prv1, nil)
if err != nil {
t.Errorf("listen side enc handshake failed: %v", err)
return
Expand Down
Loading