Merge branch 'fjl-p2p-handshake-2' into poc-9
This commit is contained in:
commit
12ca70537f
@ -107,11 +107,9 @@ func (cfg *Config) nodeKey() (*ecdsa.PrivateKey, error) {
|
||||
type Ethereum struct {
|
||||
// Channel for shutting down the ethereum
|
||||
shutdownChan chan bool
|
||||
quit chan bool
|
||||
|
||||
// DB interface
|
||||
db ethutil.Database
|
||||
blacklist p2p.Blacklist
|
||||
|
||||
//*** SERVICES ***
|
||||
// State manager for processing new blocks and managing the over all states
|
||||
@ -169,10 +167,8 @@ func New(config *Config) (*Ethereum, error) {
|
||||
|
||||
eth := &Ethereum{
|
||||
shutdownChan: make(chan bool),
|
||||
quit: make(chan bool),
|
||||
db: db,
|
||||
keyManager: keyManager,
|
||||
blacklist: p2p.NewBlacklist(),
|
||||
eventMux: &event.TypeMux{},
|
||||
logger: ethlogger,
|
||||
}
|
||||
@ -205,7 +201,6 @@ func New(config *Config) (*Ethereum, error) {
|
||||
Name: config.Name,
|
||||
MaxPeers: config.MaxPeers,
|
||||
Protocols: protocols,
|
||||
Blacklist: eth.blacklist,
|
||||
NAT: config.NAT,
|
||||
NoDial: !config.Dial,
|
||||
BootstrapNodes: config.parseBootNodes(),
|
||||
@ -279,8 +274,6 @@ func (s *Ethereum) Stop() {
|
||||
// Close the database
|
||||
defer s.db.Close()
|
||||
|
||||
close(s.quit)
|
||||
|
||||
s.txSub.Unsubscribe() // quits txBroadcastLoop
|
||||
s.blockSub.Unsubscribe() // quits blockBroadcastLoop
|
||||
|
||||
|
@ -3,7 +3,6 @@ package eth
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
@ -188,33 +187,37 @@ func (self *ethProtocol) handle() error {
|
||||
|
||||
case BlockHashesMsg:
|
||||
msgStream := rlp.NewStream(msg.Payload)
|
||||
var err error
|
||||
if _, err := msgStream.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var i int
|
||||
|
||||
iter := func() (hash []byte, ok bool) {
|
||||
hash, err = msgStream.Bytes()
|
||||
if err == nil {
|
||||
i++
|
||||
ok = true
|
||||
} else {
|
||||
if err != io.EOF {
|
||||
hash, err := msgStream.Bytes()
|
||||
if err == rlp.EOL {
|
||||
return nil, false
|
||||
} else if err != nil {
|
||||
self.protoError(ErrDecode, "msg %v: after %v hashes : %v", msg, i, err)
|
||||
return nil, false
|
||||
}
|
||||
i++
|
||||
return hash, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
self.blockPool.AddBlockHashes(iter, self.id)
|
||||
|
||||
case GetBlocksMsg:
|
||||
msgStream := rlp.NewStream(msg.Payload)
|
||||
if _, err := msgStream.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var blocks []interface{}
|
||||
var i int
|
||||
for {
|
||||
i++
|
||||
var hash []byte
|
||||
if err := msgStream.Decode(&hash); err != nil {
|
||||
if err == io.EOF {
|
||||
if err == rlp.EOL {
|
||||
break
|
||||
} else {
|
||||
return self.protoError(ErrDecode, "msg %v: %v", msg, err)
|
||||
@ -232,10 +235,13 @@ func (self *ethProtocol) handle() error {
|
||||
|
||||
case BlocksMsg:
|
||||
msgStream := rlp.NewStream(msg.Payload)
|
||||
if _, err := msgStream.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
var block types.Block
|
||||
if err := msgStream.Decode(&block); err != nil {
|
||||
if err == io.EOF {
|
||||
if err == rlp.EOL {
|
||||
break
|
||||
} else {
|
||||
return self.protoError(ErrDecode, "msg %v: %v", msg, err)
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
@ -14,6 +15,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
@ -187,6 +189,19 @@ func PubkeyID(pub *ecdsa.PublicKey) NodeID {
|
||||
return id
|
||||
}
|
||||
|
||||
// Pubkey returns the public key represented by the node ID.
|
||||
// It returns an error if the ID is not a point on the curve.
|
||||
func (id NodeID) Pubkey() (*ecdsa.PublicKey, error) {
|
||||
p := &ecdsa.PublicKey{Curve: crypto.S256(), X: new(big.Int), Y: new(big.Int)}
|
||||
half := len(id) / 2
|
||||
p.X.SetBytes(id[:half])
|
||||
p.Y.SetBytes(id[half:])
|
||||
if !p.Curve.IsOnCurve(p.X, p.Y) {
|
||||
return nil, errors.New("not a point on the S256 curve")
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// recoverNodeID computes the public key used to sign the
|
||||
// given hash from the signature.
|
||||
func recoverNodeID(hash, sig []byte) (id NodeID, err error) {
|
||||
|
@ -133,6 +133,24 @@ func TestNodeID_recover(t *testing.T) {
|
||||
if pub != recpub {
|
||||
t.Errorf("recovered wrong pubkey:\ngot: %v\nwant: %v", recpub, pub)
|
||||
}
|
||||
|
||||
ecdsa, err := pub.Pubkey()
|
||||
if err != nil {
|
||||
t.Errorf("Pubkey error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(ecdsa, &prv.PublicKey) {
|
||||
t.Errorf("Pubkey mismatch:\n got: %#v\n want: %#v", ecdsa, &prv.PublicKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeID_pubkeyBad(t *testing.T) {
|
||||
ecdsa, err := NodeID{}.Pubkey()
|
||||
if err == nil {
|
||||
t.Error("expected error for zero ID")
|
||||
}
|
||||
if ecdsa != nil {
|
||||
t.Error("expected nil result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeID_distcmp(t *testing.T) {
|
||||
|
524
p2p/handshake.go
524
p2p/handshake.go
@ -2,15 +2,18 @@ package p2p
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"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/crypto/sha3"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
@ -25,25 +28,31 @@ const (
|
||||
authRespLen = pubLen + shaLen + 1
|
||||
|
||||
eciesBytes = 65 + 16 + 32
|
||||
iHSLen = authMsgLen + eciesBytes // size of the final ECIES payload sent as initiator's handshake
|
||||
rHSLen = authRespLen + eciesBytes // size of the final ECIES payload sent as receiver's handshake
|
||||
encAuthMsgLen = authMsgLen + eciesBytes // size of the final ECIES payload sent as initiator's handshake
|
||||
encAuthRespLen = authRespLen + eciesBytes // size of the final ECIES payload sent as receiver's handshake
|
||||
)
|
||||
|
||||
// conn represents a remote connection after encryption handshake
|
||||
// and protocol handshake have completed.
|
||||
//
|
||||
// The MsgReadWriter is usually layered as follows:
|
||||
//
|
||||
// netWrapper (I/O timeouts, thread-safe ReadMsg, WriteMsg)
|
||||
// rlpxFrameRW (message encoding, encryption, authentication)
|
||||
// bufio.ReadWriter (buffering)
|
||||
// net.Conn (network I/O)
|
||||
//
|
||||
type conn struct {
|
||||
*frameRW
|
||||
MsgReadWriter
|
||||
*protoHandshake
|
||||
}
|
||||
|
||||
func newConn(fd net.Conn, hs *protoHandshake) *conn {
|
||||
return &conn{newFrameRW(fd, msgWriteTimeout), hs}
|
||||
}
|
||||
|
||||
// encHandshake represents information about the remote end
|
||||
// of a connection that is negotiated during the encryption handshake.
|
||||
type encHandshake struct {
|
||||
ID discover.NodeID
|
||||
IngressMAC []byte
|
||||
EgressMAC []byte
|
||||
// secrets represents the connection secrets
|
||||
// which are negotiated during the encryption handshake.
|
||||
type secrets struct {
|
||||
RemoteID discover.NodeID
|
||||
AES, MAC []byte
|
||||
EgressMAC, IngressMAC hash.Hash
|
||||
Token []byte
|
||||
}
|
||||
|
||||
@ -68,15 +77,21 @@ func setupConn(fd net.Conn, prv *ecdsa.PrivateKey, our *protoHandshake, dial *di
|
||||
}
|
||||
|
||||
func setupInboundConn(fd net.Conn, prv *ecdsa.PrivateKey, our *protoHandshake) (*conn, error) {
|
||||
// var remotePubkey []byte
|
||||
// sessionToken, remotePubkey, err = inboundEncHandshake(fd, prv, nil)
|
||||
// copy(remoteID[:], remotePubkey)
|
||||
secrets, err := receiverEncHandshake(fd, prv, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encryption handshake failed: %v", err)
|
||||
}
|
||||
|
||||
rw := newFrameRW(fd, msgWriteTimeout)
|
||||
// Run the protocol handshake using authenticated messages.
|
||||
rw := newRlpxFrameRW(fd, secrets)
|
||||
rhs, err := readProtocolHandshake(rw, our)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rhs.ID != secrets.RemoteID {
|
||||
return nil, errors.New("node ID in protocol handshake does not match encryption handshake")
|
||||
}
|
||||
// TODO: validate that handshake node ID matches
|
||||
if err := writeProtocolHandshake(rw, our); err != nil {
|
||||
return nil, fmt.Errorf("protocol write error: %v", err)
|
||||
}
|
||||
@ -84,10 +99,13 @@ func setupInboundConn(fd net.Conn, prv *ecdsa.PrivateKey, our *protoHandshake) (
|
||||
}
|
||||
|
||||
func setupOutboundConn(fd net.Conn, prv *ecdsa.PrivateKey, our *protoHandshake, dial *discover.Node) (*conn, error) {
|
||||
// remoteID = dial.ID
|
||||
// sessionToken, err = outboundEncHandshake(fd, prv, remoteID[:], nil)
|
||||
secrets, err := initiatorEncHandshake(fd, prv, dial.ID, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encryption handshake failed: %v", err)
|
||||
}
|
||||
|
||||
rw := newFrameRW(fd, msgWriteTimeout)
|
||||
// Run the protocol handshake using authenticated messages.
|
||||
rw := newRlpxFrameRW(fd, secrets)
|
||||
if err := writeProtocolHandshake(rw, our); err != nil {
|
||||
return nil, fmt.Errorf("protocol write error: %v", err)
|
||||
}
|
||||
@ -101,273 +119,256 @@ func setupOutboundConn(fd net.Conn, prv *ecdsa.PrivateKey, our *protoHandshake,
|
||||
return &conn{rw, rhs}, nil
|
||||
}
|
||||
|
||||
// outboundEncHandshake negotiates a session token on conn.
|
||||
// encHandshake contains the state of the encryption handshake.
|
||||
type encHandshake struct {
|
||||
initiator bool
|
||||
remoteID discover.NodeID
|
||||
|
||||
remotePub *ecies.PublicKey // remote-pubk
|
||||
initNonce, respNonce []byte // nonce
|
||||
randomPrivKey *ecies.PrivateKey // ecdhe-random
|
||||
remoteRandomPub *ecies.PublicKey // ecdhe-random-pubk
|
||||
}
|
||||
|
||||
// secrets is called after the handshake is completed.
|
||||
// It extracts the connection secrets from the handshake values.
|
||||
func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) {
|
||||
ecdheSecret, err := h.randomPrivKey.GenerateShared(h.remoteRandomPub, sskLen, sskLen)
|
||||
if err != nil {
|
||||
return secrets{}, err
|
||||
}
|
||||
|
||||
// derive base secrets from ephemeral key agreement
|
||||
sharedSecret := crypto.Sha3(ecdheSecret, crypto.Sha3(h.respNonce, h.initNonce))
|
||||
aesSecret := crypto.Sha3(ecdheSecret, sharedSecret)
|
||||
s := secrets{
|
||||
RemoteID: h.remoteID,
|
||||
AES: aesSecret,
|
||||
MAC: crypto.Sha3(ecdheSecret, aesSecret),
|
||||
Token: crypto.Sha3(sharedSecret),
|
||||
}
|
||||
|
||||
// setup sha3 instances for the MACs
|
||||
mac1 := sha3.NewKeccak256()
|
||||
mac1.Write(xor(s.MAC, h.respNonce))
|
||||
mac1.Write(auth)
|
||||
mac2 := sha3.NewKeccak256()
|
||||
mac2.Write(xor(s.MAC, h.initNonce))
|
||||
mac2.Write(authResp)
|
||||
if h.initiator {
|
||||
s.EgressMAC, s.IngressMAC = mac1, mac2
|
||||
} else {
|
||||
s.EgressMAC, s.IngressMAC = mac2, mac1
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (h *encHandshake) ecdhShared(prv *ecdsa.PrivateKey) ([]byte, error) {
|
||||
return ecies.ImportECDSA(prv).GenerateShared(h.remotePub, sskLen, sskLen)
|
||||
}
|
||||
|
||||
// initiatorEncHandshake negotiates a session token on conn.
|
||||
// it should be called on the dialing side of the connection.
|
||||
//
|
||||
// privateKey is the local client's private key
|
||||
// remotePublicKey is the remote peer's node ID
|
||||
// sessionToken is the token from a previous session with this node.
|
||||
func outboundEncHandshake(conn io.ReadWriter, prvKey *ecdsa.PrivateKey, remotePublicKey []byte, sessionToken []byte) (
|
||||
newSessionToken []byte,
|
||||
err error,
|
||||
) {
|
||||
auth, initNonce, randomPrivKey, err := authMsg(prvKey, remotePublicKey, sessionToken)
|
||||
// prv is the local client's private key.
|
||||
// token is the token from a previous session with this node.
|
||||
func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remoteID discover.NodeID, token []byte) (s secrets, err error) {
|
||||
h, err := newInitiatorHandshake(remoteID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return s, err
|
||||
}
|
||||
auth, err := h.authMsg(prv, token)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
if _, err = conn.Write(auth); err != nil {
|
||||
return nil, err
|
||||
return s, err
|
||||
}
|
||||
|
||||
response := make([]byte, rHSLen)
|
||||
response := make([]byte, encAuthRespLen)
|
||||
if _, err = io.ReadFull(conn, response); err != nil {
|
||||
return nil, err
|
||||
return s, err
|
||||
}
|
||||
recNonce, remoteRandomPubKey, _, err := completeHandshake(response, prvKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if err := h.decodeAuthResp(response, prv); err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
return newSession(initNonce, recNonce, randomPrivKey, remoteRandomPubKey)
|
||||
return h.secrets(auth, response)
|
||||
}
|
||||
|
||||
// authMsg creates the initiator handshake.
|
||||
func authMsg(prvKey *ecdsa.PrivateKey, remotePubKeyS, sessionToken []byte) (
|
||||
auth, initNonce []byte,
|
||||
randomPrvKey *ecdsa.PrivateKey,
|
||||
err error,
|
||||
) {
|
||||
// session init, common to both parties
|
||||
remotePubKey, err := importPublicKey(remotePubKeyS)
|
||||
if err != nil {
|
||||
return
|
||||
func newInitiatorHandshake(remoteID discover.NodeID) (*encHandshake, error) {
|
||||
// generate random initiator nonce
|
||||
n := make([]byte, shaLen)
|
||||
if _, err := rand.Read(n); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// generate random keypair to use for signing
|
||||
randpriv, err := ecies.GenerateKey(rand.Reader, crypto.S256(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rpub, err := remoteID.Pubkey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad remoteID: %v", err)
|
||||
}
|
||||
h := &encHandshake{
|
||||
initiator: true,
|
||||
remoteID: remoteID,
|
||||
remotePub: ecies.ImportECDSAPublic(rpub),
|
||||
initNonce: n,
|
||||
randomPrivKey: randpriv,
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
var tokenFlag byte // = 0x00
|
||||
if sessionToken == nil {
|
||||
// authMsg creates an encrypted initiator handshake message.
|
||||
func (h *encHandshake) authMsg(prv *ecdsa.PrivateKey, token []byte) ([]byte, error) {
|
||||
var tokenFlag byte
|
||||
if token == nil {
|
||||
// no session token found means we need to generate shared secret.
|
||||
// ecies shared secret is used as initial session token for new peers
|
||||
// generate shared key from prv and remote pubkey
|
||||
if sessionToken, err = ecies.ImportECDSA(prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), sskLen, sskLen); err != nil {
|
||||
return
|
||||
var err error
|
||||
if token, err = h.ecdhShared(prv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// tokenFlag = 0x00 // redundant
|
||||
} else {
|
||||
// for known peers, we use stored token from the previous session
|
||||
tokenFlag = 0x01
|
||||
}
|
||||
|
||||
//E(remote-pubk, S(ecdhe-random, ecdh-shared-secret^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x0)
|
||||
// E(remote-pubk, S(ecdhe-random, token^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x1)
|
||||
// allocate msgLen long message,
|
||||
var msg []byte = make([]byte, authMsgLen)
|
||||
initNonce = msg[authMsgLen-shaLen-1 : authMsgLen-1]
|
||||
if _, err = rand.Read(initNonce); err != nil {
|
||||
return
|
||||
}
|
||||
// create known message
|
||||
// sign known message:
|
||||
// ecdh-shared-secret^nonce for new peers
|
||||
// token^nonce for old peers
|
||||
var sharedSecret = xor(sessionToken, initNonce)
|
||||
|
||||
// generate random keypair to use for signing
|
||||
if randomPrvKey, err = crypto.GenerateKey(); err != nil {
|
||||
return
|
||||
}
|
||||
// sign shared secret (message known to both parties): shared-secret
|
||||
var signature []byte
|
||||
// signature = sign(ecdhe-random, shared-secret)
|
||||
// uses secp256k1.Sign
|
||||
if signature, err = crypto.Sign(sharedSecret, randomPrvKey); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// message
|
||||
// signed-shared-secret || H(ecdhe-random-pubk) || pubk || nonce || 0x0
|
||||
copy(msg, signature) // copy signed-shared-secret
|
||||
// H(ecdhe-random-pubk)
|
||||
var randomPubKey64 []byte
|
||||
if randomPubKey64, err = exportPublicKey(&randomPrvKey.PublicKey); err != nil {
|
||||
return
|
||||
}
|
||||
var pubKey64 []byte
|
||||
if pubKey64, err = exportPublicKey(&prvKey.PublicKey); err != nil {
|
||||
return
|
||||
}
|
||||
copy(msg[sigLen:sigLen+shaLen], crypto.Sha3(randomPubKey64))
|
||||
// pubkey copied to the correct segment.
|
||||
copy(msg[sigLen+shaLen:sigLen+shaLen+pubLen], pubKey64)
|
||||
// nonce is already in the slice
|
||||
// stick tokenFlag byte to the end
|
||||
msg[authMsgLen-1] = tokenFlag
|
||||
|
||||
// encrypt using remote-pubk
|
||||
// auth = eciesEncrypt(remote-pubk, msg)
|
||||
if auth, err = crypto.Encrypt(remotePubKey, msg); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// completeHandshake is called when the initiator receives an
|
||||
// authentication response (aka receiver handshake). It completes the
|
||||
// handshake by reading off parameters the remote peer provides needed
|
||||
// to set up the secure session.
|
||||
func completeHandshake(auth []byte, prvKey *ecdsa.PrivateKey) (
|
||||
respNonce []byte,
|
||||
remoteRandomPubKey *ecdsa.PublicKey,
|
||||
tokenFlag bool,
|
||||
err error,
|
||||
) {
|
||||
var msg []byte
|
||||
// they prove that msg is meant for me,
|
||||
// I prove I possess private key if i can read it
|
||||
if msg, err = crypto.Decrypt(prvKey, auth); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
respNonce = msg[pubLen : pubLen+shaLen]
|
||||
var remoteRandomPubKeyS = msg[:pubLen]
|
||||
if remoteRandomPubKey, err = importPublicKey(remoteRandomPubKeyS); err != nil {
|
||||
return
|
||||
}
|
||||
if msg[authRespLen-1] == 0x01 {
|
||||
tokenFlag = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// inboundEncHandshake negotiates a session token on conn.
|
||||
// it should be called on the listening side of the connection.
|
||||
//
|
||||
// privateKey is the local client's private key
|
||||
// sessionToken is the token from a previous session with this node.
|
||||
func inboundEncHandshake(conn io.ReadWriter, prvKey *ecdsa.PrivateKey, sessionToken []byte) (
|
||||
token, remotePubKey []byte,
|
||||
err error,
|
||||
) {
|
||||
// we are listening connection. we are responders in the
|
||||
// handshake. Extract info from the authentication. The initiator
|
||||
// starts by sending us a handshake that we need to respond to. so
|
||||
// we read auth message first, then respond.
|
||||
auth := make([]byte, iHSLen)
|
||||
if _, err := io.ReadFull(conn, auth); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
response, recNonce, initNonce, remotePubKey, randomPrivKey, remoteRandomPubKey, err := authResp(auth, sessionToken, prvKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if _, err = conn.Write(response); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
token, err = newSession(initNonce, recNonce, randomPrivKey, remoteRandomPubKey)
|
||||
return token, remotePubKey, err
|
||||
}
|
||||
|
||||
// authResp is called by peer if it accepted (but not
|
||||
// initiated) the connection from the remote. It is passed the initiator
|
||||
// handshake received and the session token belonging to the
|
||||
// remote initiator.
|
||||
//
|
||||
// The first return value is the authentication response (aka receiver
|
||||
// handshake) that is to be sent to the remote initiator.
|
||||
func authResp(auth, sessionToken []byte, prvKey *ecdsa.PrivateKey) (
|
||||
authResp, respNonce, initNonce, remotePubKeyS []byte,
|
||||
randomPrivKey *ecdsa.PrivateKey,
|
||||
remoteRandomPubKey *ecdsa.PublicKey,
|
||||
err error,
|
||||
) {
|
||||
// they prove that msg is meant for me,
|
||||
// I prove I possess private key if i can read it
|
||||
msg, err := crypto.Decrypt(prvKey, auth)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
remotePubKeyS = msg[sigLen+shaLen : sigLen+shaLen+pubLen]
|
||||
remotePubKey, _ := importPublicKey(remotePubKeyS)
|
||||
|
||||
var tokenFlag byte
|
||||
if sessionToken == nil {
|
||||
// no session token found means we need to generate shared secret.
|
||||
// ecies shared secret is used as initial session token for new peers
|
||||
// generate shared key from prv and remote pubkey
|
||||
if sessionToken, err = ecies.ImportECDSA(prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), sskLen, sskLen); err != nil {
|
||||
return
|
||||
}
|
||||
// tokenFlag = 0x00 // redundant
|
||||
} else {
|
||||
// for known peers, we use stored token from the previous session
|
||||
tokenFlag = 0x01
|
||||
}
|
||||
|
||||
// the initiator nonce is read off the end of the message
|
||||
initNonce = msg[authMsgLen-shaLen-1 : authMsgLen-1]
|
||||
// I prove that i own prv key (to derive shared secret, and read
|
||||
// nonce off encrypted msg) and that I own shared secret they
|
||||
// prove they own the private key belonging to ecdhe-random-pubk
|
||||
// we can now reconstruct the signed message and recover the peers
|
||||
// pubkey
|
||||
var signedMsg = xor(sessionToken, initNonce)
|
||||
var remoteRandomPubKeyS []byte
|
||||
if remoteRandomPubKeyS, err = secp256k1.RecoverPubkey(signedMsg, msg[:sigLen]); err != nil {
|
||||
return
|
||||
}
|
||||
// convert to ECDSA standard
|
||||
if remoteRandomPubKey, err = importPublicKey(remoteRandomPubKeyS); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// now we find ourselves a long task too, fill it random
|
||||
var resp = make([]byte, authRespLen)
|
||||
// generate shaLen long nonce
|
||||
respNonce = resp[pubLen : pubLen+shaLen]
|
||||
if _, err = rand.Read(respNonce); err != nil {
|
||||
return
|
||||
}
|
||||
// generate random keypair for session
|
||||
if randomPrivKey, err = crypto.GenerateKey(); err != nil {
|
||||
return
|
||||
}
|
||||
// responder auth message
|
||||
// E(remote-pubk, ecdhe-random-pubk || nonce || 0x0)
|
||||
var randomPubKeyS []byte
|
||||
if randomPubKeyS, err = exportPublicKey(&randomPrivKey.PublicKey); err != nil {
|
||||
return
|
||||
}
|
||||
copy(resp[:pubLen], randomPubKeyS)
|
||||
// nonce is already in the slice
|
||||
resp[authRespLen-1] = tokenFlag
|
||||
|
||||
// encrypt using remote-pubk
|
||||
// auth = eciesEncrypt(remote-pubk, msg)
|
||||
// why not encrypt with ecdhe-random-remote
|
||||
if authResp, err = crypto.Encrypt(remotePubKey, resp); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// newSession is called after the handshake is completed. The
|
||||
// arguments are values negotiated in the handshake. The return value
|
||||
// is a new session Token to be remembered for the next time we
|
||||
// connect with this peer.
|
||||
func newSession(initNonce, respNonce []byte, privKey *ecdsa.PrivateKey, remoteRandomPubKey *ecdsa.PublicKey) ([]byte, error) {
|
||||
// 3) Now we can trust ecdhe-random-pubk to derive new keys
|
||||
//ecdhe-shared-secret = ecdh.agree(ecdhe-random, remote-ecdhe-random-pubk)
|
||||
pubKey := ecies.ImportECDSAPublic(remoteRandomPubKey)
|
||||
dhSharedSecret, err := ecies.ImportECDSA(privKey).GenerateShared(pubKey, sskLen, sskLen)
|
||||
signed := xor(token, h.initNonce)
|
||||
signature, err := crypto.Sign(signed, h.randomPrivKey.ExportECDSA())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sharedSecret := crypto.Sha3(dhSharedSecret, crypto.Sha3(respNonce, initNonce))
|
||||
sessionToken := crypto.Sha3(sharedSecret)
|
||||
return sessionToken, nil
|
||||
|
||||
// encode auth message
|
||||
// signature || sha3(ecdhe-random-pubk) || pubk || nonce || token-flag
|
||||
msg := make([]byte, authMsgLen)
|
||||
n := copy(msg, signature)
|
||||
n += copy(msg[n:], crypto.Sha3(exportPubkey(&h.randomPrivKey.PublicKey)))
|
||||
n += copy(msg[n:], crypto.FromECDSAPub(&prv.PublicKey)[1:])
|
||||
n += copy(msg[n:], h.initNonce)
|
||||
msg[n] = tokenFlag
|
||||
|
||||
// encrypt auth message using remote-pubk
|
||||
return ecies.Encrypt(rand.Reader, h.remotePub, msg, nil, nil)
|
||||
}
|
||||
|
||||
// decodeAuthResp decode an encrypted authentication response message.
|
||||
func (h *encHandshake) decodeAuthResp(auth []byte, prv *ecdsa.PrivateKey) error {
|
||||
msg, err := crypto.Decrypt(prv, auth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not decrypt auth response (%v)", err)
|
||||
}
|
||||
h.respNonce = msg[pubLen : pubLen+shaLen]
|
||||
h.remoteRandomPub, err = importPublicKey(msg[:pubLen])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// ignore token flag for now
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
// token is the token from a previous session with this node.
|
||||
func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, token []byte) (s secrets, err error) {
|
||||
// read remote auth sent by initiator.
|
||||
auth := make([]byte, encAuthMsgLen)
|
||||
if _, err := io.ReadFull(conn, auth); err != nil {
|
||||
return s, err
|
||||
}
|
||||
h, err := decodeAuthMsg(prv, token, auth)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
// send auth response
|
||||
resp, err := h.authResp(prv, token)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
if _, err = conn.Write(resp); err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
return h.secrets(auth, resp)
|
||||
}
|
||||
|
||||
func decodeAuthMsg(prv *ecdsa.PrivateKey, token []byte, auth []byte) (*encHandshake, error) {
|
||||
var err error
|
||||
h := new(encHandshake)
|
||||
// generate random keypair for session
|
||||
h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// generate random nonce
|
||||
h.respNonce = make([]byte, shaLen)
|
||||
if _, err = rand.Read(h.respNonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg, err := crypto.Decrypt(prv, auth)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not decrypt auth message (%v)", err)
|
||||
}
|
||||
|
||||
// decode message parameters
|
||||
// signature || sha3(ecdhe-random-pubk) || pubk || nonce || token-flag
|
||||
h.initNonce = msg[authMsgLen-shaLen-1 : authMsgLen-1]
|
||||
copy(h.remoteID[:], msg[sigLen+shaLen:sigLen+shaLen+pubLen])
|
||||
rpub, err := h.remoteID.Pubkey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad remoteID: %#v", err)
|
||||
}
|
||||
h.remotePub = ecies.ImportECDSAPublic(rpub)
|
||||
|
||||
// recover remote random pubkey from signed message.
|
||||
if token == nil {
|
||||
// TODO: it is an error if the initiator has a token and we don't. check that.
|
||||
|
||||
// no session token means we need to generate shared secret.
|
||||
// ecies shared secret is used as initial session token for new peers.
|
||||
// generate shared key from prv and remote pubkey.
|
||||
if token, err = h.ecdhShared(prv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
signedMsg := xor(token, h.initNonce)
|
||||
remoteRandomPub, err := secp256k1.RecoverPubkey(signedMsg, msg[:sigLen])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h.remoteRandomPub, _ = importPublicKey(remoteRandomPub)
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// authResp generates the encrypted authentication response message.
|
||||
func (h *encHandshake) authResp(prv *ecdsa.PrivateKey, token []byte) ([]byte, error) {
|
||||
// responder auth message
|
||||
// E(remote-pubk, ecdhe-random-pubk || nonce || 0x0)
|
||||
resp := make([]byte, authRespLen)
|
||||
n := copy(resp, exportPubkey(&h.randomPrivKey.PublicKey))
|
||||
n += copy(resp[n:], h.respNonce)
|
||||
if token == nil {
|
||||
resp[n] = 0
|
||||
} else {
|
||||
resp[n] = 1
|
||||
}
|
||||
// encrypt using remote-pubk
|
||||
return ecies.Encrypt(rand.Reader, h.remotePub, resp, nil, nil)
|
||||
}
|
||||
|
||||
// importPublicKey unmarshals 512 bit public keys.
|
||||
func importPublicKey(pubKey []byte) (pubKeyEC *ecdsa.PublicKey, err error) {
|
||||
func importPublicKey(pubKey []byte) (*ecies.PublicKey, error) {
|
||||
var pubKey65 []byte
|
||||
switch len(pubKey) {
|
||||
case 64:
|
||||
@ -378,14 +379,15 @@ func importPublicKey(pubKey []byte) (pubKeyEC *ecdsa.PublicKey, err error) {
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid public key length %v (expect 64/65)", len(pubKey))
|
||||
}
|
||||
return crypto.ToECDSAPub(pubKey65), nil
|
||||
// TODO: fewer pointless conversions
|
||||
return ecies.ImportECDSAPublic(crypto.ToECDSAPub(pubKey65)), nil
|
||||
}
|
||||
|
||||
func exportPublicKey(pubKeyEC *ecdsa.PublicKey) (pubKey []byte, err error) {
|
||||
if pubKeyEC == nil {
|
||||
return nil, fmt.Errorf("no ECDSA public key given")
|
||||
func exportPubkey(pub *ecies.PublicKey) []byte {
|
||||
if pub == nil {
|
||||
panic("nil pubkey")
|
||||
}
|
||||
return crypto.FromECDSAPub(pubKeyEC)[1:], nil
|
||||
return elliptic.Marshal(pub.Curve, pub.X, pub.Y)[1:]
|
||||
}
|
||||
|
||||
func xor(one, other []byte) (xor []byte) {
|
||||
|
@ -2,53 +2,18 @@ package p2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
)
|
||||
|
||||
func TestPublicKeyEncoding(t *testing.T) {
|
||||
prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader)
|
||||
pub0 := &prv0.PublicKey
|
||||
pub0s := crypto.FromECDSAPub(pub0)
|
||||
pub1, err := importPublicKey(pub0s)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
eciesPub1 := ecies.ImportECDSAPublic(pub1)
|
||||
if eciesPub1 == nil {
|
||||
t.Errorf("invalid ecdsa public key")
|
||||
}
|
||||
pub1s, err := exportPublicKey(pub1)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if len(pub1s) != 64 {
|
||||
t.Errorf("wrong length expect 64, got", len(pub1s))
|
||||
}
|
||||
pub2, err := importPublicKey(pub1s)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
pub2s, err := exportPublicKey(pub2)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if !bytes.Equal(pub1s, pub2s) {
|
||||
t.Errorf("exports dont match")
|
||||
}
|
||||
pub2sEC := crypto.FromECDSAPub(pub2)
|
||||
if !bytes.Equal(pub0s, pub2sEC) {
|
||||
t.Errorf("exports dont match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSharedSecret(t *testing.T) {
|
||||
prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader)
|
||||
pub0 := &prv0.PublicKey
|
||||
@ -69,103 +34,85 @@ func TestSharedSecret(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCryptoHandshake(t *testing.T) {
|
||||
testCryptoHandshake(newkey(), newkey(), nil, t)
|
||||
}
|
||||
|
||||
func TestCryptoHandshakeWithToken(t *testing.T) {
|
||||
sessionToken := make([]byte, shaLen)
|
||||
rand.Read(sessionToken)
|
||||
testCryptoHandshake(newkey(), newkey(), sessionToken, t)
|
||||
}
|
||||
|
||||
func testCryptoHandshake(prv0, prv1 *ecdsa.PrivateKey, sessionToken []byte, t *testing.T) {
|
||||
var err error
|
||||
// pub0 := &prv0.PublicKey
|
||||
pub1 := &prv1.PublicKey
|
||||
|
||||
// pub0s := crypto.FromECDSAPub(pub0)
|
||||
pub1s := crypto.FromECDSAPub(pub1)
|
||||
|
||||
// simulate handshake by feeding output to input
|
||||
// initiator sends handshake 'auth'
|
||||
auth, initNonce, randomPrivKey, err := authMsg(prv0, pub1s, sessionToken)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
// t.Logf("-> %v", hexkey(auth))
|
||||
|
||||
// receiver reads auth and responds with response
|
||||
response, remoteRecNonce, remoteInitNonce, _, remoteRandomPrivKey, remoteInitRandomPubKey, err := authResp(auth, sessionToken, prv1)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
// t.Logf("<- %v\n", hexkey(response))
|
||||
|
||||
// initiator reads receiver's response and the key exchange completes
|
||||
recNonce, remoteRandomPubKey, _, err := completeHandshake(response, prv0)
|
||||
if err != nil {
|
||||
t.Errorf("completeHandshake error: %v", err)
|
||||
}
|
||||
|
||||
// now both parties should have the same session parameters
|
||||
initSessionToken, err := newSession(initNonce, recNonce, randomPrivKey, remoteRandomPubKey)
|
||||
if err != nil {
|
||||
t.Errorf("newSession error: %v", err)
|
||||
}
|
||||
|
||||
recSessionToken, err := newSession(remoteInitNonce, remoteRecNonce, remoteRandomPrivKey, remoteInitRandomPubKey)
|
||||
if err != nil {
|
||||
t.Errorf("newSession error: %v", err)
|
||||
}
|
||||
|
||||
// fmt.Printf("\nauth (%v) %x\n\nresp (%v) %x\n\n", len(auth), auth, len(response), response)
|
||||
|
||||
// fmt.Printf("\nauth %x\ninitNonce %x\nresponse%x\nremoteRecNonce %x\nremoteInitNonce %x\nremoteRandomPubKey %x\nrecNonce %x\nremoteInitRandomPubKey %x\ninitSessionToken %x\n\n", auth, initNonce, response, remoteRecNonce, remoteInitNonce, remoteRandomPubKey, recNonce, remoteInitRandomPubKey, initSessionToken)
|
||||
|
||||
if !bytes.Equal(initNonce, remoteInitNonce) {
|
||||
t.Errorf("nonces do not match")
|
||||
}
|
||||
if !bytes.Equal(recNonce, remoteRecNonce) {
|
||||
t.Errorf("receiver nonces do not match")
|
||||
}
|
||||
if !bytes.Equal(initSessionToken, recSessionToken) {
|
||||
t.Errorf("session tokens do not match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncHandshake(t *testing.T) {
|
||||
defer testlog(t).detach()
|
||||
for i := 0; i < 20; i++ {
|
||||
start := time.Now()
|
||||
if err := testEncHandshake(nil); err != nil {
|
||||
t.Fatalf("i=%d %v", i, err)
|
||||
}
|
||||
t.Logf("(without token) %d %v\n", i+1, time.Since(start))
|
||||
}
|
||||
|
||||
prv0, _ := crypto.GenerateKey()
|
||||
prv1, _ := crypto.GenerateKey()
|
||||
pub0s, _ := exportPublicKey(&prv0.PublicKey)
|
||||
pub1s, _ := exportPublicKey(&prv1.PublicKey)
|
||||
rw0, rw1 := net.Pipe()
|
||||
tokens := make(chan []byte)
|
||||
for i := 0; i < 20; i++ {
|
||||
tok := make([]byte, shaLen)
|
||||
rand.Reader.Read(tok)
|
||||
start := time.Now()
|
||||
if err := testEncHandshake(tok); err != nil {
|
||||
t.Fatalf("i=%d %v", i, err)
|
||||
}
|
||||
t.Logf("(with token) %d %v\n", i+1, time.Since(start))
|
||||
}
|
||||
}
|
||||
|
||||
func testEncHandshake(token []byte) error {
|
||||
type result struct {
|
||||
side string
|
||||
s secrets
|
||||
err error
|
||||
}
|
||||
var (
|
||||
prv0, _ = crypto.GenerateKey()
|
||||
prv1, _ = crypto.GenerateKey()
|
||||
rw0, rw1 = net.Pipe()
|
||||
output = make(chan result)
|
||||
)
|
||||
|
||||
go func() {
|
||||
token, err := outboundEncHandshake(rw0, prv0, pub1s, nil)
|
||||
if err != nil {
|
||||
t.Errorf("outbound side error: %v", err)
|
||||
r := result{side: "initiator"}
|
||||
defer func() { output <- r }()
|
||||
|
||||
pub1s := discover.PubkeyID(&prv1.PublicKey)
|
||||
r.s, r.err = initiatorEncHandshake(rw0, prv0, pub1s, token)
|
||||
if r.err != nil {
|
||||
return
|
||||
}
|
||||
id1 := discover.PubkeyID(&prv1.PublicKey)
|
||||
if r.s.RemoteID != id1 {
|
||||
r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.s.RemoteID, id1)
|
||||
}
|
||||
tokens <- token
|
||||
}()
|
||||
go func() {
|
||||
token, remotePubkey, err := inboundEncHandshake(rw1, prv1, nil)
|
||||
if err != nil {
|
||||
t.Errorf("inbound side error: %v", err)
|
||||
r := result{side: "receiver"}
|
||||
defer func() { output <- r }()
|
||||
|
||||
r.s, r.err = receiverEncHandshake(rw1, prv1, token)
|
||||
if r.err != nil {
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(remotePubkey, pub0s) {
|
||||
t.Errorf("inbound side returned wrong remote pubkey\n got: %x\n want: %x", remotePubkey, pub0s)
|
||||
id0 := discover.PubkeyID(&prv0.PublicKey)
|
||||
if r.s.RemoteID != id0 {
|
||||
r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.s.RemoteID, id0)
|
||||
}
|
||||
tokens <- token
|
||||
}()
|
||||
|
||||
t1, t2 := <-tokens, <-tokens
|
||||
if !bytes.Equal(t1, t2) {
|
||||
t.Error("session token mismatch")
|
||||
// wait for results from both sides
|
||||
r1, r2 := <-output, <-output
|
||||
|
||||
if r1.err != nil {
|
||||
return fmt.Errorf("%s side error: %v", r1.side, r1.err)
|
||||
}
|
||||
if r2.err != nil {
|
||||
return fmt.Errorf("%s side error: %v", r2.side, r2.err)
|
||||
}
|
||||
|
||||
// don't compare remote node IDs
|
||||
r1.s.RemoteID, r2.s.RemoteID = discover.NodeID{}, discover.NodeID{}
|
||||
// flip MACs on one of them so they compare equal
|
||||
r1.s.EgressMAC, r1.s.IngressMAC = r1.s.IngressMAC, r1.s.EgressMAC
|
||||
if !reflect.DeepEqual(r1.s, r2.s) {
|
||||
return fmt.Errorf("secrets mismatch:\n t1: %#v\n t2: %#v", r1.s, r2.s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestSetupConn(t *testing.T) {
|
||||
|
187
p2p/message.go
187
p2p/message.go
@ -1,14 +1,11 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@ -18,28 +15,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// parameters for frameRW
|
||||
const (
|
||||
// maximum time allowed for reading a message header.
|
||||
// this is effectively the amount of time a connection can be idle.
|
||||
frameReadTimeout = 1 * time.Minute
|
||||
|
||||
// maximum time allowed for reading the payload data of a message.
|
||||
// this is shorter than (and distinct from) frameReadTimeout because
|
||||
// the connection is not considered idle while a message is transferred.
|
||||
// this also limits the payload size of messages to how much the connection
|
||||
// can transfer within the timeout.
|
||||
payloadReadTimeout = 5 * time.Second
|
||||
|
||||
// maximum amount of time allowed for writing a complete message.
|
||||
msgWriteTimeout = 5 * time.Second
|
||||
|
||||
// messages smaller than this many bytes will be read at
|
||||
// once before passing them to a protocol. this increases
|
||||
// concurrency in the processing.
|
||||
wholePayloadSize = 64 * 1024
|
||||
)
|
||||
|
||||
// Msg defines the structure of a p2p message.
|
||||
//
|
||||
// Note that a Msg can only be sent once since the Payload reader is
|
||||
@ -55,19 +30,8 @@ type Msg struct {
|
||||
|
||||
// NewMsg creates an RLP-encoded message with the given code.
|
||||
func NewMsg(code uint64, params ...interface{}) Msg {
|
||||
buf := new(bytes.Buffer)
|
||||
for _, p := range params {
|
||||
buf.Write(ethutil.Encode(p))
|
||||
}
|
||||
return Msg{Code: code, Size: uint32(buf.Len()), Payload: buf}
|
||||
}
|
||||
|
||||
func encodePayload(params ...interface{}) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
for _, p := range params {
|
||||
buf.Write(ethutil.Encode(p))
|
||||
}
|
||||
return buf.Bytes()
|
||||
p := bytes.NewReader(ethutil.Encode(params))
|
||||
return Msg{Code: code, Size: uint32(p.Len()), Payload: p}
|
||||
}
|
||||
|
||||
// Decode parse the RLP content of a message into
|
||||
@ -75,8 +39,7 @@ func encodePayload(params ...interface{}) []byte {
|
||||
//
|
||||
// For the decoding rules, please see package rlp.
|
||||
func (msg Msg) Decode(val interface{}) error {
|
||||
s := rlp.NewListStream(msg.Payload, uint64(msg.Size))
|
||||
if err := s.Decode(val); err != nil {
|
||||
if err := rlp.Decode(msg.Payload, val); err != nil {
|
||||
return newPeerError(errInvalidMsg, "(code %#x) (size %d) %v", msg.Code, msg.Size, err)
|
||||
}
|
||||
return nil
|
||||
@ -119,138 +82,28 @@ func EncodeMsg(w MsgWriter, code uint64, data ...interface{}) error {
|
||||
return w.WriteMsg(NewMsg(code, data...))
|
||||
}
|
||||
|
||||
// frameRW is a MsgReadWriter that reads and writes devp2p message frames.
|
||||
// As required by the interface, ReadMsg and WriteMsg can be called from
|
||||
// multiple goroutines.
|
||||
type frameRW struct {
|
||||
net.Conn // make Conn methods available. be careful.
|
||||
bufconn *bufio.ReadWriter
|
||||
// netWrapper wrapsa MsgReadWriter with locks around
|
||||
// ReadMsg/WriteMsg and applies read/write deadlines.
|
||||
type netWrapper struct {
|
||||
rmu, wmu sync.Mutex
|
||||
|
||||
// this channel is used to 'lend' bufconn to a caller of ReadMsg
|
||||
// until the message payload has been consumed. the channel
|
||||
// receives a value when EOF is reached on the payload, unblocking
|
||||
// a pending call to ReadMsg.
|
||||
rsync chan struct{}
|
||||
|
||||
// this mutex guards writes to bufconn.
|
||||
writeMu sync.Mutex
|
||||
rtimeout, wtimeout time.Duration
|
||||
conn net.Conn
|
||||
wrapped MsgReadWriter
|
||||
}
|
||||
|
||||
func newFrameRW(conn net.Conn, timeout time.Duration) *frameRW {
|
||||
rsync := make(chan struct{}, 1)
|
||||
rsync <- struct{}{}
|
||||
return &frameRW{
|
||||
Conn: conn,
|
||||
bufconn: bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)),
|
||||
rsync: rsync,
|
||||
}
|
||||
func (rw *netWrapper) ReadMsg() (Msg, error) {
|
||||
rw.rmu.Lock()
|
||||
defer rw.rmu.Unlock()
|
||||
rw.conn.SetReadDeadline(time.Now().Add(rw.rtimeout))
|
||||
return rw.wrapped.ReadMsg()
|
||||
}
|
||||
|
||||
var magicToken = []byte{34, 64, 8, 145}
|
||||
|
||||
func (rw *frameRW) WriteMsg(msg Msg) error {
|
||||
rw.writeMu.Lock()
|
||||
defer rw.writeMu.Unlock()
|
||||
rw.SetWriteDeadline(time.Now().Add(msgWriteTimeout))
|
||||
if err := writeMsg(rw.bufconn, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
return rw.bufconn.Flush()
|
||||
}
|
||||
|
||||
func writeMsg(w io.Writer, msg Msg) error {
|
||||
// TODO: handle case when Size + len(code) + len(listhdr) overflows uint32
|
||||
code := ethutil.Encode(uint32(msg.Code))
|
||||
listhdr := makeListHeader(msg.Size + uint32(len(code)))
|
||||
payloadLen := uint32(len(listhdr)) + uint32(len(code)) + msg.Size
|
||||
|
||||
start := make([]byte, 8)
|
||||
copy(start, magicToken)
|
||||
binary.BigEndian.PutUint32(start[4:], payloadLen)
|
||||
|
||||
for _, b := range [][]byte{start, listhdr, code} {
|
||||
if _, err := w.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := io.CopyN(w, msg.Payload, int64(msg.Size))
|
||||
return err
|
||||
}
|
||||
|
||||
func makeListHeader(length uint32) []byte {
|
||||
if length < 56 {
|
||||
return []byte{byte(length + 0xc0)}
|
||||
}
|
||||
enc := big.NewInt(int64(length)).Bytes()
|
||||
lenb := byte(len(enc)) + 0xf7
|
||||
return append([]byte{lenb}, enc...)
|
||||
}
|
||||
|
||||
func (rw *frameRW) ReadMsg() (msg Msg, err error) {
|
||||
<-rw.rsync // wait until bufconn is ours
|
||||
|
||||
rw.SetReadDeadline(time.Now().Add(frameReadTimeout))
|
||||
|
||||
// read magic and payload size
|
||||
start := make([]byte, 8)
|
||||
if _, err = io.ReadFull(rw.bufconn, start); err != nil {
|
||||
return msg, err
|
||||
}
|
||||
if !bytes.HasPrefix(start, magicToken) {
|
||||
return msg, fmt.Errorf("bad magic token %x", start[:4])
|
||||
}
|
||||
size := binary.BigEndian.Uint32(start[4:])
|
||||
|
||||
// decode start of RLP message to get the message code
|
||||
posr := &postrack{rw.bufconn, 0}
|
||||
s := rlp.NewStream(posr)
|
||||
if _, err := s.List(); err != nil {
|
||||
return msg, err
|
||||
}
|
||||
msg.Code, err = s.Uint()
|
||||
if err != nil {
|
||||
return msg, err
|
||||
}
|
||||
msg.Size = size - posr.p
|
||||
|
||||
rw.SetReadDeadline(time.Now().Add(payloadReadTimeout))
|
||||
|
||||
if msg.Size <= wholePayloadSize {
|
||||
// msg is small, read all of it and move on to the next message.
|
||||
pbuf := make([]byte, msg.Size)
|
||||
if _, err := io.ReadFull(rw.bufconn, pbuf); err != nil {
|
||||
return msg, err
|
||||
}
|
||||
rw.rsync <- struct{}{} // bufconn is available again
|
||||
msg.Payload = bytes.NewReader(pbuf)
|
||||
} else {
|
||||
// lend bufconn to the caller until it has
|
||||
// consumed the payload. eofSignal will send a value
|
||||
// on rw.rsync when EOF is reached.
|
||||
pr := &eofSignal{rw.bufconn, msg.Size, rw.rsync}
|
||||
msg.Payload = pr
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// postrack wraps an rlp.ByteReader with a position counter.
|
||||
type postrack struct {
|
||||
r rlp.ByteReader
|
||||
p uint32
|
||||
}
|
||||
|
||||
func (r *postrack) Read(buf []byte) (int, error) {
|
||||
n, err := r.r.Read(buf)
|
||||
r.p += uint32(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *postrack) ReadByte() (byte, error) {
|
||||
b, err := r.r.ReadByte()
|
||||
if err == nil {
|
||||
r.p++
|
||||
}
|
||||
return b, err
|
||||
func (rw *netWrapper) WriteMsg(msg Msg) error {
|
||||
rw.wmu.Lock()
|
||||
defer rw.wmu.Unlock()
|
||||
rw.conn.SetWriteDeadline(time.Now().Add(rw.wtimeout))
|
||||
return rw.wrapped.WriteMsg(msg)
|
||||
}
|
||||
|
||||
// eofSignal wraps a reader with eof signaling. the eof channel is
|
||||
|
@ -2,10 +2,12 @@ package p2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@ -15,62 +17,16 @@ func TestNewMsg(t *testing.T) {
|
||||
if msg.Code != 3 {
|
||||
t.Errorf("incorrect code %d, want %d", msg.Code)
|
||||
}
|
||||
if msg.Size != 5 {
|
||||
t.Errorf("incorrect size %d, want %d", msg.Size, 5)
|
||||
expect := unhex("c50183303030")
|
||||
if msg.Size != uint32(len(expect)) {
|
||||
t.Errorf("incorrect size %d, want %d", msg.Size, len(expect))
|
||||
}
|
||||
pl, _ := ioutil.ReadAll(msg.Payload)
|
||||
expect := []byte{0x01, 0x83, 0x30, 0x30, 0x30}
|
||||
if !bytes.Equal(pl, expect) {
|
||||
t.Errorf("incorrect payload content, got %x, want %x", pl, expect)
|
||||
}
|
||||
}
|
||||
|
||||
// func TestEncodeDecodeMsg(t *testing.T) {
|
||||
// msg := NewMsg(3, 1, "000")
|
||||
// buf := new(bytes.Buffer)
|
||||
// if err := writeMsg(buf, msg); err != nil {
|
||||
// t.Fatalf("encodeMsg error: %v", err)
|
||||
// }
|
||||
// // t.Logf("encoded: %x", buf.Bytes())
|
||||
|
||||
// decmsg, err := readMsg(buf)
|
||||
// if err != nil {
|
||||
// t.Fatalf("readMsg error: %v", err)
|
||||
// }
|
||||
// if decmsg.Code != 3 {
|
||||
// t.Errorf("incorrect code %d, want %d", decmsg.Code, 3)
|
||||
// }
|
||||
// if decmsg.Size != 5 {
|
||||
// t.Errorf("incorrect size %d, want %d", decmsg.Size, 5)
|
||||
// }
|
||||
|
||||
// var data struct {
|
||||
// I uint
|
||||
// S string
|
||||
// }
|
||||
// if err := decmsg.Decode(&data); err != nil {
|
||||
// t.Fatalf("Decode error: %v", err)
|
||||
// }
|
||||
// if data.I != 1 {
|
||||
// t.Errorf("incorrect data.I: got %v, expected %d", data.I, 1)
|
||||
// }
|
||||
// if data.S != "000" {
|
||||
// t.Errorf("incorrect data.S: got %q, expected %q", data.S, "000")
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestDecodeRealMsg(t *testing.T) {
|
||||
// data := ethutil.Hex2Bytes("2240089100000080f87e8002b5457468657265756d282b2b292f5065657220536572766572204f6e652f76302e372e382f52656c656173652f4c696e75782f672b2bc082765fb84086dd80b7aefd6a6d2e3b93f4f300a86bfb6ef7bdc97cb03f793db6bb")
|
||||
// msg, err := readMsg(bytes.NewReader(data))
|
||||
// if err != nil {
|
||||
// t.Fatalf("unexpected error: %v", err)
|
||||
// }
|
||||
|
||||
// if msg.Code != 0 {
|
||||
// t.Errorf("incorrect code %d, want %d", msg.Code, 0)
|
||||
// }
|
||||
// }
|
||||
|
||||
func ExampleMsgPipe() {
|
||||
rw1, rw2 := MsgPipe()
|
||||
go func() {
|
||||
@ -185,3 +141,11 @@ func TestEOFSignal(t *testing.T) {
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func unhex(str string) []byte {
|
||||
b, err := hex.DecodeString(strings.Replace(str, "\n", "", -1))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("invalid hex string: %q", str))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
28
p2p/peer.go
28
p2p/peer.go
@ -20,8 +20,8 @@ const (
|
||||
baseProtocolLength = uint64(16)
|
||||
baseProtocolMaxMsgSize = 10 * 1024 * 1024
|
||||
|
||||
disconnectGracePeriod = 2 * time.Second
|
||||
pingInterval = 15 * time.Second
|
||||
disconnectGracePeriod = 2 * time.Second
|
||||
)
|
||||
|
||||
const (
|
||||
@ -40,6 +40,7 @@ type Peer struct {
|
||||
// Use them to display messages related to the peer.
|
||||
*logger.Logger
|
||||
|
||||
conn net.Conn
|
||||
rw *conn
|
||||
running map[string]*protoRW
|
||||
|
||||
@ -52,8 +53,9 @@ type Peer struct {
|
||||
// NewPeer returns a peer for testing purposes.
|
||||
func NewPeer(id discover.NodeID, name string, caps []Cap) *Peer {
|
||||
pipe, _ := net.Pipe()
|
||||
conn := newConn(pipe, &protoHandshake{ID: id, Name: name, Caps: caps})
|
||||
peer := newPeer(conn, nil)
|
||||
msgpipe, _ := MsgPipe()
|
||||
conn := &conn{msgpipe, &protoHandshake{ID: id, Name: name, Caps: caps}}
|
||||
peer := newPeer(pipe, conn, nil)
|
||||
close(peer.closed) // ensures Disconnect doesn't block
|
||||
return peer
|
||||
}
|
||||
@ -76,12 +78,12 @@ func (p *Peer) Caps() []Cap {
|
||||
|
||||
// RemoteAddr returns the remote address of the network connection.
|
||||
func (p *Peer) RemoteAddr() net.Addr {
|
||||
return p.rw.RemoteAddr()
|
||||
return p.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
// LocalAddr returns the local address of the network connection.
|
||||
func (p *Peer) LocalAddr() net.Addr {
|
||||
return p.rw.LocalAddr()
|
||||
return p.conn.LocalAddr()
|
||||
}
|
||||
|
||||
// Disconnect terminates the peer connection with the given reason.
|
||||
@ -98,10 +100,11 @@ func (p *Peer) String() string {
|
||||
return fmt.Sprintf("Peer %.8x %v", p.rw.ID[:], p.RemoteAddr())
|
||||
}
|
||||
|
||||
func newPeer(conn *conn, protocols []Protocol) *Peer {
|
||||
logtag := fmt.Sprintf("Peer %.8x %v", conn.ID[:], conn.RemoteAddr())
|
||||
func newPeer(fd net.Conn, conn *conn, protocols []Protocol) *Peer {
|
||||
logtag := fmt.Sprintf("Peer %.8x %v", conn.ID[:], fd.RemoteAddr())
|
||||
p := &Peer{
|
||||
Logger: logger.NewLogger(logtag),
|
||||
conn: fd,
|
||||
rw: conn,
|
||||
running: matchProtocols(protocols, conn.Caps, conn),
|
||||
disc: make(chan DiscReason),
|
||||
@ -138,7 +141,7 @@ loop:
|
||||
// We rely on protocols to abort if there is a write error. It
|
||||
// might be more robust to handle them here as well.
|
||||
p.DebugDetailf("Read error: %v\n", err)
|
||||
p.rw.Close()
|
||||
p.conn.Close()
|
||||
return DiscNetworkError
|
||||
case err := <-p.protoErr:
|
||||
reason = discReasonForError(err)
|
||||
@ -161,18 +164,19 @@ func (p *Peer) politeDisconnect(reason DiscReason) {
|
||||
EncodeMsg(p.rw, discMsg, uint(reason))
|
||||
// Wait for the other side to close the connection.
|
||||
// Discard any data that they send until then.
|
||||
io.Copy(ioutil.Discard, p.rw)
|
||||
io.Copy(ioutil.Discard, p.conn)
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(disconnectGracePeriod):
|
||||
}
|
||||
p.rw.Close()
|
||||
p.conn.Close()
|
||||
}
|
||||
|
||||
func (p *Peer) readLoop() error {
|
||||
for {
|
||||
p.conn.SetDeadline(time.Now().Add(frameReadTimeout))
|
||||
msg, err := p.rw.ReadMsg()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -190,12 +194,12 @@ func (p *Peer) handle(msg Msg) error {
|
||||
msg.Discard()
|
||||
go EncodeMsg(p.rw, pongMsg)
|
||||
case msg.Code == discMsg:
|
||||
var reason DiscReason
|
||||
var reason [1]DiscReason
|
||||
// no need to discard or for error checking, we'll close the
|
||||
// connection after this.
|
||||
rlp.Decode(msg.Payload, &reason)
|
||||
p.Disconnect(DiscRequested)
|
||||
return discRequestedError(reason)
|
||||
return discRequestedError(reason[0])
|
||||
case msg.Code < baseProtocolLength:
|
||||
// ignore other base protocol messages
|
||||
return msg.Discard()
|
||||
|
@ -3,6 +3,7 @@ package p2p
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"reflect"
|
||||
@ -29,8 +30,8 @@ var discard = Protocol{
|
||||
},
|
||||
}
|
||||
|
||||
func testPeer(protos []Protocol) (*conn, *Peer, <-chan DiscReason) {
|
||||
fd1, fd2 := net.Pipe()
|
||||
func testPeer(protos []Protocol) (io.Closer, *conn, *Peer, <-chan DiscReason) {
|
||||
fd1, _ := net.Pipe()
|
||||
hs1 := &protoHandshake{ID: randomID(), Version: baseProtocolVersion}
|
||||
hs2 := &protoHandshake{ID: randomID(), Version: baseProtocolVersion}
|
||||
for _, p := range protos {
|
||||
@ -38,11 +39,12 @@ func testPeer(protos []Protocol) (*conn, *Peer, <-chan DiscReason) {
|
||||
hs2.Caps = append(hs2.Caps, p.cap())
|
||||
}
|
||||
|
||||
peer := newPeer(newConn(fd1, hs1), protos)
|
||||
p1, p2 := MsgPipe()
|
||||
peer := newPeer(fd1, &conn{p1, hs1}, protos)
|
||||
errc := make(chan DiscReason, 1)
|
||||
go func() { errc <- peer.run() }()
|
||||
|
||||
return newConn(fd2, hs2), peer, errc
|
||||
return p1, &conn{p2, hs2}, peer, errc
|
||||
}
|
||||
|
||||
func TestPeerProtoReadMsg(t *testing.T) {
|
||||
@ -67,8 +69,8 @@ func TestPeerProtoReadMsg(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
rw, _, errc := testPeer([]Protocol{proto})
|
||||
defer rw.Close()
|
||||
closer, rw, _, errc := testPeer([]Protocol{proto})
|
||||
defer closer.Close()
|
||||
|
||||
EncodeMsg(rw, baseProtocolLength+2, 1)
|
||||
EncodeMsg(rw, baseProtocolLength+3, 2)
|
||||
@ -83,41 +85,6 @@ func TestPeerProtoReadMsg(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeerProtoReadLargeMsg(t *testing.T) {
|
||||
defer testlog(t).detach()
|
||||
|
||||
msgsize := uint32(10 * 1024 * 1024)
|
||||
done := make(chan struct{})
|
||||
proto := Protocol{
|
||||
Name: "a",
|
||||
Length: 5,
|
||||
Run: func(peer *Peer, rw MsgReadWriter) error {
|
||||
msg, err := rw.ReadMsg()
|
||||
if err != nil {
|
||||
t.Errorf("read error: %v", err)
|
||||
}
|
||||
if msg.Size != msgsize+4 {
|
||||
t.Errorf("incorrect msg.Size, got %d, expected %d", msg.Size, msgsize)
|
||||
}
|
||||
msg.Discard()
|
||||
close(done)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
rw, _, errc := testPeer([]Protocol{proto})
|
||||
defer rw.Close()
|
||||
|
||||
EncodeMsg(rw, 18, make([]byte, msgsize))
|
||||
select {
|
||||
case <-done:
|
||||
case err := <-errc:
|
||||
t.Errorf("peer returned: %v", err)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Errorf("receive timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeerProtoEncodeMsg(t *testing.T) {
|
||||
defer testlog(t).detach()
|
||||
|
||||
@ -134,8 +101,8 @@ func TestPeerProtoEncodeMsg(t *testing.T) {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
rw, _, _ := testPeer([]Protocol{proto})
|
||||
defer rw.Close()
|
||||
closer, rw, _, _ := testPeer([]Protocol{proto})
|
||||
defer closer.Close()
|
||||
|
||||
if err := expectMsg(rw, 17, []string{"foo", "bar"}); err != nil {
|
||||
t.Error(err)
|
||||
@ -145,8 +112,8 @@ func TestPeerProtoEncodeMsg(t *testing.T) {
|
||||
func TestPeerWriteForBroadcast(t *testing.T) {
|
||||
defer testlog(t).detach()
|
||||
|
||||
rw, peer, peerErr := testPeer([]Protocol{discard})
|
||||
defer rw.Close()
|
||||
closer, rw, peer, peerErr := testPeer([]Protocol{discard})
|
||||
defer closer.Close()
|
||||
|
||||
// test write errors
|
||||
if err := peer.writeProtoMsg("b", NewMsg(3)); err == nil {
|
||||
@ -181,8 +148,8 @@ func TestPeerWriteForBroadcast(t *testing.T) {
|
||||
func TestPeerPing(t *testing.T) {
|
||||
defer testlog(t).detach()
|
||||
|
||||
rw, _, _ := testPeer(nil)
|
||||
defer rw.Close()
|
||||
closer, rw, _, _ := testPeer(nil)
|
||||
defer closer.Close()
|
||||
if err := EncodeMsg(rw, pingMsg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -194,15 +161,15 @@ func TestPeerPing(t *testing.T) {
|
||||
func TestPeerDisconnect(t *testing.T) {
|
||||
defer testlog(t).detach()
|
||||
|
||||
rw, _, disc := testPeer(nil)
|
||||
defer rw.Close()
|
||||
closer, rw, _, disc := testPeer(nil)
|
||||
defer closer.Close()
|
||||
if err := EncodeMsg(rw, discMsg, DiscQuitting); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := expectMsg(rw, discMsg, []interface{}{DiscRequested}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
rw.Close() // make test end faster
|
||||
closer.Close() // make test end faster
|
||||
if reason := <-disc; reason != DiscRequested {
|
||||
t.Errorf("run returned wrong reason: got %v, want %v", reason, DiscRequested)
|
||||
}
|
||||
@ -244,13 +211,9 @@ func expectMsg(r MsgReader, code uint64, content interface{}) error {
|
||||
if err != nil {
|
||||
panic("content encode error: " + err.Error())
|
||||
}
|
||||
// skip over list header in encoded value. this is temporary.
|
||||
contentEncR := bytes.NewReader(contentEnc)
|
||||
if k, _, err := rlp.NewStream(contentEncR).Kind(); k != rlp.List || err != nil {
|
||||
panic("content must encode as RLP list")
|
||||
if int(msg.Size) != len(contentEnc) {
|
||||
return fmt.Errorf("message size mismatch: got %d, want %d", msg.Size, len(contentEnc))
|
||||
}
|
||||
contentEnc = contentEnc[len(contentEnc)-contentEncR.Len():]
|
||||
|
||||
actualContent, err := ioutil.ReadAll(msg.Payload)
|
||||
if err != nil {
|
||||
return err
|
||||
|
174
p2p/rlpx.go
Normal file
174
p2p/rlpx.go
Normal file
@ -0,0 +1,174 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
var (
|
||||
// this is used in place of actual frame header data.
|
||||
// TODO: replace this when Msg contains the protocol type code.
|
||||
zeroHeader = []byte{0xC2, 0x80, 0x80}
|
||||
|
||||
// sixteen zero bytes
|
||||
zero16 = make([]byte, 16)
|
||||
|
||||
maxUint24 = ^uint32(0) >> 8
|
||||
)
|
||||
|
||||
// rlpxFrameRW implements a simplified version of RLPx framing.
|
||||
// chunked messages are not supported and all headers are equal to
|
||||
// zeroHeader.
|
||||
//
|
||||
// rlpxFrameRW is not safe for concurrent use from multiple goroutines.
|
||||
type rlpxFrameRW struct {
|
||||
conn io.ReadWriter
|
||||
enc cipher.Stream
|
||||
dec cipher.Stream
|
||||
|
||||
macCipher cipher.Block
|
||||
egressMAC hash.Hash
|
||||
ingressMAC hash.Hash
|
||||
}
|
||||
|
||||
func newRlpxFrameRW(conn io.ReadWriter, s secrets) *rlpxFrameRW {
|
||||
macc, err := aes.NewCipher(s.MAC)
|
||||
if err != nil {
|
||||
panic("invalid MAC secret: " + err.Error())
|
||||
}
|
||||
encc, err := aes.NewCipher(s.AES)
|
||||
if err != nil {
|
||||
panic("invalid AES secret: " + err.Error())
|
||||
}
|
||||
// we use an all-zeroes IV for AES because the key used
|
||||
// for encryption is ephemeral.
|
||||
iv := make([]byte, encc.BlockSize())
|
||||
return &rlpxFrameRW{
|
||||
conn: conn,
|
||||
enc: cipher.NewCTR(encc, iv),
|
||||
dec: cipher.NewCTR(encc, iv),
|
||||
macCipher: macc,
|
||||
egressMAC: s.EgressMAC,
|
||||
ingressMAC: s.IngressMAC,
|
||||
}
|
||||
}
|
||||
|
||||
func (rw *rlpxFrameRW) WriteMsg(msg Msg) error {
|
||||
ptype, _ := rlp.EncodeToBytes(msg.Code)
|
||||
|
||||
// write header
|
||||
headbuf := make([]byte, 32)
|
||||
fsize := uint32(len(ptype)) + msg.Size
|
||||
if fsize > maxUint24 {
|
||||
return errors.New("message size overflows uint24")
|
||||
}
|
||||
putInt24(fsize, headbuf) // TODO: check overflow
|
||||
copy(headbuf[3:], zeroHeader)
|
||||
rw.enc.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now encrypted
|
||||
|
||||
// write header MAC
|
||||
copy(headbuf[16:], updateMAC(rw.egressMAC, rw.macCipher, headbuf[:16]))
|
||||
if _, err := rw.conn.Write(headbuf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// write encrypted frame, updating the egress MAC hash with
|
||||
// the data written to conn.
|
||||
tee := cipher.StreamWriter{S: rw.enc, W: io.MultiWriter(rw.conn, rw.egressMAC)}
|
||||
if _, err := tee.Write(ptype); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(tee, msg.Payload); err != nil {
|
||||
return err
|
||||
}
|
||||
if padding := fsize % 16; padding > 0 {
|
||||
if _, err := tee.Write(zero16[:16-padding]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// write frame MAC. egress MAC hash is up to date because
|
||||
// frame content was written to it as well.
|
||||
fmacseed := rw.egressMAC.Sum(nil)
|
||||
mac := updateMAC(rw.egressMAC, rw.macCipher, fmacseed)
|
||||
_, err := rw.conn.Write(mac)
|
||||
return err
|
||||
}
|
||||
|
||||
func (rw *rlpxFrameRW) ReadMsg() (msg Msg, err error) {
|
||||
// read the header
|
||||
headbuf := make([]byte, 32)
|
||||
if _, err := io.ReadFull(rw.conn, headbuf); err != nil {
|
||||
return msg, err
|
||||
}
|
||||
// verify header mac
|
||||
shouldMAC := updateMAC(rw.ingressMAC, rw.macCipher, headbuf[:16])
|
||||
if !hmac.Equal(shouldMAC, headbuf[16:]) {
|
||||
return msg, errors.New("bad header MAC")
|
||||
}
|
||||
rw.dec.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now decrypted
|
||||
fsize := readInt24(headbuf)
|
||||
// ignore protocol type for now
|
||||
|
||||
// read the frame content
|
||||
var rsize = fsize // frame size rounded up to 16 byte boundary
|
||||
if padding := fsize % 16; padding > 0 {
|
||||
rsize += 16 - padding
|
||||
}
|
||||
framebuf := make([]byte, rsize)
|
||||
if _, err := io.ReadFull(rw.conn, framebuf); err != nil {
|
||||
return msg, err
|
||||
}
|
||||
|
||||
// read and validate frame MAC. we can re-use headbuf for that.
|
||||
rw.ingressMAC.Write(framebuf)
|
||||
fmacseed := rw.ingressMAC.Sum(nil)
|
||||
if _, err := io.ReadFull(rw.conn, headbuf[:16]); err != nil {
|
||||
return msg, err
|
||||
}
|
||||
shouldMAC = updateMAC(rw.ingressMAC, rw.macCipher, fmacseed)
|
||||
if !hmac.Equal(shouldMAC, headbuf[:16]) {
|
||||
return msg, errors.New("bad frame MAC")
|
||||
}
|
||||
|
||||
// decrypt frame content
|
||||
rw.dec.XORKeyStream(framebuf, framebuf)
|
||||
|
||||
// decode message code
|
||||
content := bytes.NewReader(framebuf[:fsize])
|
||||
if err := rlp.Decode(content, &msg.Code); err != nil {
|
||||
return msg, err
|
||||
}
|
||||
msg.Size = uint32(content.Len())
|
||||
msg.Payload = content
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// updateMAC reseeds the given hash with encrypted seed.
|
||||
// it returns the first 16 bytes of the hash sum after seeding.
|
||||
func updateMAC(mac hash.Hash, block cipher.Block, seed []byte) []byte {
|
||||
aesbuf := make([]byte, aes.BlockSize)
|
||||
block.Encrypt(aesbuf, mac.Sum(nil))
|
||||
for i := range aesbuf {
|
||||
aesbuf[i] ^= seed[i]
|
||||
}
|
||||
mac.Write(aesbuf)
|
||||
return mac.Sum(nil)[:16]
|
||||
}
|
||||
|
||||
func readInt24(b []byte) uint32 {
|
||||
return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16
|
||||
}
|
||||
|
||||
func putInt24(v uint32, b []byte) {
|
||||
b[0] = byte(v >> 16)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v)
|
||||
}
|
124
p2p/rlpx_test.go
Normal file
124
p2p/rlpx_test.go
Normal file
@ -0,0 +1,124 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
func TestRlpxFrameFake(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
hash := fakeHash([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1})
|
||||
rw := newRlpxFrameRW(buf, secrets{
|
||||
AES: crypto.Sha3(),
|
||||
MAC: crypto.Sha3(),
|
||||
IngressMAC: hash,
|
||||
EgressMAC: hash,
|
||||
})
|
||||
|
||||
golden := unhex(`
|
||||
00828ddae471818bb0bfa6b551d1cb42
|
||||
01010101010101010101010101010101
|
||||
ba628a4ba590cb43f7848f41c4382885
|
||||
01010101010101010101010101010101
|
||||
`)
|
||||
|
||||
// Check WriteMsg. This puts a message into the buffer.
|
||||
if err := EncodeMsg(rw, 8, 1, 2, 3, 4); err != nil {
|
||||
t.Fatalf("WriteMsg error: %v", err)
|
||||
}
|
||||
written := buf.Bytes()
|
||||
if !bytes.Equal(written, golden) {
|
||||
t.Fatalf("output mismatch:\n got: %x\n want: %x", written, golden)
|
||||
}
|
||||
|
||||
// Check ReadMsg. It reads the message encoded by WriteMsg, which
|
||||
// is equivalent to the golden message above.
|
||||
msg, err := rw.ReadMsg()
|
||||
if err != nil {
|
||||
t.Fatalf("ReadMsg error: %v", err)
|
||||
}
|
||||
if msg.Size != 5 {
|
||||
t.Errorf("msg size mismatch: got %d, want %d", msg.Size, 5)
|
||||
}
|
||||
if msg.Code != 8 {
|
||||
t.Errorf("msg code mismatch: got %d, want %d", msg.Code, 8)
|
||||
}
|
||||
payload, _ := ioutil.ReadAll(msg.Payload)
|
||||
wantPayload := unhex("C401020304")
|
||||
if !bytes.Equal(payload, wantPayload) {
|
||||
t.Errorf("msg payload mismatch:\ngot %x\nwant %x", payload, wantPayload)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeHash []byte
|
||||
|
||||
func (fakeHash) Write(p []byte) (int, error) { return len(p), nil }
|
||||
func (fakeHash) Reset() {}
|
||||
func (fakeHash) BlockSize() int { return 0 }
|
||||
|
||||
func (h fakeHash) Size() int { return len(h) }
|
||||
func (h fakeHash) Sum(b []byte) []byte { return append(b, h...) }
|
||||
|
||||
func TestRlpxFrameRW(t *testing.T) {
|
||||
var (
|
||||
aesSecret = make([]byte, 16)
|
||||
macSecret = make([]byte, 16)
|
||||
egressMACinit = make([]byte, 32)
|
||||
ingressMACinit = make([]byte, 32)
|
||||
)
|
||||
for _, s := range [][]byte{aesSecret, macSecret, egressMACinit, ingressMACinit} {
|
||||
rand.Read(s)
|
||||
}
|
||||
conn := new(bytes.Buffer)
|
||||
|
||||
s1 := secrets{
|
||||
AES: aesSecret,
|
||||
MAC: macSecret,
|
||||
EgressMAC: sha3.NewKeccak256(),
|
||||
IngressMAC: sha3.NewKeccak256(),
|
||||
}
|
||||
s1.EgressMAC.Write(egressMACinit)
|
||||
s1.IngressMAC.Write(ingressMACinit)
|
||||
rw1 := newRlpxFrameRW(conn, s1)
|
||||
|
||||
s2 := secrets{
|
||||
AES: aesSecret,
|
||||
MAC: macSecret,
|
||||
EgressMAC: sha3.NewKeccak256(),
|
||||
IngressMAC: sha3.NewKeccak256(),
|
||||
}
|
||||
s2.EgressMAC.Write(ingressMACinit)
|
||||
s2.IngressMAC.Write(egressMACinit)
|
||||
rw2 := newRlpxFrameRW(conn, s2)
|
||||
|
||||
// send some messages
|
||||
for i := 0; i < 10; i++ {
|
||||
// write message into conn buffer
|
||||
wmsg := []interface{}{"foo", "bar", strings.Repeat("test", i)}
|
||||
err := EncodeMsg(rw1, uint64(i), wmsg...)
|
||||
if err != nil {
|
||||
t.Fatalf("WriteMsg error (i=%d): %v", i, err)
|
||||
}
|
||||
|
||||
// read message that rw1 just wrote
|
||||
msg, err := rw2.ReadMsg()
|
||||
if err != nil {
|
||||
t.Fatalf("ReadMsg error (i=%d): %v", i, err)
|
||||
}
|
||||
if msg.Code != uint64(i) {
|
||||
t.Fatalf("msg code mismatch: got %d, want %d", msg.Code, i)
|
||||
}
|
||||
payload, _ := ioutil.ReadAll(msg.Payload)
|
||||
wantPayload, _ := rlp.EncodeToBytes(wmsg)
|
||||
if !bytes.Equal(payload, wantPayload) {
|
||||
t.Fatalf("msg payload mismatch:\ngot %x\nwant %x", payload, wantPayload)
|
||||
}
|
||||
}
|
||||
}
|
@ -10,15 +10,24 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethutil"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||
)
|
||||
|
||||
const (
|
||||
handshakeTimeout = 5 * time.Second
|
||||
defaultDialTimeout = 10 * time.Second
|
||||
refreshPeersInterval = 30 * time.Second
|
||||
|
||||
// total timeout for encryption handshake and protocol
|
||||
// handshake in both directions.
|
||||
handshakeTimeout = 5 * time.Second
|
||||
// maximum time allowed for reading a complete message.
|
||||
// this is effectively the amount of time a connection can be idle.
|
||||
frameReadTimeout = 1 * time.Minute
|
||||
// maximum amount of time allowed for writing a complete message.
|
||||
frameWriteTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
var srvlog = logger.NewLogger("P2P Server")
|
||||
@ -57,10 +66,6 @@ type Server struct {
|
||||
// each peer.
|
||||
Protocols []Protocol
|
||||
|
||||
// If Blacklist is set to a non-nil value, the given Blacklist
|
||||
// is used to verify peer connections.
|
||||
Blacklist Blacklist
|
||||
|
||||
// If ListenAddr is set to a non-nil address, the server
|
||||
// will listen for incoming connections.
|
||||
//
|
||||
@ -135,7 +140,7 @@ func (srv *Server) SuggestPeer(n *discover.Node) {
|
||||
func (srv *Server) Broadcast(protocol string, code uint64, data ...interface{}) {
|
||||
var payload []byte
|
||||
if data != nil {
|
||||
payload = encodePayload(data...)
|
||||
payload = ethutil.Encode(data)
|
||||
}
|
||||
srv.lock.RLock()
|
||||
defer srv.lock.RUnlock()
|
||||
@ -174,9 +179,6 @@ func (srv *Server) Start() (err error) {
|
||||
if srv.setupFunc == nil {
|
||||
srv.setupFunc = setupConn
|
||||
}
|
||||
if srv.Blacklist == nil {
|
||||
srv.Blacklist = NewBlacklist()
|
||||
}
|
||||
|
||||
// node table
|
||||
ntab, err := discover.ListenUDP(srv.PrivateKey, srv.ListenAddr, srv.NAT)
|
||||
@ -365,7 +367,12 @@ func (srv *Server) startPeer(fd net.Conn, dest *discover.Node) {
|
||||
srvlog.Debugf("Handshake with %v failed: %v", fd.RemoteAddr(), err)
|
||||
return
|
||||
}
|
||||
p := newPeer(conn, srv.Protocols)
|
||||
|
||||
conn.MsgReadWriter = &netWrapper{
|
||||
wrapped: conn.MsgReadWriter,
|
||||
conn: fd, rtimeout: frameReadTimeout, wtimeout: frameWriteTimeout,
|
||||
}
|
||||
p := newPeer(fd, conn, srv.Protocols)
|
||||
if ok, reason := srv.addPeer(conn.ID, p); !ok {
|
||||
srvlog.DebugDetailf("Not adding %v (%v)\n", p, reason)
|
||||
p.politeDisconnect(reason)
|
||||
@ -375,7 +382,7 @@ func (srv *Server) startPeer(fd net.Conn, dest *discover.Node) {
|
||||
srvlog.Debugf("Added %v\n", p)
|
||||
srvjslog.LogJson(&logger.P2PConnected{
|
||||
RemoteId: fmt.Sprintf("%x", conn.ID[:]),
|
||||
RemoteAddress: conn.RemoteAddr().String(),
|
||||
RemoteAddress: fd.RemoteAddr().String(),
|
||||
RemoteVersionString: conn.Name,
|
||||
NumConnections: srv.PeerCount(),
|
||||
})
|
||||
@ -403,8 +410,6 @@ func (srv *Server) addPeer(id discover.NodeID, p *Peer) (bool, DiscReason) {
|
||||
return false, DiscTooManyPeers
|
||||
case srv.peers[id] != nil:
|
||||
return false, DiscAlreadyConnected
|
||||
case srv.Blacklist.Exists(id[:]):
|
||||
return false, DiscUselessPeer
|
||||
case id == srv.ntab.Self():
|
||||
return false, DiscSelf
|
||||
}
|
||||
@ -418,53 +423,3 @@ func (srv *Server) removePeer(p *Peer) {
|
||||
srv.lock.Unlock()
|
||||
srv.peerWG.Done()
|
||||
}
|
||||
|
||||
type Blacklist interface {
|
||||
Get([]byte) (bool, error)
|
||||
Put([]byte) error
|
||||
Delete([]byte) error
|
||||
Exists(pubkey []byte) (ok bool)
|
||||
}
|
||||
|
||||
type BlacklistMap struct {
|
||||
blacklist map[string]bool
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewBlacklist() *BlacklistMap {
|
||||
return &BlacklistMap{
|
||||
blacklist: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BlacklistMap) Get(pubkey []byte) (bool, error) {
|
||||
self.lock.RLock()
|
||||
defer self.lock.RUnlock()
|
||||
v, ok := self.blacklist[string(pubkey)]
|
||||
var err error
|
||||
if !ok {
|
||||
err = fmt.Errorf("not found")
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (self *BlacklistMap) Exists(pubkey []byte) (ok bool) {
|
||||
self.lock.RLock()
|
||||
defer self.lock.RUnlock()
|
||||
_, ok = self.blacklist[string(pubkey)]
|
||||
return
|
||||
}
|
||||
|
||||
func (self *BlacklistMap) Put(pubkey []byte) error {
|
||||
self.lock.Lock()
|
||||
defer self.lock.Unlock()
|
||||
self.blacklist[string(pubkey)] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BlacklistMap) Delete(pubkey []byte) error {
|
||||
self.lock.Lock()
|
||||
defer self.lock.Unlock()
|
||||
delete(self.blacklist, string(pubkey))
|
||||
return nil
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
)
|
||||
|
||||
@ -23,8 +24,14 @@ func startTestServer(t *testing.T, pf newPeerHook) *Server {
|
||||
newPeerHook: pf,
|
||||
setupFunc: func(fd net.Conn, prv *ecdsa.PrivateKey, our *protoHandshake, dial *discover.Node) (*conn, error) {
|
||||
id := randomID()
|
||||
rw := newRlpxFrameRW(fd, secrets{
|
||||
MAC: zero16,
|
||||
AES: zero16,
|
||||
IngressMAC: sha3.NewKeccak256(),
|
||||
EgressMAC: sha3.NewKeccak256(),
|
||||
})
|
||||
return &conn{
|
||||
frameRW: newFrameRW(fd, msgWriteTimeout),
|
||||
MsgReadWriter: rw,
|
||||
protoHandshake: &protoHandshake{ID: id, Version: baseProtocolVersion},
|
||||
}, nil
|
||||
},
|
||||
@ -143,9 +150,7 @@ func TestServerBroadcast(t *testing.T) {
|
||||
|
||||
// broadcast one message
|
||||
srv.Broadcast("discard", 0, "foo")
|
||||
goldbuf := new(bytes.Buffer)
|
||||
writeMsg(goldbuf, NewMsg(16, "foo"))
|
||||
golden := goldbuf.Bytes()
|
||||
golden := unhex("66e94d166f0a2c3b884cfa59ca34")
|
||||
|
||||
// check that the message has been written everywhere
|
||||
for i, conn := range conns {
|
||||
|
@ -2,10 +2,10 @@ package whisper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"gopkg.in/fatih/set.v0"
|
||||
)
|
||||
|
||||
@ -77,8 +77,7 @@ func (self *peer) broadcast(envelopes []*Envelope) error {
|
||||
}
|
||||
|
||||
if i > 0 {
|
||||
msg := p2p.NewMsg(envelopesMsg, envs[:i]...)
|
||||
if err := self.ws.WriteMsg(msg); err != nil {
|
||||
if err := p2p.EncodeMsg(self.ws, envelopesMsg, envs[:i]...); err != nil {
|
||||
return err
|
||||
}
|
||||
self.peer.DebugDetailln("broadcasted", i, "message(s)")
|
||||
@ -93,34 +92,28 @@ func (self *peer) addKnown(envelope *Envelope) {
|
||||
|
||||
func (self *peer) handleStatus() error {
|
||||
ws := self.ws
|
||||
|
||||
if err := ws.WriteMsg(self.statusMsg()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg, err := ws.ReadMsg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if msg.Code != statusMsg {
|
||||
return fmt.Errorf("peer send %x before status msg", msg.Code)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(msg.Payload)
|
||||
s := rlp.NewStream(msg.Payload)
|
||||
if _, err := s.List(); err != nil {
|
||||
return fmt.Errorf("bad status message: %v", err)
|
||||
}
|
||||
pv, err := s.Uint()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("bad status message: %v", err)
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return fmt.Errorf("malformed status. data len = 0")
|
||||
}
|
||||
|
||||
if pv := data[0]; pv != protocolVersion {
|
||||
if pv != protocolVersion {
|
||||
return fmt.Errorf("protocol version mismatch %d != %d", pv, protocolVersion)
|
||||
}
|
||||
|
||||
return nil
|
||||
return msg.Discard() // ignore anything after protocol version
|
||||
}
|
||||
|
||||
func (self *peer) statusMsg() p2p.Msg {
|
||||
|
Loading…
Reference in New Issue
Block a user