mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 06:36:43 +00:00
Merge f296300509 into 82a024d425
This commit is contained in:
commit
8f4bd86999
26 changed files with 3100 additions and 1556 deletions
|
|
@ -35,9 +35,11 @@ import (
|
|||
"crypto/elliptic"
|
||||
"crypto/hmac"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
|
|
@ -143,50 +145,27 @@ var (
|
|||
ErrInvalidMessage = fmt.Errorf("ecies: invalid message")
|
||||
)
|
||||
|
||||
var (
|
||||
big2To32 = new(big.Int).Exp(big.NewInt(2), big.NewInt(32), nil)
|
||||
big2To32M1 = new(big.Int).Sub(big2To32, big.NewInt(1))
|
||||
)
|
||||
|
||||
func incCounter(ctr []byte) {
|
||||
if ctr[3]++; ctr[3] != 0 {
|
||||
return
|
||||
} else if ctr[2]++; ctr[2] != 0 {
|
||||
return
|
||||
} else if ctr[1]++; ctr[1] != 0 {
|
||||
return
|
||||
} else if ctr[0]++; ctr[0] != 0 {
|
||||
return
|
||||
// ConcatKDF implements the Concatenation Key Derivation Function
|
||||
// specified in NIST SP 800-56 (section 5.8.1).
|
||||
// It returns kdlen bytes of key material derived from z and s1 using hash.
|
||||
// kdlen is expected to be reasonably small.
|
||||
func ConcatKDF(hash hash.Hash, z, s1 []byte, kdlen int) ([]byte, error) {
|
||||
hashlen := hash.Size()
|
||||
reps := (kdlen + hashlen - 1) / hashlen
|
||||
if uint64(reps) > math.MaxUint32 {
|
||||
return nil, ErrKeyDataTooLong // prevent counter overflow
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NIST SP 800-56 Concatenation Key Derivation Function (see section 5.8.1).
|
||||
func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) (k []byte, err error) {
|
||||
if s1 == nil {
|
||||
s1 = make([]byte, 0)
|
||||
}
|
||||
|
||||
reps := ((kdLen + 7) * 8) / (hash.BlockSize() * 8)
|
||||
if big.NewInt(int64(reps)).Cmp(big2To32M1) > 0 {
|
||||
fmt.Println(big2To32M1)
|
||||
return nil, ErrKeyDataTooLong
|
||||
}
|
||||
|
||||
counter := []byte{0, 0, 0, 1}
|
||||
k = make([]byte, 0)
|
||||
|
||||
for i := 0; i <= reps; i++ {
|
||||
counter := []byte{0, 0, 0, 0}
|
||||
k := make([]byte, 0, reps*hashlen)
|
||||
for i := uint32(1); i <= uint32(reps); i++ {
|
||||
binary.BigEndian.PutUint32(counter, i)
|
||||
hash.Write(counter)
|
||||
hash.Write(z)
|
||||
hash.Write(s1)
|
||||
k = append(k, hash.Sum(nil)...)
|
||||
k = hash.Sum(k)
|
||||
hash.Reset()
|
||||
incCounter(counter)
|
||||
}
|
||||
|
||||
k = k[:kdLen]
|
||||
return
|
||||
return k[:kdlen], nil
|
||||
}
|
||||
|
||||
// messageTag computes the MAC of a message (called the tag) as per
|
||||
|
|
@ -264,7 +243,7 @@ func Encrypt(rand io.Reader, pub *PublicKey, m, s1, s2 []byte) (ct []byte, err e
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
K, err := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen)
|
||||
K, err := ConcatKDF(hash, z, s1, params.KeyLen+params.KeyLen)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -343,7 +322,7 @@ func (prv *PrivateKey) Decrypt(rand io.Reader, c, s1, s2 []byte) (m []byte, err
|
|||
return
|
||||
}
|
||||
|
||||
K, err := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen)
|
||||
K, err := ConcatKDF(hash, z, s1, params.KeyLen+params.KeyLen)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,20 +53,63 @@ func init() {
|
|||
dumpEnc = *flDump
|
||||
}
|
||||
|
||||
// Ensure the KDF generates appropriately sized keys.
|
||||
func TestKDF(t *testing.T) {
|
||||
msg := []byte("Hello, world")
|
||||
h := sha256.New()
|
||||
|
||||
k, err := concatKDF(h, msg, nil, 64)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
t.FailNow()
|
||||
type kdftest struct {
|
||||
key, s1 string
|
||||
kdlen int
|
||||
output string
|
||||
}
|
||||
|
||||
var kdftests = []kdftest{
|
||||
{
|
||||
key: "38f9a331c022f51d66658f301837108c9710d5ee0697bfac97bb6b0ea8d4f273",
|
||||
s1: "",
|
||||
kdlen: 0,
|
||||
output: "",
|
||||
},
|
||||
{
|
||||
key: "38f9a331c022f51d66658f301837108c9710d5ee0697bfac97bb6b0ea8d4f273",
|
||||
s1: "",
|
||||
kdlen: 32,
|
||||
output: "bbfb3912ffc0d1789be7c3c2773fb6abd8df69578df2ca16beee3d0f7a9692d1",
|
||||
},
|
||||
{
|
||||
key: "38f9a331c022f51d66658f301837108c9710d5ee0697bfac97bb6b0ea8d4f273",
|
||||
s1: "",
|
||||
kdlen: 64,
|
||||
output: "bbfb3912ffc0d1789be7c3c2773fb6abd8df69578df2ca16beee3d0f7a9692d1eae543288220e41452942fe268297fff38423b65b19cf7c6263aa611a4190741",
|
||||
},
|
||||
{
|
||||
key: "38f9a331c022f51d66658f301837108c9710d5ee0697bfac97bb6b0ea8d4f273",
|
||||
s1: "",
|
||||
kdlen: 242,
|
||||
output: "bbfb3912ffc0d1789be7c3c2773fb6abd8df69578df2ca16beee3d0f7a9692d1eae543288220e41452942fe268297fff38423b65b19cf7c6263aa611a41907410a49acd5adbfbe93e902349105d7bd7ef5b106d9357b20bb4a7977d548bc2bf1a0b275a9f1de19ff8f963ec58171aa31da964edb0131436d88a7714e2429d85693409f718c8fea7ecaa076dce68f282aba4010a42feedcb1affc350497fa1078ad89e23e8a04ba2ef179c3c625b054817d792076b5882e80925d45a2874285d45a7767fa6a3853bd8417930923a554743f6eb4691e4790a97c467337a307c64a8bed5b5c1829d0a690a554d3e5334ee4980a",
|
||||
},
|
||||
{
|
||||
key: "38f9a331c022f51d66658f301837108c9710d5ee0697bfac97bb6b0ea8d4f273",
|
||||
s1: "343434",
|
||||
kdlen: 242,
|
||||
output: "71e6fe05a6a57e2312a996eb3b91fc613fb57196ea09880b7b0ed880afa399f942ad56c1a484f4ccd329f9c21911ae09b497e991d8f47060114c8f137ab65df10c3f1d2478a7af913aa406817f34ef55d7852f3390f8201056451b0c29fb73a5c9e5a5093e6d93fefa15846b81bc02e1a7033948d67fc70c1dbbbf11d97f34b15d9967709c666eff05d895cc2f415064b382a98e68c178f9e7e2d9b6169ef4cdd5bb855b21d0ff71339e7e5b3afcb2393a8c6f4c7a4e618094222be87cbfdd2417ebdced5870bfbe8761d2e646c5a62dd31852dba399507adb84334a81b4ce54714f1828a0cdf3067908815d516368c2dd58",
|
||||
},
|
||||
}
|
||||
|
||||
func TestKDF(t *testing.T) {
|
||||
for _, test := range kdftests {
|
||||
z, _ := hex.DecodeString(test.key)
|
||||
s1, _ := hex.DecodeString(test.s1)
|
||||
h := sha256.New()
|
||||
k, err := ConcatKDF(h, z, s1, test.kdlen)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if len(k) != test.kdlen {
|
||||
t.Errorf("output length mismatch: got %d, want %d", len(k), test.kdlen)
|
||||
}
|
||||
if hexk := hex.EncodeToString(k); hexk != test.output {
|
||||
t.Errorf("output mismatch:\ngot %s\nwant %s", hexk, test.output)
|
||||
}
|
||||
if t.Failed() {
|
||||
t.Fatalf("failed test: %#v", test)
|
||||
}
|
||||
if len(k) != 64 {
|
||||
fmt.Printf("KDF: generated key is the wrong size (%d instead of 64\n",
|
||||
len(k))
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
245
p2p/devp2p.go
Normal file
245
p2p/devp2p.go
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/rlpx"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
const (
|
||||
// devp2p message codes
|
||||
handshakeMsg = 0x00
|
||||
discMsg = 0x01
|
||||
pingMsg = 0x02
|
||||
pongMsg = 0x03
|
||||
getPeersMsg = 0x04
|
||||
peersMsg = 0x05
|
||||
)
|
||||
|
||||
const (
|
||||
baseProtocolVersion = 4
|
||||
baseProtocolLength = uint64(16)
|
||||
baseProtocolMaxMsgSize = 2 * 1024
|
||||
)
|
||||
|
||||
var errMsgTooBig = errors.New("encoded message size exceeds uint32")
|
||||
|
||||
// DiscReason indicates why a connection is being disconnected.
|
||||
type DiscReason uint
|
||||
|
||||
const (
|
||||
DiscRequested DiscReason = iota
|
||||
DiscNetworkError
|
||||
DiscProtocolError
|
||||
DiscUselessPeer
|
||||
DiscTooManyPeers
|
||||
DiscAlreadyConnected
|
||||
DiscIncompatibleVersion
|
||||
DiscInvalidIdentity
|
||||
DiscQuitting
|
||||
DiscUnexpectedIdentity
|
||||
DiscSelf
|
||||
DiscReadTimeout
|
||||
DiscSubprotocolError = 0x10
|
||||
)
|
||||
|
||||
var discReasonToString = [...]string{
|
||||
DiscRequested: "Disconnect requested",
|
||||
DiscNetworkError: "Network error",
|
||||
DiscProtocolError: "Breach of protocol",
|
||||
DiscUselessPeer: "Useless peer",
|
||||
DiscTooManyPeers: "Too many peers",
|
||||
DiscAlreadyConnected: "Already connected",
|
||||
DiscIncompatibleVersion: "Incompatible P2P protocol version",
|
||||
DiscInvalidIdentity: "Invalid node identity",
|
||||
DiscQuitting: "Client quitting",
|
||||
DiscUnexpectedIdentity: "Unexpected identity",
|
||||
DiscSelf: "Connected to self",
|
||||
DiscReadTimeout: "Read timeout",
|
||||
DiscSubprotocolError: "Subprotocol error",
|
||||
}
|
||||
|
||||
func (d DiscReason) String() string {
|
||||
if len(discReasonToString) < int(d) {
|
||||
return fmt.Sprintf("Unknown Reason(%d)", d)
|
||||
}
|
||||
return discReasonToString[d]
|
||||
}
|
||||
|
||||
func (d DiscReason) Error() string {
|
||||
return d.String()
|
||||
}
|
||||
|
||||
func discReasonForError(err error) DiscReason {
|
||||
if reason, ok := err.(DiscReason); ok {
|
||||
return reason
|
||||
}
|
||||
peerError, ok := err.(*peerError)
|
||||
if ok {
|
||||
switch peerError.code {
|
||||
case errInvalidMsgCode, errInvalidMsg:
|
||||
return DiscProtocolError
|
||||
default:
|
||||
return DiscSubprotocolError
|
||||
}
|
||||
}
|
||||
return DiscSubprotocolError
|
||||
}
|
||||
|
||||
// protoHandshake is the RLP structure of the protocol handshake.
|
||||
type protoHandshake struct {
|
||||
Version uint64
|
||||
Name string
|
||||
Caps []Cap
|
||||
ListenPort uint64
|
||||
ID discover.NodeID
|
||||
}
|
||||
|
||||
// devConn implements the devp2p the messaging layer atop RLPx.
|
||||
type devConn struct {
|
||||
*rlpx.Conn
|
||||
// contains negotiated protocol sessions.
|
||||
// protocol zero is pre-negotiated and carries the
|
||||
// built-in devp2p packets.
|
||||
protocols []*devProtocol
|
||||
}
|
||||
|
||||
// devProtocol represents a running subprotocol.
|
||||
type devProtocol struct {
|
||||
p *rlpx.Protocol
|
||||
}
|
||||
|
||||
func newDevConn(fd net.Conn, key *ecdsa.PrivateKey, remote *ecdsa.PublicKey) *devConn {
|
||||
c := new(devConn)
|
||||
if remote == nil {
|
||||
c.Conn = rlpx.Server(fd, &rlpx.Config{Key: key})
|
||||
} else {
|
||||
c.Conn = rlpx.Client(fd, remote, &rlpx.Config{Key: key})
|
||||
}
|
||||
c.protocols = []*devProtocol{{c.Conn.Protocol(0)}}
|
||||
return c
|
||||
}
|
||||
|
||||
func (t *devConn) addProtocols(n int) {
|
||||
for i := 0; i < n; i++ {
|
||||
p := t.Conn.Protocol(uint16(len(t.protocols)))
|
||||
t.protocols = append(t.protocols, &devProtocol{p})
|
||||
}
|
||||
}
|
||||
|
||||
// protoHandshake negotiates RLPx subprotocols.
|
||||
// the protocol handshake is the first authenticated message
|
||||
// and also verifies whether the RLPx encryption handshake 'worked' and the
|
||||
// remote side actually provided the right public key.
|
||||
func (t *devConn) doProtoHandshake(our *protoHandshake) (their *protoHandshake, err error) {
|
||||
// Writing our handshake happens concurrently, we prefer
|
||||
// returning the handshake read error. If the remote side
|
||||
// disconnects us early with a valid reason, we should return it
|
||||
// as the error so it can be tracked elsewhere.
|
||||
werr := make(chan error, 1)
|
||||
go func() { werr <- Send(t.protocols[0], handshakeMsg, our) }()
|
||||
if their, err = readProtocolHandshake(t.protocols[0], our); err != nil {
|
||||
<-werr // make sure the write terminates too
|
||||
return nil, err
|
||||
}
|
||||
if err := <-werr; err != nil {
|
||||
return nil, fmt.Errorf("write error: %v", err)
|
||||
}
|
||||
return their, nil
|
||||
}
|
||||
|
||||
func readProtocolHandshake(rw MsgReader, our *protoHandshake) (*protoHandshake, error) {
|
||||
msg, err := rw.ReadMsg()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if msg.Size > baseProtocolMaxMsgSize {
|
||||
return nil, fmt.Errorf("message too big")
|
||||
}
|
||||
if msg.Code == discMsg {
|
||||
// Disconnect before protocol handshake is valid according to the
|
||||
// spec and we send it ourself if the posthanshake checks fail.
|
||||
// We can't return the reason directly, though, because it is echoed
|
||||
// back otherwise. Wrap it in a string instead.
|
||||
var reason [1]DiscReason
|
||||
rlp.Decode(msg.Payload, &reason)
|
||||
return nil, reason[0]
|
||||
}
|
||||
if msg.Code != handshakeMsg {
|
||||
return nil, fmt.Errorf("expected handshake, got %x", msg.Code)
|
||||
}
|
||||
var hs protoHandshake
|
||||
if err := msg.Decode(&hs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// validate handshake info
|
||||
if hs.Version != our.Version {
|
||||
return nil, DiscIncompatibleVersion
|
||||
}
|
||||
if (hs.ID == discover.NodeID{}) {
|
||||
return nil, DiscInvalidIdentity
|
||||
}
|
||||
return &hs, nil
|
||||
}
|
||||
|
||||
func (t *devConn) close(err error) {
|
||||
// Tell the remote end why we're disconnecting if possible.
|
||||
// TODO: if t.DidHandshake()
|
||||
if r, ok := err.(DiscReason); ok && r != DiscNetworkError {
|
||||
SendItems(t.protocols[0], discMsg, r)
|
||||
}
|
||||
t.Close()
|
||||
}
|
||||
|
||||
func (p *devProtocol) WriteMsg(msg Msg) error {
|
||||
codelen, code, _ := rlp.EncodeToReader(msg.Code)
|
||||
if msg.Size > math.MaxUint32-uint32(codelen) {
|
||||
return errMsgTooBig
|
||||
}
|
||||
plen := msg.Size + uint32(codelen)
|
||||
return p.p.SendPacket(plen, io.MultiReader(code, msg.Payload))
|
||||
}
|
||||
|
||||
func (p *devProtocol) ReadMsg() (msg Msg, err error) {
|
||||
len, r, err := p.p.ReadPacket()
|
||||
if err != nil {
|
||||
return msg, err
|
||||
}
|
||||
// Parse the message code, which is prepended to the protocol payload.
|
||||
// r must be recognized as buffered by package rlp to prevent it from
|
||||
// reading into the payload. The interface assertion ensures that it is.
|
||||
// The input limit is 9, which is as large as an encoded uint64 can get.
|
||||
s := rlp.NewStream(r.(rlp.ByteReader), 9)
|
||||
if err := s.Decode(&msg.Code); err != nil {
|
||||
return msg, err
|
||||
}
|
||||
// Remaining data in r belongs to the protocol.
|
||||
msg.Payload = r
|
||||
msg.Size = len - uint32(rlp.IntSize(msg.Code))
|
||||
msg.ReceivedAt = time.Now()
|
||||
return msg, nil
|
||||
}
|
||||
125
p2p/devp2p_test.go
Normal file
125
p2p/devp2p_test.go
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
)
|
||||
|
||||
func TestProtocolHandshake(t *testing.T) {
|
||||
var (
|
||||
prv0, prv1 = newkey(), newkey()
|
||||
fd0, fd1 = net.Pipe()
|
||||
hs0 = &protoHandshake{Version: 3, ID: discover.PubkeyID(&prv0.PublicKey), Caps: []Cap{{"a", 0}, {"b", 2}}}
|
||||
hs1 = &protoHandshake{Version: 3, ID: discover.PubkeyID(&prv1.PublicKey), Caps: []Cap{{"c", 1}, {"d", 3}}}
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
conn := newDevConn(fd0, prv0, nil)
|
||||
|
||||
phs, err := conn.doProtoHandshake(hs0)
|
||||
if err != nil {
|
||||
t.Errorf("dial side proto handshake error: %v", err)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(phs, hs1) {
|
||||
t.Errorf("dial side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs1))
|
||||
return
|
||||
}
|
||||
conn.close(DiscQuitting)
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
conn := newDevConn(fd1, prv1, &prv0.PublicKey)
|
||||
|
||||
phs, err := conn.doProtoHandshake(hs1)
|
||||
if err != nil {
|
||||
t.Errorf("listen side proto handshake error: %v", err)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(phs, hs0) {
|
||||
t.Errorf("listen side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs0))
|
||||
return
|
||||
}
|
||||
|
||||
if err := ExpectMsg(conn.protocols[0], discMsg, []DiscReason{DiscQuitting}); err != nil {
|
||||
t.Errorf("error receiving disconnect: %v", err)
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestProtocolHandshakeErrors(t *testing.T) {
|
||||
our := &protoHandshake{Version: 3, Caps: []Cap{{"foo", 2}, {"bar", 3}}, Name: "quux"}
|
||||
id := randomID()
|
||||
tests := []struct {
|
||||
code uint64
|
||||
msg interface{}
|
||||
err error
|
||||
}{
|
||||
{
|
||||
code: discMsg,
|
||||
msg: []DiscReason{DiscQuitting},
|
||||
err: DiscQuitting,
|
||||
},
|
||||
{
|
||||
code: 0x989898,
|
||||
msg: []byte{1},
|
||||
err: errors.New("expected handshake, got 989898"),
|
||||
},
|
||||
{
|
||||
code: handshakeMsg,
|
||||
msg: make([]byte, baseProtocolMaxMsgSize+2),
|
||||
err: errors.New("message too big"),
|
||||
},
|
||||
{
|
||||
code: handshakeMsg,
|
||||
msg: []byte{1, 2, 3},
|
||||
err: newPeerError(errInvalidMsg, "(code 0) (size 4) rlp: expected input list for p2p.protoHandshake"),
|
||||
},
|
||||
{
|
||||
code: handshakeMsg,
|
||||
msg: &protoHandshake{Version: 9944, ID: id},
|
||||
err: DiscIncompatibleVersion,
|
||||
},
|
||||
{
|
||||
code: handshakeMsg,
|
||||
msg: &protoHandshake{Version: 3},
|
||||
err: DiscInvalidIdentity,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
p1, p2 := MsgPipe()
|
||||
go Send(p1, test.code, test.msg)
|
||||
_, err := readProtocolHandshake(p2, our)
|
||||
if !reflect.DeepEqual(err, test.err) {
|
||||
t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err)
|
||||
}
|
||||
p1.Close()
|
||||
}
|
||||
}
|
||||
12
p2p/dial.go
12
p2p/dial.go
|
|
@ -133,7 +133,7 @@ func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now
|
|||
// Compute number of dynamic dials necessary at this point.
|
||||
needDynDials := s.maxDynDials
|
||||
for _, p := range peers {
|
||||
if p.rw.is(dynDialedConn) {
|
||||
if p.conn.is(dynDialedConn) {
|
||||
needDynDials--
|
||||
}
|
||||
}
|
||||
|
|
@ -205,6 +205,11 @@ func (s *dialstate) taskDone(t task, now time.Time) {
|
|||
}
|
||||
|
||||
func (t *dialTask) Do(srv *Server) {
|
||||
remotePubkey, err := t.dest.ID.Pubkey()
|
||||
if err != nil {
|
||||
glog.V(logger.Warn).Infof("aborted dialing (invalid pubkey) %v\n", t.dest)
|
||||
return
|
||||
}
|
||||
addr := &net.TCPAddr{IP: t.dest.IP, Port: int(t.dest.TCP)}
|
||||
glog.V(logger.Debug).Infof("dialing %v\n", t.dest)
|
||||
fd, err := srv.Dialer.Dial("tcp", addr.String())
|
||||
|
|
@ -213,9 +218,10 @@ func (t *dialTask) Do(srv *Server) {
|
|||
return
|
||||
}
|
||||
mfd := newMeteredConn(fd, false)
|
||||
|
||||
srv.setupConn(mfd, t.flags, t.dest)
|
||||
dc := newDevConn(mfd, srv.PrivateKey, remotePubkey)
|
||||
srv.setupConn(dc, t.flags, t.dest)
|
||||
}
|
||||
|
||||
func (t *dialTask) String() string {
|
||||
return fmt.Sprintf("%v %x %v:%d", t.flags, t.dest.ID[:8], t.dest.IP, t.dest.TCP)
|
||||
}
|
||||
|
|
|
|||
157
p2p/dial_test.go
157
p2p/dial_test.go
|
|
@ -28,6 +28,9 @@ import (
|
|||
|
||||
func init() {
|
||||
spew.Config.Indent = "\t"
|
||||
spew.Config.DisableMethods = true
|
||||
// glog.SetV(8)
|
||||
// glog.SetToStderr(true)
|
||||
}
|
||||
|
||||
type dialtest struct {
|
||||
|
|
@ -49,7 +52,7 @@ func runDialTest(t *testing.T, test dialtest) {
|
|||
pm := func(ps []*Peer) map[discover.NodeID]*Peer {
|
||||
m := make(map[discover.NodeID]*Peer)
|
||||
for _, p := range ps {
|
||||
m[p.rw.id] = p
|
||||
m[p.conn.id] = p
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
|
@ -94,18 +97,18 @@ func TestDialStateDynDial(t *testing.T) {
|
|||
// A discovery query is launched.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(0)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(0)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
},
|
||||
new: []task{&discoverTask{bootstrap: true}},
|
||||
},
|
||||
// Dynamic dials are launched when it completes.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(0)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(0)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
},
|
||||
done: []task{
|
||||
&discoverTask{bootstrap: true, results: []*discover.Node{
|
||||
|
|
@ -127,11 +130,11 @@ func TestDialStateDynDial(t *testing.T) {
|
|||
// the sum of active dial count and dynamic peer count is == maxDynDials.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(0)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(3)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(4)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(0)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(3)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(4)}},
|
||||
},
|
||||
done: []task{
|
||||
&dialTask{dynDialedConn, &discover.Node{ID: uintID(3)}},
|
||||
|
|
@ -142,12 +145,12 @@ func TestDialStateDynDial(t *testing.T) {
|
|||
// maxDynDials has been reached.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(0)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(3)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(4)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(5)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(0)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(3)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(4)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(5)}},
|
||||
},
|
||||
done: []task{
|
||||
&dialTask{dynDialedConn, &discover.Node{ID: uintID(5)}},
|
||||
|
|
@ -160,11 +163,11 @@ func TestDialStateDynDial(t *testing.T) {
|
|||
// results from last discovery lookup are reused.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(0)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(3)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(4)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(5)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(0)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(3)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(4)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(5)}},
|
||||
},
|
||||
new: []task{
|
||||
&dialTask{dynDialedConn, &discover.Node{ID: uintID(6)}},
|
||||
|
|
@ -175,9 +178,9 @@ func TestDialStateDynDial(t *testing.T) {
|
|||
// and a new one is spawned because more candidates are needed.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(0)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(5)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(0)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(5)}},
|
||||
},
|
||||
done: []task{
|
||||
&dialTask{dynDialedConn, &discover.Node{ID: uintID(6)}},
|
||||
|
|
@ -192,10 +195,10 @@ func TestDialStateDynDial(t *testing.T) {
|
|||
// no new is started.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(0)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(5)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(7)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(0)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(5)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(7)}},
|
||||
},
|
||||
done: []task{
|
||||
&dialTask{dynDialedConn, &discover.Node{ID: uintID(7)}},
|
||||
|
|
@ -205,10 +208,10 @@ func TestDialStateDynDial(t *testing.T) {
|
|||
// should be immediately requested.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(0)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(5)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(7)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(0)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(5)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(7)}},
|
||||
},
|
||||
done: []task{
|
||||
&discoverTask{},
|
||||
|
|
@ -259,8 +262,8 @@ func TestDialStateDynDialFromTable(t *testing.T) {
|
|||
// Dialing nodes 1,2 succeeds. Dials from the lookup are launched.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
},
|
||||
done: []task{
|
||||
&dialTask{dynDialedConn, &discover.Node{ID: uintID(1)}},
|
||||
|
|
@ -281,11 +284,11 @@ func TestDialStateDynDialFromTable(t *testing.T) {
|
|||
// Dialing nodes 3,4,5 fails. The dials from the lookup succeed.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(10)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(11)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(12)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(10)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(11)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(12)}},
|
||||
},
|
||||
done: []task{
|
||||
&dialTask{dynDialedConn, &discover.Node{ID: uintID(3)}},
|
||||
|
|
@ -300,11 +303,11 @@ func TestDialStateDynDialFromTable(t *testing.T) {
|
|||
// discovery query is still running.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(10)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(11)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(12)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(10)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(11)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(12)}},
|
||||
},
|
||||
},
|
||||
// Nodes 3,4 are not tried again because only the first two
|
||||
|
|
@ -312,11 +315,11 @@ func TestDialStateDynDialFromTable(t *testing.T) {
|
|||
// already connected.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(10)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(11)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(12)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(10)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(11)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(12)}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -340,8 +343,8 @@ func TestDialStateStaticDial(t *testing.T) {
|
|||
// aren't yet connected.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
},
|
||||
new: []task{
|
||||
&dialTask{staticDialedConn, &discover.Node{ID: uintID(3)}},
|
||||
|
|
@ -353,9 +356,9 @@ func TestDialStateStaticDial(t *testing.T) {
|
|||
// nodes are either connected or still being dialed.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(3)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(3)}},
|
||||
},
|
||||
done: []task{
|
||||
&dialTask{staticDialedConn, &discover.Node{ID: uintID(3)}},
|
||||
|
|
@ -365,11 +368,11 @@ func TestDialStateStaticDial(t *testing.T) {
|
|||
// nodes are now connected.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(3)}},
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(4)}},
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(5)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(3)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(4)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(5)}},
|
||||
},
|
||||
done: []task{
|
||||
&dialTask{staticDialedConn, &discover.Node{ID: uintID(4)}},
|
||||
|
|
@ -382,20 +385,20 @@ func TestDialStateStaticDial(t *testing.T) {
|
|||
// Wait a round for dial history to expire, no new tasks should spawn.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(3)}},
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(4)}},
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(5)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(3)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(4)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(5)}},
|
||||
},
|
||||
},
|
||||
// If a static node is dropped, it should be immediately redialed,
|
||||
// irrespective whether it was originally static or dynamic.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(3)}},
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(5)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(3)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(5)}},
|
||||
},
|
||||
new: []task{
|
||||
&dialTask{staticDialedConn, &discover.Node{ID: uintID(2)}},
|
||||
|
|
@ -431,8 +434,8 @@ func TestDialStateCache(t *testing.T) {
|
|||
// nodes are either connected or still being dialed.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: staticDialedConn, id: uintID(2)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: staticDialedConn, id: uintID(2)}},
|
||||
},
|
||||
done: []task{
|
||||
&dialTask{staticDialedConn, &discover.Node{ID: uintID(1)}},
|
||||
|
|
@ -443,8 +446,8 @@ func TestDialStateCache(t *testing.T) {
|
|||
// entry to expire.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
},
|
||||
done: []task{
|
||||
&dialTask{staticDialedConn, &discover.Node{ID: uintID(3)}},
|
||||
|
|
@ -456,15 +459,15 @@ func TestDialStateCache(t *testing.T) {
|
|||
// Still waiting for node 3's entry to expire in the cache.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
},
|
||||
},
|
||||
// The cache entry for node 3 has expired and is retried.
|
||||
{
|
||||
peers: []*Peer{
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(1)}},
|
||||
{conn: &conn{flags: dynDialedConn, id: uintID(2)}},
|
||||
},
|
||||
new: []task{
|
||||
&dialTask{staticDialedConn, &discover.Node{ID: uintID(3)}},
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ func (db *nodeDB) storeInt64(key []byte, n int64) error {
|
|||
func (db *nodeDB) node(id NodeID) *Node {
|
||||
blob, err := db.lvl.Get(makeKey(id, nodeDBDiscoverRoot), nil)
|
||||
if err != nil {
|
||||
glog.V(logger.Detail).Infof("failed to retrieve node %v: %v", id, err)
|
||||
glog.V(logger.Detail).Infof("node %x: %v", id[:8], err)
|
||||
return nil
|
||||
}
|
||||
node := new(Node)
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
|
|
@ -41,6 +39,7 @@ type Msg struct {
|
|||
Code uint64
|
||||
Size uint32 // size of the paylod
|
||||
Payload io.Reader
|
||||
|
||||
ReceivedAt time.Time
|
||||
}
|
||||
|
||||
|
|
@ -66,10 +65,12 @@ func (msg Msg) Discard() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// MsgReader wraps the ReadMsg operation.
|
||||
type MsgReader interface {
|
||||
ReadMsg() (Msg, error)
|
||||
}
|
||||
|
||||
// MsgWriter wraps the WriteMsg operation.
|
||||
type MsgWriter interface {
|
||||
// WriteMsg sends a message. It will block until the message's
|
||||
// Payload has been consumed by the other end.
|
||||
|
|
@ -110,30 +111,6 @@ func SendItems(w MsgWriter, msgcode uint64, elems ...interface{}) error {
|
|||
return Send(w, msgcode, elems)
|
||||
}
|
||||
|
||||
// netWrapper wraps a MsgReadWriter with locks around
|
||||
// ReadMsg/WriteMsg and applies read/write deadlines.
|
||||
type netWrapper struct {
|
||||
rmu, wmu sync.Mutex
|
||||
|
||||
rtimeout, wtimeout time.Duration
|
||||
conn net.Conn
|
||||
wrapped MsgReadWriter
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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
|
||||
// closed when the wrapped reader returns an error or when count bytes
|
||||
// have been read.
|
||||
|
|
|
|||
|
|
@ -18,11 +18,9 @@ package p2p
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
|
@ -141,11 +139,3 @@ 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
|
||||
}
|
||||
|
|
|
|||
187
p2p/peer.go
187
p2p/peer.go
|
|
@ -25,44 +25,21 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
const (
|
||||
baseProtocolVersion = 4
|
||||
baseProtocolLength = uint64(16)
|
||||
baseProtocolMaxMsgSize = 2 * 1024
|
||||
|
||||
pingInterval = 15 * time.Second
|
||||
)
|
||||
|
||||
const (
|
||||
// devp2p message codes
|
||||
handshakeMsg = 0x00
|
||||
discMsg = 0x01
|
||||
pingMsg = 0x02
|
||||
pongMsg = 0x03
|
||||
getPeersMsg = 0x04
|
||||
peersMsg = 0x05
|
||||
)
|
||||
|
||||
// protoHandshake is the RLP structure of the protocol handshake.
|
||||
type protoHandshake struct {
|
||||
Version uint64
|
||||
Name string
|
||||
Caps []Cap
|
||||
ListenPort uint64
|
||||
ID discover.NodeID
|
||||
}
|
||||
const pingInterval = 15 * time.Second
|
||||
|
||||
// Peer represents a connected remote node.
|
||||
type Peer struct {
|
||||
rw *conn
|
||||
running map[string]*protoRW
|
||||
// contains an element for each running subprotocol (excluding devp2p).
|
||||
running []*protoRW
|
||||
|
||||
conn *conn
|
||||
wg sync.WaitGroup
|
||||
protoErr chan error
|
||||
closed chan struct{}
|
||||
|
|
@ -72,7 +49,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 := &conn{fd: pipe, transport: nil, id: id, caps: caps, name: name}
|
||||
randomPriv, _ := crypto.GenerateKey()
|
||||
dc := newDevConn(pipe, randomPriv, nil)
|
||||
conn := &conn{transport: dc, id: id, caps: caps, name: name}
|
||||
peer := newPeer(conn, nil)
|
||||
close(peer.closed) // ensures Disconnect doesn't block
|
||||
return peer
|
||||
|
|
@ -80,28 +59,28 @@ func NewPeer(id discover.NodeID, name string, caps []Cap) *Peer {
|
|||
|
||||
// ID returns the node's public key.
|
||||
func (p *Peer) ID() discover.NodeID {
|
||||
return p.rw.id
|
||||
return p.conn.id
|
||||
}
|
||||
|
||||
// Name returns the node name that the remote node advertised.
|
||||
func (p *Peer) Name() string {
|
||||
return p.rw.name
|
||||
return p.conn.name
|
||||
}
|
||||
|
||||
// Caps returns the capabilities (supported subprotocols) of the remote peer.
|
||||
func (p *Peer) Caps() []Cap {
|
||||
// TODO: maybe return copy
|
||||
return p.rw.caps
|
||||
return p.conn.caps
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote address of the network connection.
|
||||
func (p *Peer) RemoteAddr() net.Addr {
|
||||
return p.rw.fd.RemoteAddr()
|
||||
return p.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
// LocalAddr returns the local address of the network connection.
|
||||
func (p *Peer) LocalAddr() net.Addr {
|
||||
return p.rw.fd.LocalAddr()
|
||||
return p.conn.LocalAddr()
|
||||
}
|
||||
|
||||
// Disconnect terminates the peer connection with the given reason.
|
||||
|
|
@ -115,13 +94,13 @@ func (p *Peer) Disconnect(reason DiscReason) {
|
|||
|
||||
// String implements fmt.Stringer.
|
||||
func (p *Peer) String() string {
|
||||
return fmt.Sprintf("Peer %x %v", p.rw.id[:8], p.RemoteAddr())
|
||||
return fmt.Sprintf("Peer %x %v", p.conn.id[:8], p.RemoteAddr())
|
||||
}
|
||||
|
||||
func newPeer(conn *conn, protocols []Protocol) *Peer {
|
||||
protomap := matchProtocols(protocols, conn.caps, conn)
|
||||
protomap := matchProtocols(protocols, conn.caps)
|
||||
p := &Peer{
|
||||
rw: conn,
|
||||
conn: conn,
|
||||
running: protomap,
|
||||
disc: make(chan DiscReason),
|
||||
protoErr: make(chan error, len(protomap)+1), // protocols + pingLoop
|
||||
|
|
@ -132,33 +111,40 @@ func newPeer(conn *conn, protocols []Protocol) *Peer {
|
|||
|
||||
func (p *Peer) run() DiscReason {
|
||||
var (
|
||||
writeStart = make(chan struct{}, 1)
|
||||
writeErr = make(chan error, 1)
|
||||
writeErr = make(chan error, len(p.running))
|
||||
readErr = make(chan error, 1)
|
||||
reason DiscReason
|
||||
requested bool
|
||||
// While most of the code works with the transport interface so it
|
||||
// can be tested, using the connection requires an actual
|
||||
// *devConn.
|
||||
devconn = p.conn.transport.(*devConn)
|
||||
)
|
||||
|
||||
// Ensure that the RLPx handshake is done. The only time this will
|
||||
// actually do anything is while testing because the tests don't
|
||||
// trigger the handshake explicitly.
|
||||
if err := devconn.Handshake(); err != nil {
|
||||
return DiscProtocolError
|
||||
}
|
||||
|
||||
p.wg.Add(2)
|
||||
go p.readLoop(readErr)
|
||||
go p.pingLoop()
|
||||
go p.readLoop(devconn.protocols[0], readErr)
|
||||
go p.pingLoop(devconn.protocols[0])
|
||||
|
||||
// Start all protocol handlers.
|
||||
writeStart <- struct{}{}
|
||||
p.startProtocols(writeStart, writeErr)
|
||||
p.startProtocols(devconn, writeErr)
|
||||
|
||||
// Wait for an error or disconnect.
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case err := <-writeErr:
|
||||
// A write finished. Allow the next write to start if
|
||||
// there was no error.
|
||||
if err != nil {
|
||||
glog.V(logger.Detail).Infof("%v: write error: %v\n", p, err)
|
||||
reason = DiscNetworkError
|
||||
break loop
|
||||
}
|
||||
writeStart <- struct{}{}
|
||||
case err := <-readErr:
|
||||
if r, ok := err.(DiscReason); ok {
|
||||
glog.V(logger.Debug).Infof("%v: remote requested disconnect: %v\n", p, r)
|
||||
|
|
@ -180,7 +166,7 @@ loop:
|
|||
}
|
||||
|
||||
close(p.closed)
|
||||
p.rw.close(reason)
|
||||
p.conn.close(reason)
|
||||
p.wg.Wait()
|
||||
if requested {
|
||||
reason = DiscRequested
|
||||
|
|
@ -188,14 +174,14 @@ loop:
|
|||
return reason
|
||||
}
|
||||
|
||||
func (p *Peer) pingLoop() {
|
||||
func (p *Peer) pingLoop(devp2p *devProtocol) {
|
||||
ping := time.NewTicker(pingInterval)
|
||||
defer p.wg.Done()
|
||||
defer ping.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ping.C:
|
||||
if err := SendItems(p.rw, pingMsg); err != nil {
|
||||
if err := SendItems(devp2p, pingMsg); err != nil {
|
||||
p.protoErr <- err
|
||||
return
|
||||
}
|
||||
|
|
@ -205,27 +191,27 @@ func (p *Peer) pingLoop() {
|
|||
}
|
||||
}
|
||||
|
||||
func (p *Peer) readLoop(errc chan<- error) {
|
||||
func (p *Peer) readLoop(devp2p *devProtocol, errc chan<- error) {
|
||||
defer p.wg.Done()
|
||||
for {
|
||||
msg, err := p.rw.ReadMsg()
|
||||
msg, err := devp2p.ReadMsg()
|
||||
if err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
msg.ReceivedAt = time.Now()
|
||||
if err = p.handle(msg); err != nil {
|
||||
if err = p.handle(devp2p, msg); err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Peer) handle(msg Msg) error {
|
||||
func (p *Peer) handle(devp2p *devProtocol, msg Msg) (err error) {
|
||||
switch {
|
||||
case msg.Code == pingMsg:
|
||||
msg.Discard()
|
||||
go SendItems(p.rw, pongMsg)
|
||||
go SendItems(devp2p, pongMsg)
|
||||
return
|
||||
case msg.Code == discMsg:
|
||||
var reason [1]DiscReason
|
||||
// This is the last message. We don't need to discard or
|
||||
|
|
@ -236,8 +222,10 @@ func (p *Peer) handle(msg Msg) error {
|
|||
// ignore other base protocol messages
|
||||
return msg.Discard()
|
||||
default:
|
||||
// it's a subprotocol message
|
||||
// Dispatch as subprotocol message by message code offset.
|
||||
// This is how dispatch worked before chunking was implemented.
|
||||
proto, err := p.getProto(msg.Code)
|
||||
msg.Code -= proto.offset
|
||||
if err != nil {
|
||||
return fmt.Errorf("msg code out of range: %v", msg.Code)
|
||||
}
|
||||
|
|
@ -248,7 +236,6 @@ func (p *Peer) handle(msg Msg) error {
|
|||
return io.EOF
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func countMatchingProtocols(protocols []Protocol, caps []Cap) int {
|
||||
|
|
@ -263,24 +250,27 @@ func countMatchingProtocols(protocols []Protocol, caps []Cap) int {
|
|||
return n
|
||||
}
|
||||
|
||||
// matchProtocols creates structures for matching named subprotocols.
|
||||
func matchProtocols(protocols []Protocol, caps []Cap, rw MsgReadWriter) map[string]*protoRW {
|
||||
// matchProtocols creates protoRWs for matching named subprotocols.
|
||||
func matchProtocols(protocols []Protocol, caps []Cap) []*protoRW {
|
||||
sort.Sort(capsByNameAndVersion(caps))
|
||||
i := 0
|
||||
offset := baseProtocolLength
|
||||
result := make(map[string]*protoRW)
|
||||
|
||||
var result []*protoRW
|
||||
outer:
|
||||
for _, cap := range caps {
|
||||
for _, proto := range protocols {
|
||||
if proto.Name == cap.Name && proto.Version == cap.Version {
|
||||
// If an old protocol version matched, revert it
|
||||
if old := result[cap.Name]; old != nil {
|
||||
offset -= old.Length
|
||||
if i > 0 && result[i-1].Name == cap.Name {
|
||||
// If the previous match was for the same protocol
|
||||
// (with a lower version), reset the offset and replace it.
|
||||
offset -= result[i-1].Protocol.Length
|
||||
} else {
|
||||
// Otherwise, append a new protocol.
|
||||
result = append(result, nil)
|
||||
i++
|
||||
}
|
||||
// Assign the new match
|
||||
result[cap.Name] = &protoRW{Protocol: proto, offset: offset, in: make(chan Msg), w: rw}
|
||||
result[i-1] = &protoRW{Protocol: proto, offset: offset}
|
||||
offset += proto.Length
|
||||
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
|
|
@ -288,13 +278,33 @@ outer:
|
|||
return result
|
||||
}
|
||||
|
||||
func (p *Peer) startProtocols(writeStart <-chan struct{}, writeErr chan<- error) {
|
||||
func (p *Peer) startProtocols(dc *devConn, writeErr chan<- error) {
|
||||
switch dc.Version() {
|
||||
case 5:
|
||||
// Acknowledge the protocols on the RLPx layer. This creates
|
||||
// *devProtocol wrappers, dc.protocols[i] contains entries in
|
||||
// range 1..len(p.running).
|
||||
dc.addProtocols(len(p.running))
|
||||
for i, proto := range p.running {
|
||||
proto.offset = 0
|
||||
proto.werr = writeErr
|
||||
proto.rw = dc.protocols[i+1]
|
||||
}
|
||||
case 4:
|
||||
// This is a legacy connection with offset-based dispatch.
|
||||
for _, proto := range p.running {
|
||||
proto.closed = p.closed
|
||||
proto.in = make(chan Msg)
|
||||
proto.werr = writeErr
|
||||
proto.rw = dc.protocols[0]
|
||||
}
|
||||
default:
|
||||
panic("conn has no version")
|
||||
}
|
||||
// Spawn Run for all protocols.
|
||||
p.wg.Add(len(p.running))
|
||||
for _, proto := range p.running {
|
||||
proto := proto
|
||||
proto.closed = p.closed
|
||||
proto.wstart = writeStart
|
||||
proto.werr = writeErr
|
||||
glog.V(logger.Detail).Infof("%v: Starting protocol %s/%d\n", p, proto.Name, proto.Version)
|
||||
go func() {
|
||||
err := proto.Run(p, proto)
|
||||
|
|
@ -314,7 +324,7 @@ func (p *Peer) startProtocols(writeStart <-chan struct{}, writeErr chan<- error)
|
|||
// the given message code.
|
||||
func (p *Peer) getProto(code uint64) (*protoRW, error) {
|
||||
for _, proto := range p.running {
|
||||
if code >= proto.offset && code < proto.offset+proto.Length {
|
||||
if proto.offset > 0 && code >= proto.offset && code < proto.offset+proto.Length {
|
||||
return proto, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -323,37 +333,40 @@ func (p *Peer) getProto(code uint64) (*protoRW, error) {
|
|||
|
||||
type protoRW struct {
|
||||
Protocol
|
||||
offset uint64
|
||||
rw MsgReadWriter
|
||||
werr chan<- error // for write results
|
||||
|
||||
// for RLPx V4 offset-based dispatch
|
||||
in chan Msg // receices read messages
|
||||
closed <-chan struct{} // receives when peer is shutting down
|
||||
wstart <-chan struct{} // receives when write may start
|
||||
werr chan<- error // for write results
|
||||
offset uint64
|
||||
w MsgWriter
|
||||
index uint16
|
||||
}
|
||||
|
||||
func (rw *protoRW) WriteMsg(msg Msg) (err error) {
|
||||
func (rw *protoRW) WriteMsg(msg Msg) error {
|
||||
if msg.Code >= rw.Length {
|
||||
return newPeerError(errInvalidMsgCode, "not handled")
|
||||
}
|
||||
msg.Code += rw.offset
|
||||
select {
|
||||
case <-rw.wstart:
|
||||
err = rw.w.WriteMsg(msg)
|
||||
// Report write status back to Peer.run. It will initiate
|
||||
// shutdown if the error is non-nil and unblock the next write
|
||||
// otherwise. The calling protocol code should exit for errors
|
||||
// as well but we don't want to rely on that.
|
||||
err := rw.rw.WriteMsg(msg)
|
||||
// Report write status back to Peer.run. It will initiate shutdown
|
||||
// if the error is non-nil otherwise. The calling protocol should
|
||||
// exit soon after, but might not return the error correctly.
|
||||
if err != nil {
|
||||
rw.werr <- err
|
||||
case <-rw.closed:
|
||||
err = fmt.Errorf("shutting down")
|
||||
}
|
||||
// TODO: maybe make the error sticky to prevent further writes
|
||||
return err
|
||||
}
|
||||
|
||||
func (rw *protoRW) ReadMsg() (Msg, error) {
|
||||
if rw.offset == 0 {
|
||||
// RLPx version 5
|
||||
return rw.rw.ReadMsg()
|
||||
}
|
||||
// RLPx version 4
|
||||
select {
|
||||
case msg := <-rw.in:
|
||||
msg.Code -= rw.offset
|
||||
return msg, nil
|
||||
case <-rw.closed:
|
||||
return Msg{}, io.EOF
|
||||
|
|
|
|||
|
|
@ -50,64 +50,3 @@ func newPeerError(code int, format string, v ...interface{}) *peerError {
|
|||
func (self *peerError) Error() string {
|
||||
return self.message
|
||||
}
|
||||
|
||||
type DiscReason uint
|
||||
|
||||
const (
|
||||
DiscRequested DiscReason = iota
|
||||
DiscNetworkError
|
||||
DiscProtocolError
|
||||
DiscUselessPeer
|
||||
DiscTooManyPeers
|
||||
DiscAlreadyConnected
|
||||
DiscIncompatibleVersion
|
||||
DiscInvalidIdentity
|
||||
DiscQuitting
|
||||
DiscUnexpectedIdentity
|
||||
DiscSelf
|
||||
DiscReadTimeout
|
||||
DiscSubprotocolError = 0x10
|
||||
)
|
||||
|
||||
var discReasonToString = [...]string{
|
||||
DiscRequested: "Disconnect requested",
|
||||
DiscNetworkError: "Network error",
|
||||
DiscProtocolError: "Breach of protocol",
|
||||
DiscUselessPeer: "Useless peer",
|
||||
DiscTooManyPeers: "Too many peers",
|
||||
DiscAlreadyConnected: "Already connected",
|
||||
DiscIncompatibleVersion: "Incompatible P2P protocol version",
|
||||
DiscInvalidIdentity: "Invalid node identity",
|
||||
DiscQuitting: "Client quitting",
|
||||
DiscUnexpectedIdentity: "Unexpected identity",
|
||||
DiscSelf: "Connected to self",
|
||||
DiscReadTimeout: "Read timeout",
|
||||
DiscSubprotocolError: "Subprotocol error",
|
||||
}
|
||||
|
||||
func (d DiscReason) String() string {
|
||||
if len(discReasonToString) < int(d) {
|
||||
return fmt.Sprintf("Unknown Reason(%d)", d)
|
||||
}
|
||||
return discReasonToString[d]
|
||||
}
|
||||
|
||||
func (d DiscReason) Error() string {
|
||||
return d.String()
|
||||
}
|
||||
|
||||
func discReasonForError(err error) DiscReason {
|
||||
if reason, ok := err.(DiscReason); ok {
|
||||
return reason
|
||||
}
|
||||
peerError, ok := err.(*peerError)
|
||||
if ok {
|
||||
switch peerError.code {
|
||||
case errInvalidMsgCode, errInvalidMsg:
|
||||
return DiscProtocolError
|
||||
default:
|
||||
return DiscSubprotocolError
|
||||
}
|
||||
}
|
||||
return DiscSubprotocolError
|
||||
}
|
||||
|
|
|
|||
158
p2p/peer_test.go
158
p2p/peer_test.go
|
|
@ -24,6 +24,8 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
var discard = Protocol{
|
||||
|
|
@ -43,21 +45,20 @@ var discard = Protocol{
|
|||
},
|
||||
}
|
||||
|
||||
func testPeer(protos []Protocol) (func(), *conn, *Peer, <-chan DiscReason) {
|
||||
func testPeer(protos []Protocol) (*devConn, *Peer, <-chan DiscReason) {
|
||||
fd1, fd2 := net.Pipe()
|
||||
c1 := &conn{fd: fd1, transport: newTestTransport(randomID(), fd1)}
|
||||
c2 := &conn{fd: fd2, transport: newTestTransport(randomID(), fd2)}
|
||||
k1, k2 := newkey(), newkey()
|
||||
c1 := &conn{transport: newDevConn(fd1, k1, &k2.PublicKey)}
|
||||
for _, p := range protos {
|
||||
c1.caps = append(c1.caps, p.cap())
|
||||
c2.caps = append(c2.caps, p.cap())
|
||||
}
|
||||
|
||||
peer := newPeer(c1, protos)
|
||||
errc := make(chan DiscReason, 1)
|
||||
go func() { errc <- peer.run() }()
|
||||
|
||||
closer := func() { c2.close(errors.New("close func called")) }
|
||||
return closer, c2, peer, errc
|
||||
c2 := newDevConn(fd2, k2, nil)
|
||||
c2.addProtocols(len(protos))
|
||||
return c2, peer, errc
|
||||
}
|
||||
|
||||
func TestPeerProtoReadMsg(t *testing.T) {
|
||||
|
|
@ -80,12 +81,12 @@ func TestPeerProtoReadMsg(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
closer, rw, _, errc := testPeer([]Protocol{proto})
|
||||
defer closer()
|
||||
conn, _, errc := testPeer([]Protocol{proto})
|
||||
defer conn.Close()
|
||||
|
||||
Send(rw, baseProtocolLength+2, []uint{1})
|
||||
Send(rw, baseProtocolLength+3, []uint{2})
|
||||
Send(rw, baseProtocolLength+4, []uint{3})
|
||||
Send(conn.protocols[1], 2, []uint{1})
|
||||
Send(conn.protocols[1], 3, []uint{2})
|
||||
Send(conn.protocols[1], 4, []uint{3})
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
|
|
@ -110,29 +111,29 @@ func TestPeerProtoEncodeMsg(t *testing.T) {
|
|||
return nil
|
||||
},
|
||||
}
|
||||
closer, rw, _, _ := testPeer([]Protocol{proto})
|
||||
defer closer()
|
||||
conn, _, _ := testPeer([]Protocol{proto})
|
||||
defer conn.Close()
|
||||
|
||||
if err := ExpectMsg(rw, 17, []string{"foo", "bar"}); err != nil {
|
||||
if err := ExpectMsg(conn.protocols[1], 1, []string{"foo", "bar"}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeerPing(t *testing.T) {
|
||||
closer, rw, _, _ := testPeer(nil)
|
||||
defer closer()
|
||||
if err := SendItems(rw, pingMsg); err != nil {
|
||||
conn, _, _ := testPeer(nil)
|
||||
defer conn.Close()
|
||||
if err := SendItems(conn.protocols[0], pingMsg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ExpectMsg(rw, pongMsg, nil); err != nil {
|
||||
if err := ExpectMsg(conn.protocols[0], pongMsg, nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeerDisconnect(t *testing.T) {
|
||||
closer, rw, _, disc := testPeer(nil)
|
||||
defer closer()
|
||||
if err := SendItems(rw, discMsg, DiscQuitting); err != nil {
|
||||
conn, _, disc := testPeer(nil)
|
||||
defer conn.Close()
|
||||
if err := SendItems(conn.protocols[0], discMsg, DiscQuitting); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
select {
|
||||
|
|
@ -150,10 +151,10 @@ func TestPeerDisconnect(t *testing.T) {
|
|||
func TestPeerDisconnectRace(t *testing.T) {
|
||||
maybe := func() bool { return rand.Intn(1) == 1 }
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
protoclose := make(chan error)
|
||||
protodisc := make(chan DiscReason)
|
||||
closer, rw, p, disc := testPeer([]Protocol{
|
||||
for i := 0; i < 100; i++ {
|
||||
protoclose := make(chan error, 1)
|
||||
protodisc := make(chan DiscReason, 1)
|
||||
conn, p, disc := testPeer([]Protocol{
|
||||
{
|
||||
Name: "closereq",
|
||||
Run: func(p *Peer, rw MsgReadWriter) error { return <-protoclose },
|
||||
|
|
@ -165,12 +166,13 @@ func TestPeerDisconnectRace(t *testing.T) {
|
|||
Length: 1,
|
||||
},
|
||||
})
|
||||
conn.Handshake()
|
||||
|
||||
// Simulate incoming messages.
|
||||
go SendItems(rw, baseProtocolLength+1)
|
||||
go SendItems(rw, baseProtocolLength+2)
|
||||
go SendItems(conn.protocols[1], 1)
|
||||
go SendItems(conn.protocols[2], 2)
|
||||
// Close the network connection.
|
||||
go closer()
|
||||
go conn.Close()
|
||||
// Make protocol "closereq" return.
|
||||
protoclose <- errors.New("protocol closed")
|
||||
// Make protocol "disconnect" call peer.Disconnect
|
||||
|
|
@ -181,7 +183,7 @@ func TestPeerDisconnectRace(t *testing.T) {
|
|||
}
|
||||
// In some cases, simulate remote requesting a disconnect.
|
||||
if maybe() {
|
||||
go SendItems(rw, discMsg, DiscQuitting)
|
||||
go SendItems(conn.protocols[0], discMsg, DiscQuitting)
|
||||
}
|
||||
|
||||
select {
|
||||
|
|
@ -214,96 +216,78 @@ func TestNewPeer(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMatchProtocols(t *testing.T) {
|
||||
tests := []struct {
|
||||
tests := map[string]struct {
|
||||
Remote []Cap
|
||||
Local []Protocol
|
||||
Match map[string]protoRW
|
||||
Match []*protoRW
|
||||
}{
|
||||
{
|
||||
// No remote capabilities
|
||||
"no remote caps": {
|
||||
Local: []Protocol{{Name: "a"}},
|
||||
},
|
||||
{
|
||||
// No local protocols
|
||||
"no local protocols": {
|
||||
Remote: []Cap{{Name: "a"}},
|
||||
},
|
||||
{
|
||||
// No mutual protocols
|
||||
"no mutual protocols": {
|
||||
Remote: []Cap{{Name: "a"}},
|
||||
Local: []Protocol{{Name: "b"}},
|
||||
},
|
||||
{
|
||||
// Some matches, some differences
|
||||
"some matches": {
|
||||
Remote: []Cap{{Name: "local"}, {Name: "match1"}, {Name: "match2"}},
|
||||
Local: []Protocol{{Name: "match1"}, {Name: "match2"}, {Name: "remote"}},
|
||||
Match: map[string]protoRW{"match1": {Protocol: Protocol{Name: "match1"}}, "match2": {Protocol: Protocol{Name: "match2"}}},
|
||||
Match: []*protoRW{
|
||||
{Protocol: Protocol{Name: "match1"}, offset: 16},
|
||||
{Protocol: Protocol{Name: "match2"}, offset: 16},
|
||||
},
|
||||
{
|
||||
// Various alphabetical ordering
|
||||
},
|
||||
"alphabetical ordering": {
|
||||
Remote: []Cap{{Name: "aa"}, {Name: "ab"}, {Name: "bb"}, {Name: "ba"}},
|
||||
Local: []Protocol{{Name: "ba"}, {Name: "bb"}, {Name: "ab"}, {Name: "aa"}},
|
||||
Match: map[string]protoRW{"aa": {Protocol: Protocol{Name: "aa"}}, "ab": {Protocol: Protocol{Name: "ab"}}, "ba": {Protocol: Protocol{Name: "ba"}}, "bb": {Protocol: Protocol{Name: "bb"}}},
|
||||
Match: []*protoRW{
|
||||
{Protocol: Protocol{Name: "aa"}, offset: 16},
|
||||
{Protocol: Protocol{Name: "ab"}, offset: 16},
|
||||
{Protocol: Protocol{Name: "ba"}, offset: 16},
|
||||
{Protocol: Protocol{Name: "bb"}, offset: 16},
|
||||
},
|
||||
{
|
||||
// No mutual versions
|
||||
},
|
||||
"no mutual versions": {
|
||||
Remote: []Cap{{Version: 1}},
|
||||
Local: []Protocol{{Version: 2}},
|
||||
},
|
||||
{
|
||||
// Multiple versions, single common
|
||||
"multiple versions, single common": {
|
||||
Remote: []Cap{{Version: 1}, {Version: 2}},
|
||||
Local: []Protocol{{Version: 2}, {Version: 3}},
|
||||
Match: map[string]protoRW{"": {Protocol: Protocol{Version: 2}}},
|
||||
Match: []*protoRW{
|
||||
{Protocol: Protocol{Version: 2}, offset: 16},
|
||||
},
|
||||
{
|
||||
// Multiple versions, multiple common
|
||||
},
|
||||
"multiple versions, multiple common": {
|
||||
Remote: []Cap{{Version: 1}, {Version: 2}, {Version: 3}, {Version: 4}},
|
||||
Local: []Protocol{{Version: 2}, {Version: 3}},
|
||||
Match: map[string]protoRW{"": {Protocol: Protocol{Version: 3}}},
|
||||
Match: []*protoRW{
|
||||
{Protocol: Protocol{Version: 3}, offset: 16},
|
||||
},
|
||||
{
|
||||
// Various version orderings
|
||||
},
|
||||
"version ordering": {
|
||||
Remote: []Cap{{Version: 4}, {Version: 1}, {Version: 3}, {Version: 2}},
|
||||
Local: []Protocol{{Version: 2}, {Version: 3}, {Version: 1}},
|
||||
Match: map[string]protoRW{"": {Protocol: Protocol{Version: 3}}},
|
||||
Match: []*protoRW{
|
||||
{Protocol: Protocol{Version: 3}, offset: 16},
|
||||
},
|
||||
{
|
||||
// Versions overriding sub-protocol lengths
|
||||
},
|
||||
"versions overriding subprotocol lengths": {
|
||||
Remote: []Cap{{Version: 1}, {Version: 2}, {Version: 3}, {Name: "a"}},
|
||||
Local: []Protocol{{Version: 1, Length: 1}, {Version: 2, Length: 2}, {Version: 3, Length: 3}, {Name: "a"}},
|
||||
Match: map[string]protoRW{"": {Protocol: Protocol{Version: 3}}, "a": {Protocol: Protocol{Name: "a"}, offset: 3}},
|
||||
Match: []*protoRW{
|
||||
{Protocol: Protocol{Version: 3, Length: 3}, offset: 16},
|
||||
{Protocol: Protocol{Name: "a"}, offset: 19},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
result := matchProtocols(tt.Local, tt.Remote, nil)
|
||||
if len(result) != len(tt.Match) {
|
||||
t.Errorf("test %d: negotiation mismatch: have %v, want %v", i, len(result), len(tt.Match))
|
||||
continue
|
||||
}
|
||||
// Make sure all negotiated protocols are needed and correct
|
||||
for name, proto := range result {
|
||||
match, ok := tt.Match[name]
|
||||
if !ok {
|
||||
t.Errorf("test %d, proto '%s': negotiated but shouldn't have", i, name)
|
||||
continue
|
||||
}
|
||||
if proto.Name != match.Name {
|
||||
t.Errorf("test %d, proto '%s': name mismatch: have %v, want %v", i, name, proto.Name, match.Name)
|
||||
}
|
||||
if proto.Version != match.Version {
|
||||
t.Errorf("test %d, proto '%s': version mismatch: have %v, want %v", i, name, proto.Version, match.Version)
|
||||
}
|
||||
if proto.offset-baseProtocolLength != match.offset {
|
||||
t.Errorf("test %d, proto '%s': offset mismatch: have %v, want %v", i, name, proto.offset-baseProtocolLength, match.offset)
|
||||
}
|
||||
}
|
||||
// Make sure no protocols missed negotiation
|
||||
for name, _ := range tt.Match {
|
||||
if _, ok := result[name]; !ok {
|
||||
t.Errorf("test %d, proto '%s': not negotiated, should have", i, name)
|
||||
continue
|
||||
}
|
||||
for tname, tt := range tests {
|
||||
result := matchProtocols(tt.Local, tt.Remote)
|
||||
if !reflect.DeepEqual(result, tt.Match) {
|
||||
t.Errorf("%s: wrong result\ngot %s\nwant: %s", tname, spew.Sdump(result), spew.Sdump(tt.Match))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
635
p2p/rlpx.go
635
p2p/rlpx.go
|
|
@ -1,635 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
maxUint24 = ^uint32(0) >> 8
|
||||
|
||||
sskLen = 16 // ecies.MaxSharedKeyLength(pubKey) / 2
|
||||
sigLen = 65 // elliptic S256
|
||||
pubLen = 64 // 512 bit pubkey in uncompressed representation without format byte
|
||||
shaLen = 32 // hash length (for nonce etc)
|
||||
|
||||
authMsgLen = sigLen + shaLen + pubLen + shaLen + 1
|
||||
authRespLen = pubLen + shaLen + 1
|
||||
|
||||
eciesBytes = 65 + 16 + 32
|
||||
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
|
||||
|
||||
// total timeout for encryption handshake and protocol
|
||||
// handshake in both directions.
|
||||
handshakeTimeout = 5 * time.Second
|
||||
|
||||
// This is the timeout for sending the disconnect reason.
|
||||
// This is shorter than the usual timeout because we don't want
|
||||
// to wait if the connection is known to be bad anyway.
|
||||
discWriteTimeout = 1 * time.Second
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
rmu, wmu sync.Mutex
|
||||
rw *rlpxFrameRW
|
||||
}
|
||||
|
||||
func newRLPX(fd net.Conn) transport {
|
||||
fd.SetDeadline(time.Now().Add(handshakeTimeout))
|
||||
return &rlpx{fd: fd}
|
||||
}
|
||||
|
||||
func (t *rlpx) ReadMsg() (Msg, error) {
|
||||
t.rmu.Lock()
|
||||
defer t.rmu.Unlock()
|
||||
t.fd.SetReadDeadline(time.Now().Add(frameReadTimeout))
|
||||
return t.rw.ReadMsg()
|
||||
}
|
||||
|
||||
func (t *rlpx) WriteMsg(msg Msg) error {
|
||||
t.wmu.Lock()
|
||||
defer t.wmu.Unlock()
|
||||
t.fd.SetWriteDeadline(time.Now().Add(frameWriteTimeout))
|
||||
return t.rw.WriteMsg(msg)
|
||||
}
|
||||
|
||||
func (t *rlpx) close(err error) {
|
||||
t.wmu.Lock()
|
||||
defer t.wmu.Unlock()
|
||||
// Tell the remote end why we're disconnecting if possible.
|
||||
if t.rw != nil {
|
||||
if r, ok := err.(DiscReason); ok && r != DiscNetworkError {
|
||||
t.fd.SetWriteDeadline(time.Now().Add(discWriteTimeout))
|
||||
SendItems(t.rw, discMsg, r)
|
||||
}
|
||||
}
|
||||
t.fd.Close()
|
||||
}
|
||||
|
||||
// doEncHandshake runs the protocol handshake using authenticated
|
||||
// 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) doProtoHandshake(our *protoHandshake) (their *protoHandshake, err error) {
|
||||
// Writing our handshake happens concurrently, we prefer
|
||||
// returning the handshake read error. If the remote side
|
||||
// disconnects us early with a valid reason, we should return it
|
||||
// as the error so it can be tracked elsewhere.
|
||||
werr := make(chan error, 1)
|
||||
go func() { werr <- Send(t.rw, handshakeMsg, our) }()
|
||||
if their, err = readProtocolHandshake(t.rw, our); err != nil {
|
||||
<-werr // make sure the write terminates too
|
||||
return nil, err
|
||||
}
|
||||
if err := <-werr; err != nil {
|
||||
return nil, fmt.Errorf("write error: %v", err)
|
||||
}
|
||||
return their, nil
|
||||
}
|
||||
|
||||
func readProtocolHandshake(rw MsgReader, our *protoHandshake) (*protoHandshake, error) {
|
||||
msg, err := rw.ReadMsg()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if msg.Size > baseProtocolMaxMsgSize {
|
||||
return nil, fmt.Errorf("message too big")
|
||||
}
|
||||
if msg.Code == discMsg {
|
||||
// Disconnect before protocol handshake is valid according to the
|
||||
// spec and we send it ourself if the posthanshake checks fail.
|
||||
// We can't return the reason directly, though, because it is echoed
|
||||
// back otherwise. Wrap it in a string instead.
|
||||
var reason [1]DiscReason
|
||||
rlp.Decode(msg.Payload, &reason)
|
||||
return nil, reason[0]
|
||||
}
|
||||
if msg.Code != handshakeMsg {
|
||||
return nil, fmt.Errorf("expected handshake, got %x", msg.Code)
|
||||
}
|
||||
var hs protoHandshake
|
||||
if err := msg.Decode(&hs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// validate handshake info
|
||||
if hs.Version != our.Version {
|
||||
return nil, DiscIncompatibleVersion
|
||||
}
|
||||
if (hs.ID == discover.NodeID{}) {
|
||||
return nil, DiscInvalidIdentity
|
||||
}
|
||||
return &hs, nil
|
||||
}
|
||||
|
||||
func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *discover.Node) (discover.NodeID, error) {
|
||||
var (
|
||||
sec secrets
|
||||
err error
|
||||
)
|
||||
if dial == nil {
|
||||
sec, err = receiverEncHandshake(t.fd, prv, nil)
|
||||
} else {
|
||||
sec, err = initiatorEncHandshake(t.fd, prv, dial.ID, nil)
|
||||
}
|
||||
if err != nil {
|
||||
return discover.NodeID{}, err
|
||||
}
|
||||
t.wmu.Lock()
|
||||
t.rw = newRLPXFrameRW(t.fd, sec)
|
||||
t.wmu.Unlock()
|
||||
return sec.RemoteID, nil
|
||||
}
|
||||
|
||||
// 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 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
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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 s, err
|
||||
}
|
||||
auth, err := h.authMsg(prv, token)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
if _, err = conn.Write(auth); err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
response := make([]byte, encAuthRespLen)
|
||||
if _, err = io.ReadFull(conn, response); err != nil {
|
||||
return s, err
|
||||
}
|
||||
if err := h.decodeAuthResp(response, prv); err != nil {
|
||||
return s, err
|
||||
}
|
||||
return h.secrets(auth, response)
|
||||
}
|
||||
|
||||
func newInitiatorHandshake(remoteID discover.NodeID) (*encHandshake, error) {
|
||||
rpub, err := remoteID.Pubkey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad remoteID: %v", err)
|
||||
}
|
||||
// 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, secp256k1.S256(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h := &encHandshake{
|
||||
initiator: true,
|
||||
remoteID: remoteID,
|
||||
remotePub: ecies.ImportECDSAPublic(rpub),
|
||||
initNonce: n,
|
||||
randomPrivKey: randpriv,
|
||||
}
|
||||
return h, 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
|
||||
var err error
|
||||
if token, err = h.ecdhShared(prv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// for known peers, we use stored token from the previous session
|
||||
tokenFlag = 0x01
|
||||
}
|
||||
|
||||
// sign known message:
|
||||
// ecdh-shared-secret^nonce for new peers
|
||||
// token^nonce for old peers
|
||||
signed := xor(token, h.initNonce)
|
||||
signature, err := crypto.Sign(signed, h.randomPrivKey.ExportECDSA())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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, secp256k1.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
|
||||
}
|
||||
|
||||
// validate the sha3 of recovered pubkey
|
||||
remoteRandomPubMAC := msg[sigLen : sigLen+shaLen]
|
||||
shaRemoteRandomPub := crypto.Sha3(remoteRandomPub[1:])
|
||||
if !bytes.Equal(remoteRandomPubMAC, shaRemoteRandomPub) {
|
||||
return nil, fmt.Errorf("sha3 of recovered ephemeral pubkey does not match checksum in auth message")
|
||||
}
|
||||
|
||||
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) (*ecies.PublicKey, error) {
|
||||
var pubKey65 []byte
|
||||
switch len(pubKey) {
|
||||
case 64:
|
||||
// add 'uncompressed key' flag
|
||||
pubKey65 = append([]byte{0x04}, pubKey...)
|
||||
case 65:
|
||||
pubKey65 = pubKey
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid public key length %v (expect 64/65)", len(pubKey))
|
||||
}
|
||||
// TODO: fewer pointless conversions
|
||||
return ecies.ImportECDSAPublic(crypto.ToECDSAPub(pubKey65)), nil
|
||||
}
|
||||
|
||||
func exportPubkey(pub *ecies.PublicKey) []byte {
|
||||
if pub == nil {
|
||||
panic("nil pubkey")
|
||||
}
|
||||
return elliptic.Marshal(pub.Curve, pub.X, pub.Y)[1:]
|
||||
}
|
||||
|
||||
func xor(one, other []byte) (xor []byte) {
|
||||
xor = make([]byte, len(one))
|
||||
for i := 0; i < len(one); i++ {
|
||||
xor[i] = one[i] ^ other[i]
|
||||
}
|
||||
return xor
|
||||
}
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
95
p2p/rlpx/bufsema.go
Normal file
95
p2p/rlpx/bufsema.go
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rlpx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var errAcquireTimeout = errors.New("acquisition timeout")
|
||||
|
||||
// bufSema is a counting semaphore.
|
||||
type bufSema struct {
|
||||
val, cap, waiting uint32
|
||||
wakeup chan struct{}
|
||||
}
|
||||
|
||||
func newBufSema(cap uint32) *bufSema {
|
||||
return &bufSema{cap: cap, val: cap, wakeup: make(chan struct{})}
|
||||
}
|
||||
|
||||
func (sem *bufSema) get() uint32 {
|
||||
return atomic.LoadUint32(&sem.val)
|
||||
}
|
||||
|
||||
// release increments sem, potentially unblocking a call to
|
||||
// waitAcquire if there is one. release never blocks.
|
||||
func (sem *bufSema) release(n uint32) {
|
||||
new := atomic.AddUint32(&sem.val, n)
|
||||
if new > sem.cap {
|
||||
panic(fmt.Sprintf("semaphore count %d exceeds cap after release(%d)", new, n))
|
||||
}
|
||||
// Wake up a pending waitAcquire call if there is one.
|
||||
if atomic.CompareAndSwapUint32(&sem.waiting, 1, 0) {
|
||||
sem.wakeup <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// waitAcquire decrements the semaphore by n. If less than
|
||||
// n units are available, waitAcquire blocks until release is called.
|
||||
// It may only be called from one goroutine at a time.
|
||||
func (sem *bufSema) waitAcquire(n uint32, timeout time.Duration) error {
|
||||
if n > sem.cap {
|
||||
return fmt.Errorf("requested amount %d exceeds semaphore cap of %d", n, sem.cap)
|
||||
}
|
||||
var timer *time.Timer
|
||||
for {
|
||||
// Set the waiting flag so release will try to wake us after
|
||||
// incrementing sem.val.
|
||||
if !atomic.CompareAndSwapUint32(&sem.waiting, 0, 1) {
|
||||
panic("concurrent call to waitAcquire")
|
||||
}
|
||||
// Decrement if sem.val if possible.
|
||||
if atomic.LoadUint32(&sem.val) >= n {
|
||||
atomic.AddUint32(&sem.val, ^(n - 1))
|
||||
// Gobble up wakeup signal in case release decremented sem.waiting.
|
||||
if !atomic.CompareAndSwapUint32(&sem.waiting, 1, 0) {
|
||||
<-sem.wakeup
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Start the timeout on the first iteration.
|
||||
if timer == nil {
|
||||
timer = time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
}
|
||||
select {
|
||||
case <-sem.wakeup:
|
||||
// Woken by release. It has decremented sem.waiting back to zero.
|
||||
case <-timer.C:
|
||||
// Gobble up wakeup signal in case release decremented sem.waiting.
|
||||
if !atomic.CompareAndSwapUint32(&sem.waiting, 1, 0) {
|
||||
<-sem.wakeup
|
||||
}
|
||||
return errAcquireTimeout
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
95
p2p/rlpx/bufsema_test.go
Normal file
95
p2p/rlpx/bufsema_test.go
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rlpx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestBufSemaCountSimple(t *testing.T) {
|
||||
sem := newBufSema(2000)
|
||||
|
||||
checkacquire := func(count, wantCount uint32, wantErr error) {
|
||||
err := sem.waitAcquire(count, 10*time.Millisecond)
|
||||
if !reflect.DeepEqual(err, wantErr) {
|
||||
t.Fatalf("wrong error after acquire(%d): got %q, want %q", count, err, wantErr)
|
||||
}
|
||||
if val := sem.get(); val != wantCount {
|
||||
t.Fatalf("wrong count after acquire(%d): got %d, want %d", count, val, wantCount)
|
||||
}
|
||||
}
|
||||
checkrelease := func(count, wantCount uint32) {
|
||||
sem.release(count)
|
||||
if val := sem.get(); val != wantCount {
|
||||
t.Fatalf("wrong count after release(%d): got %d, want %d", count, val, wantCount)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the counter is maintained correctly.
|
||||
checkacquire(1000, 1000, nil)
|
||||
checkacquire(1000, 0, nil)
|
||||
checkacquire(1000, 0, errAcquireTimeout)
|
||||
checkrelease(900, 900)
|
||||
checkrelease(900, 1800)
|
||||
checkrelease(199, 1999)
|
||||
checkrelease(1, 2000)
|
||||
|
||||
// Check that requesting more than sem.cap fails.
|
||||
checkacquire(2001, 2000, errors.New("requested amount 2001 exceeds semaphore cap of 2000"))
|
||||
|
||||
// Check that a failed waitAcquire leaves sem.val as is when it is < sem.cap.
|
||||
checkacquire(500, 1500, nil)
|
||||
checkrelease(200, 1700)
|
||||
checkacquire(2000, 1700, errAcquireTimeout)
|
||||
}
|
||||
|
||||
// This test checks that release wakes up waitAcquire.
|
||||
func TestBufSemaRace(t *testing.T) {
|
||||
const (
|
||||
waitCount = 10000
|
||||
iterations = 5000
|
||||
)
|
||||
sem := newBufSema(waitCount)
|
||||
pleaserelease := make(chan uint32, 500)
|
||||
releaser := func() {
|
||||
for rv := range pleaserelease {
|
||||
sem.release(rv)
|
||||
}
|
||||
}
|
||||
defer close(pleaserelease)
|
||||
go releaser()
|
||||
go releaser()
|
||||
go releaser()
|
||||
|
||||
for i := 0; i < iterations; i++ {
|
||||
if err := sem.waitAcquire(waitCount, 1*time.Second); err != nil {
|
||||
t.Fatalf("iteration %d: %v", i, err)
|
||||
}
|
||||
for i := uint32(0); i < waitCount; {
|
||||
rv := rand.Uint32() % waitCount
|
||||
if i+rv > waitCount {
|
||||
rv = waitCount - i
|
||||
}
|
||||
i += rv
|
||||
pleaserelease <- rv
|
||||
}
|
||||
}
|
||||
}
|
||||
483
p2p/rlpx/framing.go
Normal file
483
p2p/rlpx/framing.go
Normal file
|
|
@ -0,0 +1,483 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rlpx
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
const (
|
||||
staticFrameSize uint32 = 8 * 1024
|
||||
frameHeaderSize = 16 // encoded header
|
||||
frameHeaderFullSize = 32 // encoded header + MAC
|
||||
)
|
||||
|
||||
var (
|
||||
errProtocolClaimTimeout = errors.New("protocol for pending message was not claimed in time")
|
||||
errUnexpectedChunkStart = errors.New("received chunk start header for existing transfer")
|
||||
errChunkTooLarge = errors.New("chunk size larger than remaining message size")
|
||||
)
|
||||
|
||||
// readLoop runs in its own goroutine for each connection,
|
||||
// dispatching frames to protocols.
|
||||
func readLoop(c *Conn) (err error) {
|
||||
defer func() {
|
||||
// When the loop ends, forward the error to all protocols so
|
||||
// their next ReadPacket fails. Active chunked transfers also
|
||||
// need to cancel immediately so shutdown is not delayed.
|
||||
c.mu.Lock()
|
||||
for _, p := range c.proto {
|
||||
p.readClose(err)
|
||||
for _, pr := range p.xfers {
|
||||
pr.close(err)
|
||||
}
|
||||
}
|
||||
c.readErr = err
|
||||
c.mu.Unlock()
|
||||
}()
|
||||
|
||||
// Local cache of claimed protocols.
|
||||
protos := make(map[uint16]*Protocol)
|
||||
|
||||
for {
|
||||
// Read the next frame header.
|
||||
c.fd.SetReadDeadline(time.Now().Add(c.cfg.readIdleTimeout()))
|
||||
fsize, hdr, err := c.rw.readFrameHeader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Grab the protocol, checking the local cache before
|
||||
// interacting with the claims machinery in Conn.
|
||||
proto := protos[hdr.protocol]
|
||||
if proto == nil {
|
||||
if proto = c.waitForProtocol(hdr.protocol); proto == nil {
|
||||
return errProtocolClaimTimeout
|
||||
}
|
||||
protos[proto.id] = proto
|
||||
}
|
||||
// Wait until there is enough buffer space for the body
|
||||
// before reading it.
|
||||
err = proto.readBufSema.waitAcquire(fsize, c.cfg.readBufferWaitTimeout())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Read the body of the frame.
|
||||
c.fd.SetReadDeadline(time.Now().Add(c.cfg.readTimeout()))
|
||||
body, err := c.rw.readFrameBody(fsize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Dispatch the frame to the protocol.
|
||||
// This shouldn't block.
|
||||
if pr := proto.xfers[hdr.contextID]; pr != nil {
|
||||
if hdr.chunkStart {
|
||||
return errUnexpectedChunkStart
|
||||
}
|
||||
end, err := pr.feed(body)
|
||||
if end {
|
||||
delete(proto.xfers, hdr.contextID)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
pr, err := frameToPacket(proto, hdr, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pr.bufN > 0 {
|
||||
// Track as ongoing transfer if there is still something
|
||||
// to buffer after the initial frame.
|
||||
proto.xfers[hdr.contextID] = pr
|
||||
}
|
||||
proto.feedPacket(pr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// frameToPacket handles the initial frame for a new packet.
|
||||
func frameToPacket(proto *Protocol, hdr frameHeader, frame frameBuffer) (pr *packetReader, err error) {
|
||||
if hdr.chunkStart {
|
||||
if uint32(len(frame)) > hdr.totalSize {
|
||||
return nil, fmt.Errorf("initial chunk size %d larger than total size %d", len(frame), hdr.totalSize)
|
||||
}
|
||||
if uint32(len(frame)) < hdr.totalSize {
|
||||
return newPacketReader(proto.readBufSema, hdr.totalSize, frame), nil
|
||||
}
|
||||
}
|
||||
return newPacketReader(proto.readBufSema, uint32(len(frame)), frame), nil
|
||||
}
|
||||
|
||||
// packetReader is the payload of a packet.
|
||||
// frames are appended to it as they are read from the connection.
|
||||
type packetReader struct {
|
||||
// all of these can be accessed without locking
|
||||
// because Read is not safe for concurrent use.
|
||||
readBufs []frameBuffer
|
||||
origBufs []frameBuffer
|
||||
bufSema *bufSema
|
||||
readN uint32 // how much can still be read
|
||||
|
||||
// these fields are protected by cond.L
|
||||
cond *sync.Cond // wakes waitFrame
|
||||
newBufs []frameBuffer // buffer inbox
|
||||
err error // error inbox
|
||||
bufN uint32 // how much still needs to be buffered
|
||||
}
|
||||
|
||||
func newPacketReader(bsem *bufSema, psize uint32, initialFrame frameBuffer) *packetReader {
|
||||
pr := &packetReader{
|
||||
bufSema: bsem,
|
||||
cond: sync.NewCond(new(sync.Mutex)),
|
||||
readN: psize,
|
||||
bufN: psize,
|
||||
}
|
||||
if len(initialFrame) > 0 {
|
||||
pr.bufN -= uint32(len(initialFrame))
|
||||
pr.readBufs = []frameBuffer{initialFrame}
|
||||
pr.origBufs = []frameBuffer{initialFrame}
|
||||
}
|
||||
return pr
|
||||
}
|
||||
|
||||
func (pr *packetReader) Read(rslice []byte) (int, error) {
|
||||
if err := pr.waitFrame(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n := 0
|
||||
for i := 0; i < len(pr.readBufs) && n < len(rslice); i++ {
|
||||
nn, _ := pr.readBufs[i].Read(rslice[n:])
|
||||
n += nn
|
||||
}
|
||||
pr.afterRead(n)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (pr *packetReader) ReadByte() (byte, error) {
|
||||
if err := pr.waitFrame(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
b, _ := pr.readBufs[0].ReadByte()
|
||||
pr.afterRead(1)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// blocks until at least one frame is available,
|
||||
// then transfers any new frame buffers that have appeared
|
||||
// to readBufs/origBufs.
|
||||
func (pr *packetReader) waitFrame() error {
|
||||
if len(pr.readBufs) > 0 {
|
||||
return nil
|
||||
}
|
||||
if pr.readN == 0 {
|
||||
return io.EOF
|
||||
}
|
||||
pr.cond.L.Lock()
|
||||
defer pr.cond.L.Unlock()
|
||||
for len(pr.newBufs) == 0 && pr.err == nil {
|
||||
pr.cond.Wait()
|
||||
}
|
||||
pr.readBufs = append(pr.readBufs, pr.newBufs...)
|
||||
pr.origBufs = append(pr.origBufs, pr.newBufs...)
|
||||
pr.newBufs = pr.newBufs[:0]
|
||||
return pr.err
|
||||
}
|
||||
|
||||
// removes drained buffers and decrements the read buffer semaphore.
|
||||
func (pr *packetReader) afterRead(n int) {
|
||||
pr.readN -= uint32(n)
|
||||
drained := 0
|
||||
drainedLen := uint32(0)
|
||||
for i, buf := range pr.readBufs {
|
||||
if len(buf) != 0 {
|
||||
break
|
||||
}
|
||||
drained++
|
||||
drainedLen += uint32(len(pr.origBufs[i]))
|
||||
}
|
||||
if drained > 0 {
|
||||
pr.readBufs = pr.readBufs[:copy(pr.readBufs, pr.readBufs[drained:])]
|
||||
pr.origBufs = pr.origBufs[:copy(pr.origBufs, pr.origBufs[drained:])]
|
||||
pr.bufSema.release(drainedLen)
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *packetReader) close(err error) {
|
||||
pr.cond.L.Lock()
|
||||
pr.err = err
|
||||
pr.cond.Signal() // wake up waitFrame
|
||||
pr.cond.L.Unlock()
|
||||
}
|
||||
|
||||
func (pr *packetReader) feed(frame frameBuffer) (end bool, err error) {
|
||||
pr.cond.L.Lock()
|
||||
defer pr.cond.L.Unlock()
|
||||
if uint32(len(frame)) > pr.bufN {
|
||||
pr.err = errChunkTooLarge
|
||||
end = true
|
||||
} else {
|
||||
pr.bufN -= uint32(len(frame))
|
||||
pr.newBufs = append(pr.newBufs, frame)
|
||||
end = pr.bufN == 0
|
||||
}
|
||||
pr.cond.Signal() // wake up waitFrame
|
||||
return end, pr.err
|
||||
}
|
||||
|
||||
// represents a frame header that has been read.
|
||||
type frameHeader struct {
|
||||
protocol, contextID uint16
|
||||
chunkStart bool // initial frame of chunked message
|
||||
totalSize uint32 // total number of bytes of chunked message
|
||||
}
|
||||
|
||||
// header types for sending
|
||||
type chunkStartHeader struct {
|
||||
Protocol, ContextID uint16
|
||||
TotalSize uint32
|
||||
}
|
||||
type regularHeader struct {
|
||||
Protocol, ContextID uint16
|
||||
}
|
||||
|
||||
func decodeHeader(b []byte) (fsize uint32, h frameHeader, err error) {
|
||||
fsize = readInt24(b)
|
||||
if fsize == 0 {
|
||||
return 0, h, errors.New("zero-sized frame")
|
||||
}
|
||||
b = b[3:]
|
||||
lc, rest, err := rlp.SplitList(b)
|
||||
if err != nil {
|
||||
return fsize, h, err
|
||||
}
|
||||
// This is silly. rlp.DecodeBytes errors for data
|
||||
// after the value, so we need to pass a slice
|
||||
// containing just the value.
|
||||
hlist := b[:len(b)-len(rest)]
|
||||
|
||||
switch cnt, _ := rlp.CountValues(lc); cnt {
|
||||
case 1:
|
||||
var in struct{ Protocol uint16 }
|
||||
err = rlp.DecodeBytes(hlist, &in)
|
||||
h.protocol = in.Protocol
|
||||
case 2:
|
||||
var in regularHeader
|
||||
err = rlp.DecodeBytes(hlist, &in)
|
||||
h.protocol = in.Protocol
|
||||
h.contextID = in.ContextID
|
||||
case 3:
|
||||
var in chunkStartHeader
|
||||
err = rlp.DecodeBytes(hlist, &in)
|
||||
h.protocol = in.Protocol
|
||||
h.contextID = in.ContextID
|
||||
h.totalSize = in.TotalSize
|
||||
h.chunkStart = true
|
||||
default:
|
||||
err = fmt.Errorf("too many list elements")
|
||||
}
|
||||
return fsize, h, err
|
||||
}
|
||||
|
||||
// frameRW implements the framed wire protocol.
|
||||
type frameRW struct {
|
||||
conn io.ReadWriter
|
||||
// for reading
|
||||
headbuf []byte
|
||||
dec cipher.Stream
|
||||
ingressMacCipher cipher.Block
|
||||
ingressMac hash.Hash
|
||||
// for writing
|
||||
enc cipher.Stream
|
||||
egressMacCipher cipher.Block
|
||||
egressMac hash.Hash
|
||||
}
|
||||
|
||||
func newFrameRW(conn io.ReadWriter, ingress, egress secrets) *frameRW {
|
||||
return &frameRW{
|
||||
conn: conn,
|
||||
headbuf: make([]byte, 32),
|
||||
enc: cipher.NewCTR(mustBlockCipher("egress.encKey", egress.encKey), egress.encIV),
|
||||
egressMacCipher: mustBlockCipher("egress.macKey", egress.macKey),
|
||||
egressMac: egress.mac,
|
||||
dec: cipher.NewCTR(mustBlockCipher("ingress.encKey", ingress.encKey), ingress.encIV),
|
||||
ingressMacCipher: mustBlockCipher("ingress.macKey", ingress.macKey),
|
||||
ingressMac: ingress.mac,
|
||||
}
|
||||
}
|
||||
|
||||
func mustBlockCipher(what string, key []byte) cipher.Block {
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("invalid %s: %v", what, err))
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// sends a frame on the connection. the body buffer must placeholder bytes
|
||||
// for the encoded frame header and its MAC.
|
||||
func (rw *frameRW) sendFrame(hdr interface{}, body *frameBuffer) error {
|
||||
wbuf := *body
|
||||
usize := uint32(len(wbuf))
|
||||
if usize < frameHeaderFullSize {
|
||||
panic(fmt.Sprintf("invalid body buffer, size < %d", frameHeaderFullSize))
|
||||
}
|
||||
if usize-frameHeaderFullSize > maxUint24 {
|
||||
return errors.New("frame size overflows uint24")
|
||||
}
|
||||
|
||||
// Write and encrypt the frame header to the buffer.
|
||||
headbuf := wbuf[:frameHeaderSize]
|
||||
putInt24(headbuf, usize-frameHeaderFullSize)
|
||||
headbufAfterSize := headbuf[3:3]
|
||||
rlp.Encode(&headbufAfterSize, hdr)
|
||||
rw.enc.XORKeyStream(headbuf, headbuf)
|
||||
copy(wbuf[frameHeaderSize:], updateMAC(rw.egressMac, rw.egressMacCipher, headbuf))
|
||||
|
||||
// Write and encrypt frame data to the buffer.
|
||||
wbuf.pad16()
|
||||
rw.enc.XORKeyStream(wbuf[frameHeaderFullSize:], wbuf[frameHeaderFullSize:])
|
||||
rw.egressMac.Write(wbuf[frameHeaderFullSize:])
|
||||
fmacseed := rw.egressMac.Sum(nil)
|
||||
wbuf = append(wbuf, zero[:frameHeaderSize]...)
|
||||
copy(wbuf[len(wbuf)-16:], updateMAC(rw.egressMac, rw.egressMacCipher, fmacseed))
|
||||
|
||||
// Send the whole buffered frame on the socket.
|
||||
_, err := rw.conn.Write(wbuf)
|
||||
*body = wbuf
|
||||
return err
|
||||
}
|
||||
|
||||
func (rw *frameRW) readFrameHeader() (fsize uint32, hdr frameHeader, err error) {
|
||||
// Read the header and verify its MAC.
|
||||
if _, err := io.ReadFull(rw.conn, rw.headbuf); err != nil {
|
||||
return 0, hdr, err
|
||||
}
|
||||
shouldMAC := updateMAC(rw.ingressMac, rw.ingressMacCipher, rw.headbuf[:16])
|
||||
if !hmac.Equal(shouldMAC, rw.headbuf[16:]) {
|
||||
return 0, hdr, errors.New("bad header MAC")
|
||||
}
|
||||
rw.dec.XORKeyStream(rw.headbuf[:16], rw.headbuf[:16])
|
||||
|
||||
// Parse the header.
|
||||
fsize, hdr, err = decodeHeader(rw.headbuf)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("can't decode frame header: %v", err)
|
||||
}
|
||||
return fsize, hdr, err
|
||||
}
|
||||
|
||||
func (rw *frameRW) readFrameBody(fsize uint32) (frameBuffer, error) {
|
||||
// Grab a buffer for the content.
|
||||
var rsize = fsize
|
||||
if padding := fsize % 16; padding > 0 {
|
||||
rsize += 16 - padding // frame size rounded up to 16 byte boundary
|
||||
}
|
||||
fb := makeFrameReadBuffer(rsize + 16)
|
||||
if _, err := io.ReadFull(rw.conn, fb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Verify the body MAC and decrypt the content.
|
||||
mac, bb := fb[len(fb)-16:], fb[:len(fb)-16]
|
||||
rw.ingressMac.Write(bb)
|
||||
fmacseed := rw.ingressMac.Sum(nil)
|
||||
shouldMAC := updateMAC(rw.ingressMac, rw.ingressMacCipher, fmacseed)
|
||||
if !hmac.Equal(shouldMAC, mac) {
|
||||
return nil, errors.New("bad frame body MAC")
|
||||
}
|
||||
rw.dec.XORKeyStream(bb, bb)
|
||||
return bb[:fsize], 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(aesbuf[:0]))
|
||||
for i := range aesbuf {
|
||||
aesbuf[i] ^= seed[i]
|
||||
}
|
||||
mac.Write(aesbuf)
|
||||
return mac.Sum(nil)[:16]
|
||||
}
|
||||
|
||||
type frameBuffer []byte
|
||||
|
||||
func makeFrameWriteBuffer() *frameBuffer {
|
||||
buf := make(frameBuffer, frameHeaderFullSize, frameHeaderFullSize+staticFrameSize)
|
||||
return &buf
|
||||
}
|
||||
|
||||
func makeFrameReadBuffer(size uint32) frameBuffer {
|
||||
return make(frameBuffer, size)
|
||||
}
|
||||
|
||||
// resetForWrite truncates the buffer so it contains just enough space
|
||||
// for an encoded frame header. it must be called before writing
|
||||
// payload content for a new frame.
|
||||
func (buf *frameBuffer) resetForWrite() {
|
||||
*buf = append((*buf)[:0], zero[:frameHeaderFullSize]...)
|
||||
}
|
||||
|
||||
func (buf *frameBuffer) Write(s []byte) (n int, err error) {
|
||||
*buf = append(*buf, s...)
|
||||
return len(s), nil
|
||||
}
|
||||
|
||||
func (buf *frameBuffer) Read(s []byte) (int, error) {
|
||||
if buf == nil || len(*buf) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n := copy(s, *buf)
|
||||
*buf = (*buf)[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (buf *frameBuffer) ReadByte() (byte, error) {
|
||||
if buf == nil || len(*buf) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
b := (*buf)[0]
|
||||
*buf = (*buf)[1:]
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (buf *frameBuffer) pad16() {
|
||||
if padding := len(*buf) % 16; padding > 0 {
|
||||
*buf = append(*buf, zero[:16-padding]...)
|
||||
}
|
||||
}
|
||||
|
||||
func readInt24(b []byte) uint32 {
|
||||
return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16
|
||||
}
|
||||
|
||||
func putInt24(s []byte, v uint32) {
|
||||
s[0] = byte(v >> 16)
|
||||
s[1] = byte(v >> 8)
|
||||
s[2] = byte(v)
|
||||
}
|
||||
156
p2p/rlpx/framing_test.go
Normal file
156
p2p/rlpx/framing_test.go
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rlpx
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
func TestPacketReader(t *testing.T) {
|
||||
feed := func(pr *packetReader, n uint32) {
|
||||
for sent := uint32(0); sent < n; {
|
||||
chunk := randomChunk(sent, n-sent)
|
||||
sent += uint32(len(chunk))
|
||||
if err := pr.bufSema.waitAcquire(uint32(len(chunk)), 200*time.Millisecond); err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
end, err := pr.feed(chunk)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("pr.feed returned error: %v", err))
|
||||
}
|
||||
if end && sent != n {
|
||||
panic(fmt.Errorf("pr.feed returned end=true with %d/%d bytes of input", sent, n))
|
||||
}
|
||||
}
|
||||
}
|
||||
for size := uint32(1); size < 2<<17; size *= 2 {
|
||||
sem := newBufSema(staticFrameSize * 2)
|
||||
pr := newPacketReader(sem, size, nil)
|
||||
go feed(pr, size)
|
||||
if err := checkSeq(pr, size); err != nil {
|
||||
t.Fatalf("size %d: read error: %v", size, err)
|
||||
}
|
||||
if val := sem.get(); val != staticFrameSize*2 {
|
||||
t.Fatalf("size %d: wrong semaphore value after reading all data. got %d, want %d", size, val, staticFrameSize*2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkSeq(r io.Reader, size uint32) error {
|
||||
content := make([]byte, size)
|
||||
if _, err := io.ReadFull(r, content); err != nil {
|
||||
return err
|
||||
}
|
||||
for i, b := range content {
|
||||
if b != byte(i) {
|
||||
return fmt.Errorf("mismatch at index %d: have %d, want %d", i, b, byte(i))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func randomChunk(seed uint32, maxSize uint32) frameBuffer {
|
||||
size := rand.Uint32()%staticFrameSize + 1
|
||||
if size > maxSize {
|
||||
size = maxSize
|
||||
}
|
||||
chunk := make(frameBuffer, size)
|
||||
for i := range chunk {
|
||||
chunk[i] = byte(uint32(i) + seed)
|
||||
}
|
||||
return chunk
|
||||
}
|
||||
|
||||
/*
|
||||
func TestFrameFakeGolden(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
hash := fakeHash{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 := newFrameRW(buf, secrets{
|
||||
AES: crypto.Sha3(),
|
||||
MAC: crypto.Sha3(),
|
||||
IngressMAC: hash,
|
||||
EgressMAC: hash,
|
||||
})
|
||||
|
||||
golden := hexb(`
|
||||
00828ddae471818bb0bfa6b551d1cb42
|
||||
01010101010101010101010101010101
|
||||
ba628a4ba590cb43f7848f41c4382885
|
||||
01010101010101010101010101010101
|
||||
`)
|
||||
body := hexb(`08C401020304`)
|
||||
|
||||
// Check sendFrame. This encodes the frame to buf.
|
||||
fwbuf := makeFrameWriteBuffer()
|
||||
fwbuf.Write(body)
|
||||
if err := rw.sendFrame(regularHeader{0, 0}, fwbuf); err != nil {
|
||||
t.Fatalf("sendFrame error: %v", err)
|
||||
}
|
||||
written := buf.Bytes()
|
||||
if !bytes.Equal(written, golden) {
|
||||
t.Fatalf("output mismatch:\n got: %x\n want: %x", written, golden)
|
||||
}
|
||||
|
||||
// Check readFrame. It reads the message encoded by sendFrame, which
|
||||
// must be equivalent to the golden message above.
|
||||
fsize, hdr, err := rw.readFrameHeader()
|
||||
if err != nil {
|
||||
t.Fatalf("readFrameHeader error: %v", err)
|
||||
}
|
||||
if (hdr != frameHeader{}) {
|
||||
t.Errorf("read header mismatch: got %v, want zero header", hdr)
|
||||
}
|
||||
if int(fsize) != len(body) {
|
||||
t.Errorf("read size mismatch: got %d, want %d", fsize, len(body))
|
||||
}
|
||||
// if !bytes.Equal(bodybuf.Bytes(), body) {
|
||||
// t.Errorf("read body mismatch:\ngot %x\nwant %x", bodybuf.Bytes(), body)
|
||||
// }
|
||||
}
|
||||
|
||||
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 hexb(str string) []byte {
|
||||
unspace := strings.NewReplacer("\n", "", "\t", "", " ", "")
|
||||
b, err := hex.DecodeString(unspace.Replace(str))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("invalid hex string: %q", str))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func hexkey(str string) *ecdsa.PrivateKey {
|
||||
return crypto.ToECDSA(hexb(str))
|
||||
}
|
||||
339
p2p/rlpx/handshake.go
Normal file
339
p2p/rlpx/handshake.go
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rlpx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
maxUint24 = ^uint32(0) >> 8
|
||||
kdfSharedDataPrefix = "rlpx handshake\x05"
|
||||
|
||||
// Handshake Sizes
|
||||
sskLen = 16 // ecies.MaxSharedKeyLength(pubKey) / 2
|
||||
sigLen = 65 // elliptic S256
|
||||
pubLen = 64 // 512 bit pubkey in uncompressed representation without format byte
|
||||
shaLen = 32 // hash length (for nonce etc)
|
||||
nonceLen = 24
|
||||
authMsgLen = sigLen + shaLen + pubLen + shaLen + 1
|
||||
authRespLen = pubLen + shaLen + 1
|
||||
eciesBytes = 65 + 16 + 32
|
||||
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
|
||||
)
|
||||
|
||||
var zero [32]byte
|
||||
|
||||
// encHandshake contains the state of the encryption handshake.
|
||||
type handshake struct {
|
||||
conn io.ReadWriter
|
||||
initiator bool
|
||||
localPrivKey *ecdsa.PrivateKey
|
||||
remotePub *ecies.PublicKey // remote-pubk
|
||||
initNonce, respNonce []byte // nonce
|
||||
randomPrivKey *ecies.PrivateKey // ecdhe-random
|
||||
remoteRandomPub *ecies.PublicKey // ecdhe-random-pubk
|
||||
}
|
||||
|
||||
// secrets represents the derived secrets for authenticated encryption.
|
||||
type secrets struct {
|
||||
encKey, encIV, macKey []byte
|
||||
mac hash.Hash
|
||||
}
|
||||
|
||||
type handshakeRandSource interface {
|
||||
generateNonce(b []byte) error
|
||||
generateKey() (*ecies.PrivateKey, error)
|
||||
}
|
||||
|
||||
type realRandSource struct{}
|
||||
|
||||
func (realRandSource) generateNonce(b []byte) error {
|
||||
_, err := io.ReadFull(rand.Reader, b)
|
||||
return err
|
||||
}
|
||||
|
||||
func (realRandSource) generateKey() (*ecies.PrivateKey, error) {
|
||||
return ecies.GenerateKey(rand.Reader, secp256k1.S256(), nil)
|
||||
}
|
||||
|
||||
func (h *handshake) deriveSecrets(forceV4 bool, auth, authResp []byte) (vsn uint, ingress, egress secrets, err error) {
|
||||
remoteNonce := h.initNonce
|
||||
if h.initiator {
|
||||
remoteNonce = h.respNonce
|
||||
}
|
||||
remoteVersion := binary.BigEndian.Uint64(remoteNonce[nonceLen:])
|
||||
if forceV4 || remoteVersion > 255 {
|
||||
return h.deriveSecretsV4(auth, authResp)
|
||||
}
|
||||
return h.deriveSecretsV5()
|
||||
}
|
||||
|
||||
func (h *handshake) deriveSecretsV4(auth, authResp []byte) (vsn uint, ingress, egress secrets, err error) {
|
||||
vsn = 4
|
||||
ecdheSecret, err := h.randomPrivKey.GenerateShared(h.remoteRandomPub, sskLen, sskLen)
|
||||
if err != nil {
|
||||
return vsn, ingress, egress, err
|
||||
}
|
||||
sharedSecret := crypto.Sha3(ecdheSecret, crypto.Sha3(h.respNonce, h.initNonce))
|
||||
aesSecret := crypto.Sha3(ecdheSecret, sharedSecret)
|
||||
macSecret := crypto.Sha3(ecdheSecret, aesSecret)
|
||||
|
||||
egress = secrets{encKey: aesSecret, encIV: zero[:16], macKey: macSecret}
|
||||
egress.mac = sha3.NewKeccak256()
|
||||
egress.mac.Write(xor(h.initNonce, macSecret))
|
||||
egress.mac.Write(authResp)
|
||||
ingress = secrets{encKey: aesSecret, encIV: zero[:16], macKey: macSecret}
|
||||
ingress.mac = sha3.NewKeccak256()
|
||||
ingress.mac.Write(xor(h.respNonce, macSecret))
|
||||
ingress.mac.Write(auth)
|
||||
if h.initiator {
|
||||
ingress, egress = egress, ingress
|
||||
}
|
||||
return vsn, ingress, egress, nil
|
||||
}
|
||||
|
||||
func (h *handshake) deriveSecretsV5() (vsn uint, ingress, egress secrets, err error) {
|
||||
vsn = 5
|
||||
ecdheSecret, err := h.randomPrivKey.GenerateShared(h.remoteRandomPub, sskLen, sskLen)
|
||||
if err != nil {
|
||||
return vsn, ingress, egress, err
|
||||
}
|
||||
initPub := exportPubkey(h.remotePub)
|
||||
respPub := elliptic.Marshal(h.localPrivKey.Curve, h.localPrivKey.X, h.localPrivKey.Y)[1:]
|
||||
if h.initiator {
|
||||
initPub, respPub = respPub, initPub
|
||||
}
|
||||
sharedData := make([]byte, len(kdfSharedDataPrefix)+nonceLen*2+pubLen*2)
|
||||
n := copy(sharedData, kdfSharedDataPrefix)
|
||||
n += copy(sharedData[n:], h.initNonce[:nonceLen])
|
||||
n += copy(sharedData[n:], h.respNonce[:nonceLen])
|
||||
n += copy(sharedData[n:], initPub)
|
||||
n += copy(sharedData[n:], respPub)
|
||||
derived, err := ecies.ConcatKDF(sha3.NewKeccak256(), ecdheSecret, sharedData, 160)
|
||||
if err != nil {
|
||||
return vsn, ingress, egress, err
|
||||
}
|
||||
|
||||
ingress = secrets{encKey: derived[0:32], encIV: derived[64:80], macKey: derived[96:128]}
|
||||
ingress.mac = sha3.NewKeccak256()
|
||||
ingress.mac.Write(ingress.macKey)
|
||||
egress = secrets{encKey: derived[32:64], encIV: derived[80:96], macKey: derived[128:160]}
|
||||
egress.mac = sha3.NewKeccak256()
|
||||
egress.mac.Write(egress.macKey)
|
||||
if h.initiator {
|
||||
ingress, egress = egress, ingress
|
||||
}
|
||||
return vsn, ingress, egress, nil
|
||||
}
|
||||
|
||||
func (h *handshake) ecdhShared(prv *ecdsa.PrivateKey) ([]byte, error) {
|
||||
return ecies.ImportECDSA(prv).GenerateShared(h.remotePub, sskLen, sskLen)
|
||||
}
|
||||
|
||||
func (c *Conn) fillHandshake(nonce *[]byte, key **ecies.PrivateKey) (err error) {
|
||||
*nonce = make([]byte, shaLen)
|
||||
if c.cfg.ForceV4 {
|
||||
err = c.handshakeRand.generateNonce(*nonce)
|
||||
} else {
|
||||
binary.BigEndian.PutUint64((*nonce)[nonceLen:], 5)
|
||||
err = c.handshakeRand.generateNonce((*nonce)[:nonceLen])
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*key, err = c.handshakeRand.generateKey()
|
||||
return err
|
||||
}
|
||||
|
||||
// initiatorHandshake negotiates connection secrets on conn.
|
||||
// it should be called on the dialing end of the connection.
|
||||
// prv is the local client's private key.
|
||||
func (c *Conn) initiatorHandshake() (vsn uint, ingress, egress secrets, err error) {
|
||||
h := &handshake{initiator: true, localPrivKey: c.cfg.Key, remotePub: ecies.ImportECDSAPublic(c.remoteID)}
|
||||
if err := c.fillHandshake(&h.initNonce, &h.randomPrivKey); err != nil {
|
||||
return 0, ingress, egress, err
|
||||
}
|
||||
auth, err := h.authMsg()
|
||||
if err != nil {
|
||||
return 0, ingress, egress, err
|
||||
}
|
||||
if _, err := c.fd.Write(auth); err != nil {
|
||||
return 0, ingress, egress, err
|
||||
}
|
||||
|
||||
response := make([]byte, encAuthRespLen)
|
||||
if _, err := io.ReadFull(c.fd, response); err != nil {
|
||||
return 0, ingress, egress, err
|
||||
}
|
||||
if err := h.decodeAuthResp(response); err != nil {
|
||||
return 0, ingress, egress, err
|
||||
}
|
||||
return h.deriveSecrets(c.cfg.ForceV4, auth, response)
|
||||
}
|
||||
|
||||
// authMsg creates an encrypted initiator handshake message.
|
||||
func (h *handshake) authMsg() ([]byte, error) {
|
||||
staticSharedSecret, err := h.ecdhShared(h.localPrivKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// sign static-shared-secret^nonce
|
||||
signed := xor(staticSharedSecret, h.initNonce)
|
||||
signature, err := crypto.Sign(signed, h.randomPrivKey.ExportECDSA())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// encode auth message: sig || 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(&h.localPrivKey.PublicKey)[1:])
|
||||
n += copy(msg[n:], h.initNonce)
|
||||
msg[n] = 0
|
||||
// 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 *handshake) decodeAuthResp(auth []byte) error {
|
||||
msg, err := crypto.Decrypt(h.localPrivKey, 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
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// recipientHandshake negotiates connection secrets on conn.
|
||||
// it should be called on the listening side of the connection.
|
||||
// prv is the local client's private key.
|
||||
func (c *Conn) recipientHandshake() (vsn uint, remoteID *ecdsa.PublicKey, ingress, egress secrets, err error) {
|
||||
auth := make([]byte, encAuthMsgLen)
|
||||
if _, err := io.ReadFull(c.fd, auth); err != nil {
|
||||
return 0, nil, ingress, egress, err
|
||||
}
|
||||
h := &handshake{localPrivKey: c.cfg.Key}
|
||||
if err := h.decodeAuthMsg(auth); err != nil {
|
||||
return 0, nil, ingress, egress, fmt.Errorf("invalid auth: %v", err)
|
||||
}
|
||||
if err := c.fillHandshake(&h.respNonce, &h.randomPrivKey); err != nil {
|
||||
return 0, nil, ingress, egress, err
|
||||
}
|
||||
|
||||
resp, err := h.authResp()
|
||||
if err != nil {
|
||||
return 0, nil, ingress, egress, fmt.Errorf("can't create auth resp: %v", err)
|
||||
}
|
||||
if _, err := c.fd.Write(resp); err != nil {
|
||||
return 0, nil, ingress, egress, err
|
||||
}
|
||||
vsn, ingress, egress, err = h.deriveSecrets(c.cfg.ForceV4, auth, resp)
|
||||
if h.remotePub != nil {
|
||||
remoteID = h.remotePub.ExportECDSA()
|
||||
}
|
||||
return vsn, remoteID, ingress, egress, err
|
||||
}
|
||||
|
||||
func (h *handshake) decodeAuthMsg(auth []byte) error {
|
||||
msg, err := crypto.Decrypt(h.localPrivKey, auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// signature || sha3(ecdhe-random-pubk) || pubk || nonce || token-flag
|
||||
h.initNonce = msg[authMsgLen-shaLen-1 : authMsgLen-1]
|
||||
h.remotePub, err = importPublicKey(msg[sigLen+shaLen : sigLen+shaLen+pubLen])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid remote identity: %v", err)
|
||||
}
|
||||
// recover remote random pubkey from signed message.
|
||||
staticSharedSecret, err := h.ecdhShared(h.localPrivKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signed := xor(staticSharedSecret, h.initNonce)
|
||||
remoteRandomPub, err := secp256k1.RecoverPubkey(signed, msg[:sigLen])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// validate the sha3 of recovered pubkey
|
||||
remoteRandomPubMAC := msg[sigLen : sigLen+shaLen]
|
||||
shaRemoteRandomPub := crypto.Sha3(remoteRandomPub[1:])
|
||||
if !bytes.Equal(remoteRandomPubMAC, shaRemoteRandomPub) {
|
||||
return fmt.Errorf("recovered pubkey hash mismatch")
|
||||
}
|
||||
h.remoteRandomPub, _ = importPublicKey(remoteRandomPub)
|
||||
return nil
|
||||
}
|
||||
|
||||
// authResp generates the encrypted authentication response message.
|
||||
func (h *handshake) authResp() ([]byte, error) {
|
||||
// E(remote-pubk, ecdhe-random-pubk || nonce || token-flag)
|
||||
resp := make([]byte, authRespLen)
|
||||
n := copy(resp, exportPubkey(&h.randomPrivKey.PublicKey))
|
||||
n += copy(resp[n:], h.respNonce)
|
||||
resp[n] = 0
|
||||
return ecies.Encrypt(rand.Reader, h.remotePub, resp, nil, nil)
|
||||
}
|
||||
|
||||
// importPublicKey unmarshals 512 bit public keys.
|
||||
func importPublicKey(pubKey []byte) (*ecies.PublicKey, error) {
|
||||
var pubKey65 []byte
|
||||
switch len(pubKey) {
|
||||
case 64:
|
||||
// add 'uncompressed key' flag
|
||||
pubKey65 = append([]byte{0x04}, pubKey...)
|
||||
case 65:
|
||||
pubKey65 = pubKey
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid public key length %v (expect 64/65)", len(pubKey))
|
||||
}
|
||||
// TODO: fewer pointless conversions
|
||||
return ecies.ImportECDSAPublic(crypto.ToECDSAPub(pubKey65)), nil
|
||||
}
|
||||
|
||||
func exportPubkey(pub *ecies.PublicKey) []byte {
|
||||
if pub == nil {
|
||||
panic("nil pubkey")
|
||||
}
|
||||
return elliptic.Marshal(pub.Curve, pub.X, pub.Y)[1:]
|
||||
}
|
||||
|
||||
func xor(one, other []byte) (xor []byte) {
|
||||
xor = make([]byte, len(one))
|
||||
for i := 0; i < len(one); i++ {
|
||||
xor[i] = one[i] ^ other[i]
|
||||
}
|
||||
return xor
|
||||
}
|
||||
255
p2p/rlpx/handshake_test.go
Normal file
255
p2p/rlpx/handshake_test.go
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rlpx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||
)
|
||||
|
||||
func init() {
|
||||
spew.Config.Indent = "\t"
|
||||
}
|
||||
|
||||
func TestSharedSecret(t *testing.T) {
|
||||
prv0, _ := crypto.GenerateKey()
|
||||
pub0 := &prv0.PublicKey
|
||||
prv1, _ := crypto.GenerateKey()
|
||||
pub1 := &prv1.PublicKey
|
||||
ss0, err := ecies.ImportECDSA(prv0).GenerateShared(ecies.ImportECDSAPublic(pub1), sskLen, sskLen)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ss1, err := ecies.ImportECDSA(prv1).GenerateShared(ecies.ImportECDSAPublic(pub0), sskLen, sskLen)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(ss0, ss1) {
|
||||
t.Errorf("secret mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
// This test does random V5 handshakes and compares the secrets.
|
||||
func TestHandshake(t *testing.T) {
|
||||
for i := 0; i < 10 && !t.Failed(); i++ {
|
||||
start := time.Now()
|
||||
doTestHandshake(t)
|
||||
t.Logf("%d %v\n", i+1, time.Since(start))
|
||||
}
|
||||
}
|
||||
|
||||
func doTestHandshake(t *testing.T) {
|
||||
var (
|
||||
prv0, _ = crypto.GenerateKey()
|
||||
prv1, _ = crypto.GenerateKey()
|
||||
p1, p2 = net.Pipe()
|
||||
c1 = Server(p1, &Config{Key: prv0})
|
||||
c2 = Client(p2, &prv0.PublicKey, &Config{Key: prv1})
|
||||
)
|
||||
shake := func(conn *Conn, rkey *ecdsa.PublicKey) error {
|
||||
defer conn.Close()
|
||||
if err := conn.Handshake(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !reflect.DeepEqual(conn.RemoteID(), rkey) {
|
||||
return fmt.Errorf("remote ID mismatch: got %v, want: %v", conn.RemoteID(), rkey)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
run(t, rig{
|
||||
"initiator": func() error { return shake(c1, &prv1.PublicKey) },
|
||||
"recipient": func() error { return shake(c2, &prv0.PublicKey) },
|
||||
})
|
||||
|
||||
// compare derived secrets
|
||||
if !reflect.DeepEqual(c1.rw.egressMac, c2.rw.ingressMac) {
|
||||
t.Errorf("egress mac mismatch:\n c1.rw: %#v\n c2.rw: %#v", c1.rw.egressMac, c2.rw.ingressMac)
|
||||
}
|
||||
if !reflect.DeepEqual(c1.rw.ingressMac, c2.rw.egressMac) {
|
||||
t.Errorf("ingress mac mismatch:\n c1.rw: %#v\n c2.rw: %#v", c1.rw.ingressMac, c2.rw.egressMac)
|
||||
}
|
||||
if !reflect.DeepEqual(c1.rw.enc, c2.rw.dec) {
|
||||
t.Errorf("enc cipher mismatch:\n c1.rw: %#v\n c2.rw: %#v", c1.rw.enc, c2.rw.dec)
|
||||
}
|
||||
if !reflect.DeepEqual(c1.rw.dec, c2.rw.enc) {
|
||||
t.Errorf("dec cipher mismatch:\n c1.rw: %#v\n c2.rw: %#v", c1.rw.dec, c2.rw.enc)
|
||||
}
|
||||
}
|
||||
|
||||
// This test runs initator/recipient against each other for each test vector.
|
||||
func TestHandshakeTV(t *testing.T) {
|
||||
for i, ht := range handshakeTV {
|
||||
p1, p2 := net.Pipe()
|
||||
run(t, rig{
|
||||
"initiator": func() error { return checkInitiator(p1, ht) },
|
||||
"recipient": func() error { return checkRecipient(p2, ht) },
|
||||
})
|
||||
if t.Failed() {
|
||||
t.Fatalf("failed test case %d:\n%s", i, spew.Sdump(ht))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This test runs the encrypted auth packets from the test vectors against
|
||||
// the recipient code.
|
||||
func TestHandshakePacketsRecipientTV(t *testing.T) {
|
||||
for i, ht := range handshakeTV {
|
||||
p1, p2 := net.Pipe()
|
||||
run(t, rig{
|
||||
"recipient": func() error {
|
||||
defer p1.Close()
|
||||
return checkRecipient(p1, ht)
|
||||
},
|
||||
"auth packet send": func() error {
|
||||
_, err := p2.Write(ht.encAuth)
|
||||
return err
|
||||
},
|
||||
"authResp packet recv": func() error {
|
||||
ioutil.ReadAll(p2)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if t.Failed() {
|
||||
t.Fatalf("failed test case %d:\n%s", i, spew.Sdump(ht))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This test runs the encrypted authResp packets from the test vectors against
|
||||
// the initiator code.
|
||||
func TestHandshakePacketsInitiatorTV(t *testing.T) {
|
||||
for i, ht := range handshakeTV {
|
||||
p1, p2 := net.Pipe()
|
||||
run(t, rig{
|
||||
"initiator": func() error {
|
||||
defer p1.Close()
|
||||
return checkInitiator(p1, ht)
|
||||
},
|
||||
"authResp packet send": func() error {
|
||||
_, err := p2.Write(ht.encAuthResp)
|
||||
return err
|
||||
},
|
||||
"auth packet recv": func() error {
|
||||
ioutil.ReadAll(p2)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if t.Failed() {
|
||||
t.Fatalf("failed test case %d:\n%s", i, spew.Sdump(ht))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This test checks that secrets.mac is initialized correctly.
|
||||
func TestHandshakeDeriveMacTV(t *testing.T) {
|
||||
for i, ht := range handshakeTV {
|
||||
h := handshake{
|
||||
initiator: true,
|
||||
localPrivKey: ht.initiator.Key,
|
||||
remotePub: ecies.ImportECDSAPublic(&ht.recipient.Key.PublicKey),
|
||||
initNonce: ht.initiatorNonce,
|
||||
respNonce: ht.recipientNonce,
|
||||
randomPrivKey: ecies.ImportECDSA(ht.initiatorEphemeralKey),
|
||||
remoteRandomPub: ecies.ImportECDSAPublic(&ht.recipientEphemeralKey.PublicKey),
|
||||
}
|
||||
vsn, ingress, egress, err := h.deriveSecrets(ht.initiator.ForceV4, ht.encAuth, ht.encAuthResp)
|
||||
if err != nil {
|
||||
t.Error("deriveSecrets error: %v", err)
|
||||
}
|
||||
if sum := ingress.mac.Sum(nil); !bytes.Equal(sum, ht.initiatorIngressMacDigest) {
|
||||
t.Errorf("ingress mac mismatch: got %x, want %x", sum, ht.initiatorIngressMacDigest)
|
||||
}
|
||||
if sum := egress.mac.Sum(nil); !bytes.Equal(sum, ht.initiatorEgressMacDigest) {
|
||||
t.Errorf("egress mac mismatch: got %x, want %x", sum, ht.initiatorEgressMacDigest)
|
||||
}
|
||||
if err := ht.checkSecrets(vsn, nil, ingress, egress); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if t.Failed() {
|
||||
t.Fatalf("failed test case %d:\n%s", i, spew.Sdump(ht))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkInitiator(pipe net.Conn, ht handshakeTest) error {
|
||||
remotePub := &ht.recipient.Key.PublicKey
|
||||
conn := Client(pipe, remotePub, ht.initiator)
|
||||
conn.handshakeRand = fakeRandSource{key: ht.initiatorEphemeralKey, nonce: ht.initiatorNonce}
|
||||
vsn, ingress, egress, err := conn.initiatorHandshake()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ht.checkSecrets(vsn, nil, ingress, egress)
|
||||
}
|
||||
|
||||
func checkRecipient(pipe net.Conn, ht handshakeTest) error {
|
||||
conn := Server(pipe, ht.recipient)
|
||||
conn.handshakeRand = fakeRandSource{key: ht.recipientEphemeralKey, nonce: ht.recipientNonce}
|
||||
vsn, remoteID, ingress, egress, err := conn.recipientHandshake()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ht.checkSecrets(vsn, remoteID, egress, ingress)
|
||||
}
|
||||
|
||||
func (ht handshakeTest) checkSecrets(vsn uint, remoteID *ecdsa.PublicKey, ingress, egress secrets) error {
|
||||
if remoteID != nil && !reflect.DeepEqual(remoteID, &ht.initiator.Key.PublicKey) {
|
||||
return fmt.Errorf("remoteID mismatch:\ngot %x\nwant %x",
|
||||
crypto.FromECDSAPub(remoteID), crypto.FromECDSAPub(&ht.initiator.Key.PublicKey))
|
||||
}
|
||||
if vsn != ht.negotiatedVersion {
|
||||
return fmt.Errorf("version mismatch: got %d, want %d", vsn, ht.negotiatedVersion)
|
||||
}
|
||||
// Remove the MACs so secrets can be compared with DeepEqual.
|
||||
ingress.mac, egress.mac = nil, nil
|
||||
if !reflect.DeepEqual(ingress, ht.initiatorIngressSecrets) {
|
||||
return fmt.Errorf("initiatorIngressSecrets mismatch:\ngot %swant %s",
|
||||
spew.Sdump(ingress), spew.Sdump(ht.initiatorEgressSecrets))
|
||||
}
|
||||
if !reflect.DeepEqual(egress, ht.initiatorEgressSecrets) {
|
||||
return fmt.Errorf("initiatorEgressSecrets mismatch:\ngot %swant %s",
|
||||
spew.Sdump(egress), spew.Sdump(ht.initiatorIngressSecrets))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakeRandSource struct {
|
||||
key *ecdsa.PrivateKey
|
||||
nonce []byte
|
||||
}
|
||||
|
||||
func (ht fakeRandSource) generateNonce(b []byte) error {
|
||||
if len(b) > len(ht.nonce) {
|
||||
panic(fmt.Sprintf("requested %d bytes of nonce data, have %d", len(b), len(ht.nonce)))
|
||||
}
|
||||
copy(b, ht.nonce)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ht fakeRandSource) generateKey() (*ecies.PrivateKey, error) {
|
||||
return ecies.ImportECDSA(ht.key), nil
|
||||
}
|
||||
254
p2p/rlpx/handshake_tv_test.go
Normal file
254
p2p/rlpx/handshake_tv_test.go
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rlpx
|
||||
|
||||
import "crypto/ecdsa"
|
||||
|
||||
type handshakeTest struct {
|
||||
// Inputs
|
||||
initiator, recipient *Config
|
||||
initiatorEphemeralKey, recipientEphemeralKey *ecdsa.PrivateKey
|
||||
initiatorNonce, recipientNonce []byte
|
||||
|
||||
// Derived Values: These must match exactly in all Test*TV
|
||||
// functions and do not depend on any random values apart from the
|
||||
// ones above.
|
||||
negotiatedVersion uint
|
||||
initiatorEgressSecrets, initiatorIngressSecrets secrets
|
||||
|
||||
// Encrypted Packets: We can't check them directly because both
|
||||
// encryption and signing introduce random values.
|
||||
// TestHandshakePacketsRecipientTV and
|
||||
// TestHandshakePacketsInitiatorTV check that each 'side' accepts
|
||||
// the other packet and computes the right secrets.
|
||||
encAuth, encAuthResp []byte
|
||||
|
||||
// Digests of the empty string created with each MAC hash. These
|
||||
// are checked TestHandshakeDeriveMacTV with the packets above
|
||||
// because RLPx V4 includes the ciphertext in the hash.
|
||||
initiatorIngressMacDigest, initiatorEgressMacDigest []byte
|
||||
}
|
||||
|
||||
var handshakeTV = []handshakeTest{
|
||||
// initiator V5, recipient V5
|
||||
{
|
||||
initiator: &Config{
|
||||
Key: hexkey("5e173f6ac3c669587538e7727cf19b782a4f2fda07c1eaa662c593e5e85e3051"),
|
||||
ForceV4: false,
|
||||
},
|
||||
recipient: &Config{
|
||||
Key: hexkey("c45f950382d542169ea207959ee0220ec1491755abe405cd7498d6b16adb6df8"),
|
||||
ForceV4: false,
|
||||
},
|
||||
initiatorEphemeralKey: hexkey("19c2185f4f40634926ebed3af09070ca9e029f2edd5fae6253074896205f5f6c"),
|
||||
recipientEphemeralKey: hexkey("d25688cf0ab10afa1a0e2dba7853ed5f1e5bf1c631757ed4e103b593ff3f5620"),
|
||||
initiatorNonce: hexb("cd26fecb93657d1cd9e9eaf4f8be720b56dd1d39f190c4e10000000000000005"),
|
||||
recipientNonce: hexb("f37ec61d84cea03dcc5e8385db93248584e8af4b4d1c832d0000000000000005"),
|
||||
|
||||
encAuth: hexb(`
|
||||
04f0849817e9483c39b1eead6f5a0dfb09cbc4c43151172c2549c5b07c9364f7
|
||||
853cbae79e7d2a3b9a79d042ddbdf1d95db1f8c7428989123afb02fcad93bfac
|
||||
413ad9a9f2bf9b2a621a09fe804229f31f9a71ae6776f10bc8d13bb372fa4af5
|
||||
9a39fd0ae546cb5fce9fd55d59b13e6cbcc234421ab089f1f08932d4622e460c
|
||||
b0f63b3375b8388e25f84db55a415c764386e00bc19da675baf8e643f48d14c9
|
||||
89432062ed3495943bb6b3f46e8a5011edc3648a0396bccbaa0fe164bd2b8919
|
||||
df542da5fd34f24e2d5b84082a9be5fce2e17625d90078eafa8d3125314553e7
|
||||
008dedd9fd5d9d082811a08581d596f7605eba7500aafd2f3c5c8b8cfdce2ac3
|
||||
417559c3d52d6d326a832f0c43077d0697c06db24a5a28c1d67033ecda5d4ff8
|
||||
74831427bcda3ef422b8cc9f34b1eabf39607a
|
||||
`),
|
||||
encAuthResp: hexb(`
|
||||
0496117783823744f0f58cd952ed34a1866e4ade1a2d66cdfd041f877c4e4216
|
||||
d45645ad1dedee1d54d5a767d87231fbadbc6dcb2b48c75b3ab46cb18b224a7f
|
||||
cd3d9619e03d24813aaa0cc37adb32aa2fa7fbc13aa1fbc01d24d402715fe213
|
||||
62a457a986ec649983f0ff81f5f207799849bce8061dab17b491ac7f0090c426
|
||||
f7e63c31f11917e8e33c65d74bd2094435e73ffab1dfbaff368de079244d4ebd
|
||||
7f8b542f7081756a1e94b4ed26fb1c3bddabbd642064a15ad597a4f63894ea31
|
||||
13cab7533eec3b8ae163f8ebd61d7bac71e4
|
||||
`),
|
||||
|
||||
negotiatedVersion: 5,
|
||||
initiatorIngressSecrets: secrets{
|
||||
encKey: hexb("5d268dbeede1c3ce4e7cd1f900543f671467284d53c6f6fd6b284789652bd1f6"),
|
||||
encIV: hexb("e5703e8952a6eafcdb2c1940c7615843"),
|
||||
macKey: hexb("09726cd8b6414cb1f5858b0339badeeed377a48cbe5f3d28a4f74ae41e610c4d"),
|
||||
},
|
||||
initiatorEgressSecrets: secrets{
|
||||
encKey: hexb("40bfcb0da6d30512f57187f61b4816fcdc9aaaf107184e29467fe6f6ccefe4a7"),
|
||||
encIV: hexb("0e906055c0ca86940626d0fde4f3a9c2"),
|
||||
macKey: hexb("c676534122bd3a555ca8f2d924b63222b1b5b5efccb7b37a52795c1c49450fdc"),
|
||||
},
|
||||
initiatorIngressMacDigest: hexb("de72d7161bc7a9ddda4a70a48d08eda55d6fc4d90ef80a4b6645f81d6373e66b"),
|
||||
initiatorEgressMacDigest: hexb("47bb77bff168de73c7ae34473f4d085abdf97cce7e01cab6ee3a4f69a021645f"),
|
||||
},
|
||||
|
||||
// initiator V5, recipient V4
|
||||
{
|
||||
initiator: &Config{
|
||||
Key: hexkey("5e173f6ac3c669587538e7727cf19b782a4f2fda07c1eaa662c593e5e85e3051"),
|
||||
ForceV4: false,
|
||||
},
|
||||
recipient: &Config{
|
||||
Key: hexkey("c45f950382d542169ea207959ee0220ec1491755abe405cd7498d6b16adb6df8"),
|
||||
ForceV4: true,
|
||||
},
|
||||
initiatorEphemeralKey: hexkey("19c2185f4f40634926ebed3af09070ca9e029f2edd5fae6253074896205f5f6c"),
|
||||
recipientEphemeralKey: hexkey("d25688cf0ab10afa1a0e2dba7853ed5f1e5bf1c631757ed4e103b593ff3f5620"),
|
||||
initiatorNonce: hexb("cd26fecb93657d1cd9e9eaf4f8be720b56dd1d39f190c4e10000000000000005"),
|
||||
recipientNonce: hexb("f37ec61d84cea03dcc5e8385db93248584e8af4b4d1c832d8c7453c0089687a7"),
|
||||
|
||||
encAuth: hexb(`
|
||||
04e96a95f95ca7188aedd8abcf58f5f99efc7efed406923084655ce9b197cfe1
|
||||
0921159e105d1cf335e2f4755813598b868784130f4f0ae0bf1776b70d9e3061
|
||||
9205869af0963e0503133dabd7e5ed8ddf531ac3ac4d243f131252b8fd5a1638
|
||||
7296236b9b6b23432dcf02064284522690b8a07cf7e8cd9935409693203f31b9
|
||||
4fe5b2fba7e49a90711597e13ab318230a5fa3c89965569c9da21aa20686df25
|
||||
a39bd3393c87ae0f9d11bd6b270480f3544be771fdca8fd2cebb161cc508ee38
|
||||
f1eedb793d75f7e081fdf837be699fee2af1f00e2e8924d2ec5c64dabe445d0f
|
||||
14caae3c20583252fa8adace052a5f5832ebb957d3324cf12d27232934193806
|
||||
a4bfdf9adc3a0921e0bdc7c7457cf35b5d99e729d7e0fd9aa09ab1217ff5ceca
|
||||
768fb1fc636499eecab58bb01f46eea652ccbd
|
||||
`),
|
||||
encAuthResp: hexb(`
|
||||
04d0f8e56113ee9402ab4fed101fe03842f265e13e9bb76af2ac1ffba11d8892
|
||||
524e59f1906eb2e6e35f774ccb3449d2f5084b96063e668fb73d90a94b0114dc
|
||||
c14364a087f270adc65421741d87c492eafe1ca3b86a76313d026564e1abbb48
|
||||
6d03727c9baf8d0314af54296ea829aa086174a7836113f1dd420750af98f0b8
|
||||
802940ab16af05421d6b812054b285fdf1ae82ae0c08f1dbee3d60691979e8bd
|
||||
0b31599ac47138ba24d404699ae4558fec8bb94f120e63362e4b94a50894021e
|
||||
70e69101820018472823a48bc0d61c617c21
|
||||
`),
|
||||
|
||||
negotiatedVersion: 4,
|
||||
initiatorIngressSecrets: secrets{
|
||||
encKey: hexb("3ca5db8d7d13af7bb3763fee9cef628925a4abda5961d7392fae731c02278377"),
|
||||
encIV: hexb("00000000000000000000000000000000"),
|
||||
macKey: hexb("cb2cd684639c1b64b80687b977c4140bea8c953a1f3975aca6f1589a850879ba"),
|
||||
},
|
||||
initiatorEgressSecrets: secrets{
|
||||
encKey: hexb("3ca5db8d7d13af7bb3763fee9cef628925a4abda5961d7392fae731c02278377"),
|
||||
encIV: hexb("00000000000000000000000000000000"),
|
||||
macKey: hexb("cb2cd684639c1b64b80687b977c4140bea8c953a1f3975aca6f1589a850879ba"),
|
||||
},
|
||||
initiatorIngressMacDigest: hexb("d835ba6c6ac42d4c686a2e3cee6b2ee0190a7da79d6275f2b0b4bdc71fb66709"),
|
||||
initiatorEgressMacDigest: hexb("1901e950288f010d005ccafede47d1dc177c442605a9702fcd6c5a0e717dc130"),
|
||||
},
|
||||
|
||||
// initiator V4, recipient V5
|
||||
{
|
||||
initiator: &Config{
|
||||
Key: hexkey("5e173f6ac3c669587538e7727cf19b782a4f2fda07c1eaa662c593e5e85e3051"),
|
||||
ForceV4: true,
|
||||
},
|
||||
recipient: &Config{
|
||||
Key: hexkey("c45f950382d542169ea207959ee0220ec1491755abe405cd7498d6b16adb6df8"),
|
||||
ForceV4: false,
|
||||
},
|
||||
initiatorEphemeralKey: hexkey("19c2185f4f40634926ebed3af09070ca9e029f2edd5fae6253074896205f5f6c"),
|
||||
recipientEphemeralKey: hexkey("d25688cf0ab10afa1a0e2dba7853ed5f1e5bf1c631757ed4e103b593ff3f5620"),
|
||||
initiatorNonce: hexb("cd26fecb93657d1cd9e9eaf4f8be720b56dd1d39f190c4e1c6b7ec66f077bb11"),
|
||||
recipientNonce: hexb("f37ec61d84cea03dcc5e8385db93248584e8af4b4d1c832d0000000000000005"),
|
||||
|
||||
encAuth: hexb(`
|
||||
0461109a208261e0bedca9b3d474e991409fe0f5be7fd757d33c3a2934be7819
|
||||
269509b86229f36677f23aebe239c21e0c2b244811f9ee0d5370bc4cef1510a0
|
||||
77e1d4d295a6219a9393d7493a52b9e98ccca17a196b43efb30cdfaf2ea99fff
|
||||
bd29c44b5b417924ad3afad6c436e85a0024e24f55f27bb8f86735a76586093f
|
||||
4d496348363cb2fb9905e62786c18030a8b4e3fe4a621439ae1c10598a09f9f5
|
||||
e61315cea0cd09fb0438d9c76b1d516e183a8df2bdc2d7e3a51f4011a990999c
|
||||
b5fb737b9dd16181f44253b313811286004efe9298d4ff49eefd28dc095ed362
|
||||
8b56551f052c4d94c0c0108d08656a0201eb29d66702acb06e2894fab08a684a
|
||||
394258171fc3f099c4f58a075ecde74c8084731639e19b194ff9eef6824a1330
|
||||
59bd884d81ef14541fd9475b9f3d8bcb613eb6
|
||||
`),
|
||||
encAuthResp: hexb(`
|
||||
04c77decb1d4abe500fb924a5972d495b6ca6782122e1e795d8282575302ed32
|
||||
85b0d4bae57ac07c2f455e67cf73d2c77b9dcd295252ca146a65ec3a7e9d6336
|
||||
a1ed212843cafac42831c5a785fe7fc6e18a1ce7ae3d9603c439cdd991ec2f7a
|
||||
5197838efbd8c0ad68e31559c8a711ca3368bb6f4ed6de53db86df7a56eb2897
|
||||
bbb251a2c2e86af3198e87bd98af9f3d96ae7f0777656a3a0c9dec4718f49377
|
||||
0f78b6fecc51f398c0e36e696da3b482584c00581b6b7e596b71580777972bf4
|
||||
e158ce46acf49c893a509c00415996948df2
|
||||
`),
|
||||
|
||||
negotiatedVersion: 4,
|
||||
initiatorIngressSecrets: secrets{
|
||||
encKey: hexb("fc8e46d37d756d53af5f6cebb35d94118bf305fe1c73fc9e672350cbc1dedd75"),
|
||||
encIV: hexb("00000000000000000000000000000000"),
|
||||
macKey: hexb("274693e239751ff505004ed5ab680fd80823d49a12139554bffce549b32d048c"),
|
||||
},
|
||||
initiatorEgressSecrets: secrets{
|
||||
encKey: hexb("fc8e46d37d756d53af5f6cebb35d94118bf305fe1c73fc9e672350cbc1dedd75"),
|
||||
encIV: hexb("00000000000000000000000000000000"),
|
||||
macKey: hexb("274693e239751ff505004ed5ab680fd80823d49a12139554bffce549b32d048c"),
|
||||
},
|
||||
initiatorIngressMacDigest: hexb("395908f0f3da7e588aad3e6ec04fab504f0a65664bf8fe135b67ebaf4cc41daf"),
|
||||
initiatorEgressMacDigest: hexb("09cc2e837d5cf048f41ea39dccc4b07814a125dc5d47e416ae13dac8d4523239"),
|
||||
},
|
||||
|
||||
// old V4 test vector from https://gist.github.com/fjl/3a78780d17c755d22df2
|
||||
{
|
||||
initiator: &Config{
|
||||
Key: hexkey("5e173f6ac3c669587538e7727cf19b782a4f2fda07c1eaa662c593e5e85e3051"),
|
||||
ForceV4: true,
|
||||
},
|
||||
recipient: &Config{
|
||||
Key: hexkey("c45f950382d542169ea207959ee0220ec1491755abe405cd7498d6b16adb6df8"),
|
||||
ForceV4: true,
|
||||
},
|
||||
initiatorEphemeralKey: hexkey("19c2185f4f40634926ebed3af09070ca9e029f2edd5fae6253074896205f5f6c"),
|
||||
recipientEphemeralKey: hexkey("d25688cf0ab10afa1a0e2dba7853ed5f1e5bf1c631757ed4e103b593ff3f5620"),
|
||||
initiatorNonce: hexb("cd26fecb93657d1cd9e9eaf4f8be720b56dd1d39f190c4e1c6b7ec66f077bb11"),
|
||||
recipientNonce: hexb("f37ec61d84cea03dcc5e8385db93248584e8af4b4d1c832d8c7453c0089687a7"),
|
||||
|
||||
encAuth: hexb(`
|
||||
04a0274c5951e32132e7f088c9bdfdc76c9d91f0dc6078e848f8e3361193dbdc
|
||||
43b94351ea3d89e4ff33ddcefbc80070498824857f499656c4f79bbd97b6c51a
|
||||
514251d69fd1785ef8764bd1d262a883f780964cce6a14ff206daf1206aa073a
|
||||
2d35ce2697ebf3514225bef186631b2fd2316a4b7bcdefec8d75a1025ba2c540
|
||||
4a34e7795e1dd4bc01c6113ece07b0df13b69d3ba654a36e35e69ff9d482d88d
|
||||
2f0228e7d96fe11dccbb465a1831c7d4ad3a026924b182fc2bdfe016a6944312
|
||||
021da5cc459713b13b86a686cf34d6fe6615020e4acf26bf0d5b7579ba813e77
|
||||
23eb95b3cef9942f01a58bd61baee7c9bdd438956b426a4ffe238e61746a8c93
|
||||
d5e10680617c82e48d706ac4953f5e1c4c4f7d013c87d34a06626f498f34576d
|
||||
c017fdd3d581e83cfd26cf125b6d2bda1f1d56
|
||||
`),
|
||||
encAuthResp: hexb(`
|
||||
049934a7b2d7f9af8fd9db941d9da281ac9381b5740e1f64f7092f3588d4f87f
|
||||
5ce55191a6653e5e80c1c5dd538169aa123e70dc6ffc5af1827e546c0e958e42
|
||||
dad355bcc1fcb9cdf2cf47ff524d2ad98cbf275e661bf4cf00960e74b5956b79
|
||||
9771334f426df007350b46049adb21a6e78ab1408d5e6ccde6fb5e69f0f4c92b
|
||||
b9c725c02f99fa72b9cdc8dd53cff089e0e73317f61cc5abf6152513cb7d833f
|
||||
09d2851603919bf0fbe44d79a09245c6e8338eb502083dc84b846f2fee1cc310
|
||||
d2cc8b1b9334728f97220bb799376233e113
|
||||
`),
|
||||
|
||||
negotiatedVersion: 4,
|
||||
initiatorIngressSecrets: secrets{
|
||||
encKey: hexb("c0458fa97a5230830e05f4f20b7c755c1d4e54b1ce5cf43260bb191eef4e418d"),
|
||||
encIV: hexb("00000000000000000000000000000000"),
|
||||
macKey: hexb("48c938884d5067a1598272fcddaa4b833cd5e7d92e8228c0ecdfabbe68aef7f1"),
|
||||
},
|
||||
initiatorEgressSecrets: secrets{
|
||||
encKey: hexb("c0458fa97a5230830e05f4f20b7c755c1d4e54b1ce5cf43260bb191eef4e418d"),
|
||||
encIV: hexb("00000000000000000000000000000000"),
|
||||
macKey: hexb("48c938884d5067a1598272fcddaa4b833cd5e7d92e8228c0ecdfabbe68aef7f1"),
|
||||
},
|
||||
initiatorIngressMacDigest: hexb("75823d96e23136c89666ee025fb21a432be906512b3dd4a3049e898adb433847"),
|
||||
initiatorEgressMacDigest: hexb("09771e93b1a6109e97074cbe2d2b0cf3d3878efafe68f53c41bb60c0ec49097e"),
|
||||
},
|
||||
}
|
||||
408
p2p/rlpx/rlpx.go
Normal file
408
p2p/rlpx/rlpx.go
Normal file
|
|
@ -0,0 +1,408 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package rlpx implements the RLPx secure transport protocol.
|
||||
//
|
||||
// RLPx multiplexes packet streams over an authenticated and encrypted
|
||||
// network connection.
|
||||
//
|
||||
// The wire protocol specification lives at https://github.com/ethereum/devp2p.
|
||||
//
|
||||
// Protocols
|
||||
//
|
||||
// RLPx transports packet streams for multiple protocols on the same
|
||||
// connection, ensuring that available bandwidth is fairly distributed
|
||||
// among them. Negotiation of protocol identifiers is not part of the
|
||||
// transport layer and is typically done by sending messages with
|
||||
// protocol identifier 0.
|
||||
package rlpx
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHandshakeTimeout = 5 * time.Second
|
||||
defaultReadTimeout = 10 * time.Second
|
||||
defaultReadIdleTimeout = 25 * time.Second
|
||||
defaultWriteTimeout = 10 * time.Second
|
||||
defaultReadBufferSize = 2 * 1024 * 1024
|
||||
defaultReadBufferWaitTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
// A Config structure is used to configure an RLPx client or server
|
||||
// connection. After one has been passed to any function in package
|
||||
// rlpx, it must not be modified. A Config may be reused; the rlpx
|
||||
// package will also not modify it.
|
||||
type Config struct {
|
||||
// Key is the private key of the server. The key must use the
|
||||
// secp256k1 curve, other curves are not supported.
|
||||
// This field is required for both client and server connections.
|
||||
Key *ecdsa.PrivateKey
|
||||
|
||||
HandshakeTimeout time.Duration // for the key negotiation handshake (default 5s)
|
||||
ReadIdleTimeout time.Duration // applies while waiting for a new frame (default 25s)
|
||||
ReadTimeout time.Duration // for reading the payload data of a single frame (default 10s)
|
||||
WriteTimeout time.Duration // for writing one frame of data (default 10s)
|
||||
|
||||
// ReadBufferSize controls how much data can be buffered for each
|
||||
// protocol. The default is 2MB for compatibility with legacy
|
||||
// peers.
|
||||
//
|
||||
// If the read buffer is full, the implementation waits for
|
||||
// buffer space to become available. The connection is closed if
|
||||
// no space becomes available within the timeout (default 5s).
|
||||
ReadBufferSize uint32
|
||||
ReadBufferWaitTimeout time.Duration
|
||||
|
||||
// Forces use of the version 4 handshake.
|
||||
ForceV4 bool
|
||||
}
|
||||
|
||||
func (cfg *Config) handshakeTimeout() time.Duration {
|
||||
if cfg.HandshakeTimeout != 0 {
|
||||
return cfg.HandshakeTimeout
|
||||
}
|
||||
return defaultHandshakeTimeout
|
||||
}
|
||||
|
||||
func (cfg *Config) readTimeout() time.Duration {
|
||||
if cfg.ReadTimeout != 0 {
|
||||
return cfg.ReadTimeout
|
||||
}
|
||||
return defaultReadTimeout
|
||||
}
|
||||
|
||||
func (cfg *Config) readIdleTimeout() time.Duration {
|
||||
if cfg.ReadIdleTimeout != 0 {
|
||||
return cfg.ReadIdleTimeout
|
||||
}
|
||||
return defaultReadIdleTimeout
|
||||
}
|
||||
|
||||
func (cfg *Config) writeTimeout() time.Duration {
|
||||
if cfg.WriteTimeout != 0 {
|
||||
return cfg.WriteTimeout
|
||||
}
|
||||
return defaultWriteTimeout
|
||||
}
|
||||
|
||||
func (cfg *Config) readBufferWaitTimeout() time.Duration {
|
||||
if cfg.ReadBufferWaitTimeout != 0 {
|
||||
return cfg.ReadBufferWaitTimeout
|
||||
}
|
||||
return defaultReadBufferWaitTimeout
|
||||
}
|
||||
|
||||
func (cfg *Config) readBufferSize() uint32 {
|
||||
if cfg.ReadBufferSize != 0 {
|
||||
return cfg.ReadBufferSize
|
||||
}
|
||||
return defaultReadBufferSize
|
||||
}
|
||||
|
||||
// Conn represents an RLPx connection.
|
||||
type Conn struct {
|
||||
// readonly fields
|
||||
cfg *Config
|
||||
isServer bool
|
||||
fd net.Conn
|
||||
handshake sync.Once
|
||||
handshakeRand handshakeRandSource // for testing
|
||||
|
||||
wmu sync.Mutex // excludes writes on rw
|
||||
rw *frameRW // set after handshake
|
||||
remoteID *ecdsa.PublicKey
|
||||
vsn uint // negotiated version
|
||||
|
||||
mu sync.Mutex
|
||||
proto map[uint16]*Protocol
|
||||
readErr error
|
||||
}
|
||||
|
||||
// Client returns a new client side RLPx connection using fd as the
|
||||
// underlying transport. The public key of the remote end must be
|
||||
// known in advance.
|
||||
//
|
||||
// config must not be nil and must contain a
|
||||
// valid private key.
|
||||
func Client(fd net.Conn, remotePubkey *ecdsa.PublicKey, config *Config) *Conn {
|
||||
c := newConn(fd, config)
|
||||
c.remoteID = remotePubkey
|
||||
return c
|
||||
}
|
||||
|
||||
// Server returns a new server side RLPx connection using fd as the
|
||||
// underlying transport. The configuration config must be non-nil and
|
||||
// must contain a valid private key
|
||||
func Server(fd net.Conn, config *Config) *Conn {
|
||||
c := newConn(fd, config)
|
||||
c.isServer = true
|
||||
return c
|
||||
}
|
||||
|
||||
func newConn(fd net.Conn, config *Config) *Conn {
|
||||
return &Conn{
|
||||
fd: fd,
|
||||
cfg: config,
|
||||
proto: make(map[uint16]*Protocol),
|
||||
}
|
||||
}
|
||||
|
||||
// Handshake runs the client or server handshake protocol if it has
|
||||
// not yet been run. Most uses of this package need not call Handshake
|
||||
// explicitly: the first Read or Write will call it automatically.
|
||||
func (c *Conn) Handshake() (err error) {
|
||||
// TODO: check cfg.Key curve, maybe panic earlier
|
||||
c.handshake.Do(func() {
|
||||
if c.handshakeRand == nil {
|
||||
c.handshakeRand = realRandSource{}
|
||||
}
|
||||
var (
|
||||
ingress, egress secrets
|
||||
rid *ecdsa.PublicKey
|
||||
vsn uint
|
||||
)
|
||||
c.fd.SetDeadline(time.Now().Add(c.cfg.handshakeTimeout()))
|
||||
if c.isServer {
|
||||
vsn, rid, ingress, egress, err = c.recipientHandshake()
|
||||
} else {
|
||||
vsn, ingress, egress, err = c.initiatorHandshake()
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
c.vsn = vsn
|
||||
if rid != nil {
|
||||
c.remoteID = rid
|
||||
}
|
||||
c.mu.Unlock()
|
||||
c.rw = newFrameRW(c.fd, ingress, egress)
|
||||
go readLoop(c)
|
||||
})
|
||||
if err == nil && c.rw == nil {
|
||||
return errors.New("handshake failed")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// LocalAddr returns the local network address of the underlying net.Conn.
|
||||
func (c *Conn) LocalAddr() net.Addr {
|
||||
return c.fd.LocalAddr()
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address of the underlying net.Conn.
|
||||
func (c *Conn) RemoteAddr() net.Addr {
|
||||
return c.fd.RemoteAddr()
|
||||
}
|
||||
|
||||
// RemoteID returns the public key of the remote end.
|
||||
// If the remote identity is not yet known, it returns nil.
|
||||
func (c *Conn) RemoteID() *ecdsa.PublicKey {
|
||||
c.mu.Lock()
|
||||
id := c.remoteID
|
||||
c.mu.Unlock()
|
||||
return id
|
||||
}
|
||||
|
||||
// Version returns the negotiated RLPx version of the connection.
|
||||
// The return value is zero before the handshake has executed and
|
||||
// can be 4 or 5 afterwards.
|
||||
func (c *Conn) Version() uint {
|
||||
c.mu.Lock()
|
||||
vsn := c.vsn
|
||||
c.mu.Unlock()
|
||||
return vsn
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
func (c *Conn) Close() error {
|
||||
// TODO: shut down reader/wr
|
||||
return c.fd.Close()
|
||||
}
|
||||
|
||||
// Protocol returns a handle for the given protocol id.
|
||||
// It can be called at most once for any given id,
|
||||
// subsequent call with the same id will panic.
|
||||
func (c *Conn) Protocol(id uint16) *Protocol {
|
||||
p := c.getProtocol(id)
|
||||
close(p.claimSignal) // panics when claimed twice
|
||||
return p
|
||||
}
|
||||
|
||||
// waits until the given protocol is claimed by a call to Protocol.
|
||||
func (c *Conn) waitForProtocol(id uint16) *Protocol {
|
||||
p := c.getProtocol(id)
|
||||
timeout := time.NewTimer(5 * time.Second)
|
||||
defer timeout.Stop()
|
||||
select {
|
||||
case <-timeout.C:
|
||||
return nil
|
||||
case <-p.claimSignal:
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) getProtocol(id uint16) *Protocol {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.proto[id] == nil {
|
||||
c.proto[id] = newProtocol(c, id)
|
||||
}
|
||||
return c.proto[id]
|
||||
}
|
||||
|
||||
// Protocol is a handle for the given protocol.
|
||||
type Protocol struct {
|
||||
c *Conn
|
||||
claimed bool
|
||||
id uint16
|
||||
claimSignal chan struct{}
|
||||
|
||||
// for readLoop
|
||||
xfers map[uint16]*packetReader
|
||||
readBufSema *bufSema
|
||||
|
||||
// for ReadPacket
|
||||
readCond *sync.Cond // unblocks ReadPacket
|
||||
newPackets []*packetReader
|
||||
readErr error
|
||||
|
||||
// for writing
|
||||
contextidSeq uint16
|
||||
}
|
||||
|
||||
func newProtocol(c *Conn, id uint16) *Protocol {
|
||||
return &Protocol{
|
||||
c: c,
|
||||
id: id,
|
||||
claimSignal: make(chan struct{}),
|
||||
xfers: make(map[uint16]*packetReader),
|
||||
readBufSema: newBufSema(c.cfg.readBufferSize()),
|
||||
readCond: sync.NewCond(new(sync.Mutex)),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Protocol) feedPacket(pr *packetReader) {
|
||||
p.readCond.L.Lock()
|
||||
p.newPackets = append(p.newPackets, pr)
|
||||
p.readCond.Signal()
|
||||
p.readCond.L.Unlock()
|
||||
}
|
||||
|
||||
func (p *Protocol) readClose(err error) {
|
||||
p.readCond.L.Lock()
|
||||
p.readErr = err
|
||||
p.readCond.Broadcast()
|
||||
p.readCond.L.Unlock()
|
||||
}
|
||||
|
||||
// ReadHeader waits for a packet to appear. The content of the packet
|
||||
// can be read from r as it is received. More packets can be read
|
||||
// immediately, r does not need to be consumed before the next call.
|
||||
func (p *Protocol) ReadPacket() (totalSize uint32, r io.Reader, err error) {
|
||||
// Lazy handshake.
|
||||
if err := p.c.Handshake(); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
// Wait for a packet or error.
|
||||
p.readCond.L.Lock()
|
||||
defer p.readCond.L.Unlock()
|
||||
for len(p.newPackets) == 0 && p.readErr == nil {
|
||||
p.readCond.Wait()
|
||||
}
|
||||
if len(p.newPackets) == 0 && p.readErr != nil {
|
||||
return 0, nil, p.readErr
|
||||
}
|
||||
pr := p.newPackets[0]
|
||||
p.newPackets = p.newPackets[:copy(p.newPackets, p.newPackets[1:])]
|
||||
return pr.readN, pr, nil
|
||||
}
|
||||
|
||||
// SendPacket sends len bytes from the payload reader on the connection.
|
||||
func (p *Protocol) SendPacket(len uint32, payload io.Reader) error {
|
||||
if err := p.c.Handshake(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len <= staticFrameSize {
|
||||
// The message is small enough and can be sent in a single frame.
|
||||
buf := makeFrameWriteBuffer()
|
||||
if n, err := io.CopyN(buf, payload, int64(len)); err != nil {
|
||||
return fmt.Errorf("read from packet payload failed at pos %d: %v", n, err)
|
||||
}
|
||||
return p.c.sendFrame(regularHeader{p.id, 0}, buf)
|
||||
}
|
||||
return p.sendChunked(len, payload)
|
||||
}
|
||||
|
||||
func (p *Protocol) sendChunked(size uint32, payload io.Reader) error {
|
||||
contextid := p.nextContextID()
|
||||
initial := true
|
||||
buf := makeFrameWriteBuffer()
|
||||
var rpos int64
|
||||
for seq := uint16(0); size > 0; seq++ {
|
||||
var header interface{}
|
||||
if initial {
|
||||
header = chunkStartHeader{p.id, contextid, size}
|
||||
initial = false
|
||||
} else {
|
||||
header = regularHeader{p.id, contextid}
|
||||
}
|
||||
|
||||
fsize := staticFrameSize
|
||||
if size < fsize {
|
||||
fsize = size
|
||||
}
|
||||
if !initial {
|
||||
buf.resetForWrite()
|
||||
}
|
||||
if n, err := io.CopyN(buf, payload, int64(fsize)); err != nil {
|
||||
// The remote end is waiting for the rest of the packet
|
||||
// but we can't provide it. Since there is no way to cancel
|
||||
// partial transfers, our only option is closing the connection.
|
||||
// TODO: close the connection
|
||||
return fmt.Errorf("read from packet payload failed at pos %d: %v", rpos+n, err)
|
||||
}
|
||||
rpos += int64(fsize)
|
||||
if err := p.c.sendFrame(header, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
size -= fsize
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// returns the next context ID for a chunked transfer.
|
||||
// never returns 0, which is reserved for single-frame transfers.
|
||||
func (p *Protocol) nextContextID() uint16 {
|
||||
p.contextidSeq++
|
||||
return p.contextidSeq
|
||||
}
|
||||
|
||||
func (c *Conn) sendFrame(header interface{}, body *frameBuffer) error {
|
||||
c.wmu.Lock()
|
||||
defer c.wmu.Unlock()
|
||||
c.fd.SetWriteDeadline(time.Now().Add(c.cfg.writeTimeout()))
|
||||
return c.rw.sendFrame(header, body)
|
||||
}
|
||||
186
p2p/rlpx/rlpx_test.go
Normal file
186
p2p/rlpx/rlpx_test.go
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rlpx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
func TestSequentialTransfer(t *testing.T) {
|
||||
var (
|
||||
p1, p2 = net.Pipe()
|
||||
k1, k2 = newkey(), newkey()
|
||||
sc = Server(p1, &Config{Key: k1})
|
||||
cc = Client(p2, &k1.PublicKey, &Config{Key: k2})
|
||||
)
|
||||
run(t, rig{
|
||||
"server": func() error { return testProtoReaders(t, sc, 1) },
|
||||
"client": func() error { return testProtoWriters(t, cc, 1) },
|
||||
})
|
||||
}
|
||||
|
||||
func TestConcurrentTransfer(t *testing.T) {
|
||||
var (
|
||||
p1, p2 = net.Pipe()
|
||||
k1, k2 = newkey(), newkey()
|
||||
sc = Server(p1, &Config{Key: k1})
|
||||
cc = Client(p2, &k1.PublicKey, &Config{Key: k2})
|
||||
)
|
||||
run(t, rig{
|
||||
"server": func() error { return testProtoReaders(t, cc, 10) },
|
||||
"client": func() error { return testProtoWriters(t, sc, 10) },
|
||||
})
|
||||
}
|
||||
|
||||
func TestConcurrentTransferReadError(t *testing.T) {
|
||||
var (
|
||||
p1, p2 = net.Pipe()
|
||||
k1, k2 = newkey(), newkey()
|
||||
sc = Server(p1, &Config{Key: k1})
|
||||
cc = Client(p2, &k1.PublicKey, &Config{Key: k2})
|
||||
badPacketSize = uint32(16 * 8 * 1024)
|
||||
)
|
||||
run(t, rig{
|
||||
"client": func() error { return testProtoWriters(t, sc, 10) },
|
||||
"server": func() error { return testProtoReaders(t, cc, 10) },
|
||||
|
||||
// This sends a bad frame after a two sane ones.
|
||||
"badFrameWrite": func() error {
|
||||
if err := cc.Handshake(); err != nil {
|
||||
return fmt.Errorf("handshake error: %v", err)
|
||||
}
|
||||
heads := []interface{}{
|
||||
chunkStartHeader{Protocol: 11, ContextID: 1, TotalSize: badPacketSize},
|
||||
regularHeader{Protocol: 11, ContextID: 1},
|
||||
// The bad frame is a chunk start header with a context id
|
||||
// that is already in use.
|
||||
chunkStartHeader{Protocol: 11, ContextID: 1, TotalSize: 22},
|
||||
}
|
||||
for i, h := range heads {
|
||||
buf := makeFrameWriteBuffer()
|
||||
buf.Write(make([]byte, 1024))
|
||||
if err := cc.sendFrame(h, buf); err != nil {
|
||||
return fmt.Errorf("error sending frame %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
// The other end should receive an error from Read.
|
||||
"badFrameRead": func() error {
|
||||
proto := sc.Protocol(11)
|
||||
_, r, err := proto.ReadPacket()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unexpected ReadPacket error: %v", err)
|
||||
}
|
||||
_, err = io.CopyN(ioutil.Discard, r, int64(badPacketSize))
|
||||
if err == nil {
|
||||
return fmt.Errorf("no error received")
|
||||
}
|
||||
if err != errUnexpectedChunkStart {
|
||||
return fmt.Errorf("wrong error: got %q want %q", err, errUnexpectedChunkStart)
|
||||
}
|
||||
// TODO: shouldn't all transfers fail?
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testProtoWriters(t *testing.T, conn *Conn, nprotos uint16) error {
|
||||
defer conn.Close()
|
||||
writers := rig{}
|
||||
for i := uint16(0); i < nprotos; i++ {
|
||||
i := i
|
||||
writers[fmt.Sprint("protocol ", i)] = func() error { return testWriter(t, conn.Protocol(i)) }
|
||||
}
|
||||
run(t, writers)
|
||||
return nil
|
||||
}
|
||||
|
||||
func testProtoReaders(t *testing.T, conn *Conn, nprotos uint16) error {
|
||||
defer conn.Close()
|
||||
readers := rig{}
|
||||
for i := uint16(0); i < nprotos; i++ {
|
||||
i := i
|
||||
readers[fmt.Sprint("protocol ", i)] = func() error { return testReader(t, conn.Protocol(i)) }
|
||||
}
|
||||
run(t, readers)
|
||||
return nil
|
||||
}
|
||||
|
||||
func testWriter(t *testing.T, p *Protocol) error {
|
||||
for size := 1; size < 8*1024*1024; size *= 2 {
|
||||
if err := sendBytes(p, make([]byte, size)); err != nil {
|
||||
return fmt.Errorf("error sending %d bytes: %v", size, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func testReader(t *testing.T, p *Protocol) error {
|
||||
for size := 1; size < 8*1024*1024; size *= 2 {
|
||||
len, r, err := p.ReadPacket()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReadPacket error with size %d: %v", size, err)
|
||||
}
|
||||
if len != uint32(size) {
|
||||
return fmt.Errorf("len mismatch, got %d want %d", len, size)
|
||||
}
|
||||
if n, err := io.CopyN(ioutil.Discard, r, int64(size)); err != nil {
|
||||
return fmt.Errorf("body read error at %d of %d bytes: %v", n, size, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type rig map[string]func() error
|
||||
|
||||
func run(t *testing.T, rig rig) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(rig))
|
||||
for name, fn := range rig {
|
||||
name, fn := name, fn
|
||||
go func() {
|
||||
if err := fn(); err != nil {
|
||||
t.Error(name, err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func sendBytes(p *Protocol, data []byte) error {
|
||||
return p.SendPacket(uint32(len(data)), bytes.NewReader(data))
|
||||
}
|
||||
|
||||
func newkey() *ecdsa.PrivateKey {
|
||||
key, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
panic("couldn't generate key: " + err.Error())
|
||||
}
|
||||
return key
|
||||
}
|
||||
376
p2p/rlpx_test.go
376
p2p/rlpx_test.go
|
|
@ -1,376 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
func TestSharedSecret(t *testing.T) {
|
||||
prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader)
|
||||
pub0 := &prv0.PublicKey
|
||||
prv1, _ := crypto.GenerateKey()
|
||||
pub1 := &prv1.PublicKey
|
||||
|
||||
ss0, err := ecies.ImportECDSA(prv0).GenerateShared(ecies.ImportECDSAPublic(pub1), sskLen, sskLen)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ss1, err := ecies.ImportECDSA(prv1).GenerateShared(ecies.ImportECDSAPublic(pub0), sskLen, sskLen)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t.Logf("Secret:\n%v %x\n%v %x", len(ss0), ss0, len(ss0), ss1)
|
||||
if !bytes.Equal(ss0, ss1) {
|
||||
t.Errorf("dont match :(")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncHandshake(t *testing.T) {
|
||||
for i := 0; i < 10; 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))
|
||||
}
|
||||
for i := 0; i < 10; 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
|
||||
id discover.NodeID
|
||||
err error
|
||||
}
|
||||
var (
|
||||
prv0, _ = crypto.GenerateKey()
|
||||
prv1, _ = crypto.GenerateKey()
|
||||
fd0, fd1 = net.Pipe()
|
||||
c0, c1 = newRLPX(fd0).(*rlpx), newRLPX(fd1).(*rlpx)
|
||||
output = make(chan result)
|
||||
)
|
||||
|
||||
go func() {
|
||||
r := result{side: "initiator"}
|
||||
defer func() { output <- r }()
|
||||
defer fd0.Close()
|
||||
|
||||
dest := &discover.Node{ID: discover.PubkeyID(&prv1.PublicKey)}
|
||||
r.id, r.err = c0.doEncHandshake(prv0, dest)
|
||||
if r.err != nil {
|
||||
return
|
||||
}
|
||||
id1 := discover.PubkeyID(&prv1.PublicKey)
|
||||
if r.id != id1 {
|
||||
r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.id, id1)
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
r := result{side: "receiver"}
|
||||
defer func() { output <- r }()
|
||||
defer fd1.Close()
|
||||
|
||||
r.id, r.err = c1.doEncHandshake(prv1, nil)
|
||||
if r.err != nil {
|
||||
return
|
||||
}
|
||||
id0 := discover.PubkeyID(&prv0.PublicKey)
|
||||
if r.id != id0 {
|
||||
r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.id, id0)
|
||||
}
|
||||
}()
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// compare derived secrets
|
||||
if !reflect.DeepEqual(c0.rw.egressMAC, c1.rw.ingressMAC) {
|
||||
return fmt.Errorf("egress mac mismatch:\n c0.rw: %#v\n c1.rw: %#v", c0.rw.egressMAC, c1.rw.ingressMAC)
|
||||
}
|
||||
if !reflect.DeepEqual(c0.rw.ingressMAC, c1.rw.egressMAC) {
|
||||
return fmt.Errorf("ingress mac mismatch:\n c0.rw: %#v\n c1.rw: %#v", c0.rw.ingressMAC, c1.rw.egressMAC)
|
||||
}
|
||||
if !reflect.DeepEqual(c0.rw.enc, c1.rw.enc) {
|
||||
return fmt.Errorf("enc cipher mismatch:\n c0.rw: %#v\n c1.rw: %#v", c0.rw.enc, c1.rw.enc)
|
||||
}
|
||||
if !reflect.DeepEqual(c0.rw.dec, c1.rw.dec) {
|
||||
return fmt.Errorf("dec cipher mismatch:\n c0.rw: %#v\n c1.rw: %#v", c0.rw.dec, c1.rw.dec)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestProtocolHandshake(t *testing.T) {
|
||||
var (
|
||||
prv0, _ = crypto.GenerateKey()
|
||||
node0 = &discover.Node{ID: discover.PubkeyID(&prv0.PublicKey), IP: net.IP{1, 2, 3, 4}, TCP: 33}
|
||||
hs0 = &protoHandshake{Version: 3, ID: node0.ID, Caps: []Cap{{"a", 0}, {"b", 2}}}
|
||||
|
||||
prv1, _ = crypto.GenerateKey()
|
||||
node1 = &discover.Node{ID: discover.PubkeyID(&prv1.PublicKey), IP: net.IP{5, 6, 7, 8}, TCP: 44}
|
||||
hs1 = &protoHandshake{Version: 3, ID: node1.ID, Caps: []Cap{{"c", 1}, {"d", 3}}}
|
||||
|
||||
fd0, fd1 = net.Pipe()
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
rlpx := newRLPX(fd0)
|
||||
remid, err := rlpx.doEncHandshake(prv0, node1)
|
||||
if err != nil {
|
||||
t.Errorf("dial side enc handshake failed: %v", err)
|
||||
return
|
||||
}
|
||||
if remid != node1.ID {
|
||||
t.Errorf("dial side remote id mismatch: got %v, want %v", remid, node1.ID)
|
||||
return
|
||||
}
|
||||
|
||||
phs, err := rlpx.doProtoHandshake(hs0)
|
||||
if err != nil {
|
||||
t.Errorf("dial side proto handshake error: %v", err)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(phs, hs1) {
|
||||
t.Errorf("dial side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs1))
|
||||
return
|
||||
}
|
||||
rlpx.close(DiscQuitting)
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
rlpx := newRLPX(fd1)
|
||||
remid, err := rlpx.doEncHandshake(prv1, nil)
|
||||
if err != nil {
|
||||
t.Errorf("listen side enc handshake failed: %v", err)
|
||||
return
|
||||
}
|
||||
if remid != node0.ID {
|
||||
t.Errorf("listen side remote id mismatch: got %v, want %v", remid, node0.ID)
|
||||
return
|
||||
}
|
||||
|
||||
phs, err := rlpx.doProtoHandshake(hs1)
|
||||
if err != nil {
|
||||
t.Errorf("listen side proto handshake error: %v", err)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(phs, hs0) {
|
||||
t.Errorf("listen side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs0))
|
||||
return
|
||||
}
|
||||
|
||||
if err := ExpectMsg(rlpx, discMsg, []DiscReason{DiscQuitting}); err != nil {
|
||||
t.Errorf("error receiving disconnect: %v", err)
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestProtocolHandshakeErrors(t *testing.T) {
|
||||
our := &protoHandshake{Version: 3, Caps: []Cap{{"foo", 2}, {"bar", 3}}, Name: "quux"}
|
||||
id := randomID()
|
||||
tests := []struct {
|
||||
code uint64
|
||||
msg interface{}
|
||||
err error
|
||||
}{
|
||||
{
|
||||
code: discMsg,
|
||||
msg: []DiscReason{DiscQuitting},
|
||||
err: DiscQuitting,
|
||||
},
|
||||
{
|
||||
code: 0x989898,
|
||||
msg: []byte{1},
|
||||
err: errors.New("expected handshake, got 989898"),
|
||||
},
|
||||
{
|
||||
code: handshakeMsg,
|
||||
msg: make([]byte, baseProtocolMaxMsgSize+2),
|
||||
err: errors.New("message too big"),
|
||||
},
|
||||
{
|
||||
code: handshakeMsg,
|
||||
msg: []byte{1, 2, 3},
|
||||
err: newPeerError(errInvalidMsg, "(code 0) (size 4) rlp: expected input list for p2p.protoHandshake"),
|
||||
},
|
||||
{
|
||||
code: handshakeMsg,
|
||||
msg: &protoHandshake{Version: 9944, ID: id},
|
||||
err: DiscIncompatibleVersion,
|
||||
},
|
||||
{
|
||||
code: handshakeMsg,
|
||||
msg: &protoHandshake{Version: 3},
|
||||
err: DiscInvalidIdentity,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
p1, p2 := MsgPipe()
|
||||
go Send(p1, test.code, test.msg)
|
||||
_, err := readProtocolHandshake(p2, our)
|
||||
if !reflect.DeepEqual(err, test.err) {
|
||||
t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 := Send(rw, 8, []uint{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 := Send(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -123,8 +123,7 @@ type Server struct {
|
|||
|
||||
// Hooks for testing. These are useful because we can inhibit
|
||||
// the whole protocol stack.
|
||||
newTransport func(net.Conn) transport
|
||||
newPeerHook func(*Peer)
|
||||
peerRunFunction func(*Peer) DiscReason
|
||||
|
||||
lock sync.Mutex // protects running
|
||||
running bool
|
||||
|
|
@ -157,38 +156,35 @@ const (
|
|||
trustedConn
|
||||
)
|
||||
|
||||
// used in place of devConn so server tests can substitute a mock.
|
||||
type transport interface {
|
||||
// devConn
|
||||
doProtoHandshake(our *protoHandshake) (their *protoHandshake, err error)
|
||||
close(err error)
|
||||
// rlpx.Conn
|
||||
Handshake() error
|
||||
RemoteAddr() net.Addr
|
||||
LocalAddr() net.Addr
|
||||
RemoteID() *ecdsa.PublicKey
|
||||
}
|
||||
|
||||
// conn wraps a network connection with information gathered
|
||||
// during the two handshakes.
|
||||
type conn struct {
|
||||
fd net.Conn
|
||||
transport
|
||||
id discover.NodeID
|
||||
flags connFlag
|
||||
cont chan error // The run loop uses cont to signal errors to setupConn.
|
||||
id discover.NodeID // valid after the encryption handshake
|
||||
caps []Cap // valid after the protocol handshake
|
||||
name string // valid after the protocol handshake
|
||||
}
|
||||
|
||||
type transport interface {
|
||||
// The two handshakes.
|
||||
doEncHandshake(prv *ecdsa.PrivateKey, dialDest *discover.Node) (discover.NodeID, error)
|
||||
doProtoHandshake(our *protoHandshake) (*protoHandshake, error)
|
||||
// The MsgReadWriter can only be used after the encryption
|
||||
// handshake has completed. The code uses conn.id to track this
|
||||
// by setting it to a non-nil value after the encryption handshake.
|
||||
MsgReadWriter
|
||||
// transports must provide Close because we use MsgPipe in some of
|
||||
// the tests. Closing the actual network connection doesn't do
|
||||
// anything in those tests because NsgPipe doesn't use it.
|
||||
close(err error)
|
||||
}
|
||||
|
||||
func (c *conn) String() string {
|
||||
s := c.flags.String() + " conn"
|
||||
if (c.id != discover.NodeID{}) {
|
||||
s += fmt.Sprintf(" %x", c.id[:8])
|
||||
}
|
||||
s += " " + c.fd.RemoteAddr().String()
|
||||
s += " " + c.RemoteAddr().String()
|
||||
return s
|
||||
}
|
||||
|
||||
|
|
@ -314,9 +310,6 @@ func (srv *Server) Start() (err error) {
|
|||
if srv.PrivateKey == nil {
|
||||
return fmt.Errorf("Server.PrivateKey must be set to a non-nil key")
|
||||
}
|
||||
if srv.newTransport == nil {
|
||||
srv.newTransport = newRLPX
|
||||
}
|
||||
if srv.Dialer == nil {
|
||||
srv.Dialer = &net.Dialer{Timeout: defaultDialTimeout}
|
||||
}
|
||||
|
|
@ -505,7 +498,7 @@ running:
|
|||
}
|
||||
// Disconnect all peers.
|
||||
for _, p := range peers {
|
||||
p.Disconnect(DiscQuitting)
|
||||
p.conn.close(DiscQuitting)
|
||||
}
|
||||
// Wait for peers to shut down. Pending connections and tasks are
|
||||
// not handled here and will terminate soon-ish because srv.quit
|
||||
|
|
@ -588,7 +581,7 @@ func (srv *Server) listenLoop() {
|
|||
// Spawn the handler. It will give the slot back when the connection
|
||||
// has been established.
|
||||
go func() {
|
||||
srv.setupConn(fd, inboundConn, nil)
|
||||
srv.setupConn(newDevConn(fd, srv.PrivateKey, nil), inboundConn, nil)
|
||||
slots <- struct{}{}
|
||||
}()
|
||||
}
|
||||
|
|
@ -597,24 +590,24 @@ func (srv *Server) listenLoop() {
|
|||
// setupConn runs the handshakes and attempts to add the connection
|
||||
// as a peer. It returns when the connection has been added as a peer
|
||||
// or the handshakes have failed.
|
||||
func (srv *Server) setupConn(fd net.Conn, flags connFlag, dialDest *discover.Node) {
|
||||
func (srv *Server) setupConn(t transport, flags connFlag, dialDest *discover.Node) {
|
||||
// Prevent leftover pending conns from entering the handshake.
|
||||
srv.lock.Lock()
|
||||
running := srv.running
|
||||
srv.lock.Unlock()
|
||||
c := &conn{fd: fd, transport: srv.newTransport(fd), flags: flags, cont: make(chan error)}
|
||||
c := &conn{transport: t, flags: flags, cont: make(chan error)}
|
||||
if !running {
|
||||
c.close(errServerStopped)
|
||||
return
|
||||
}
|
||||
// Run the encryption handshake.
|
||||
var err error
|
||||
if c.id, err = c.doEncHandshake(srv.PrivateKey, dialDest); err != nil {
|
||||
glog.V(logger.Debug).Infof("%v faild enc handshake: %v", c, err)
|
||||
if err := c.Handshake(); err != nil {
|
||||
glog.V(logger.Debug).Infof("%v failed enc handshake: %v", c, err)
|
||||
c.close(err)
|
||||
return
|
||||
}
|
||||
// For dialed connections, check that the remote public key matches.
|
||||
c.id = discover.PubkeyID(c.RemoteID())
|
||||
if dialDest != nil && c.id != dialDest.ID {
|
||||
c.close(DiscUnexpectedIdentity)
|
||||
glog.V(logger.Debug).Infof("%v dialed identity mismatch, want %x", c, dialDest.ID[:8])
|
||||
|
|
@ -675,15 +668,17 @@ func (srv *Server) runPeer(p *Peer) {
|
|||
NumConnections: srv.PeerCount(),
|
||||
})
|
||||
|
||||
if srv.newPeerHook != nil {
|
||||
srv.newPeerHook(p)
|
||||
var reason DiscReason
|
||||
if srv.peerRunFunction != nil {
|
||||
reason = srv.peerRunFunction(p)
|
||||
} else {
|
||||
reason = p.run()
|
||||
}
|
||||
discreason := p.run()
|
||||
// Note: run waits for existing peers to be sent on srv.delpeer
|
||||
// before returning, so this send should not select on srv.quit.
|
||||
srv.delpeer <- p
|
||||
|
||||
glog.V(logger.Debug).Infof("Removed %v (%v)\n", p, discreason)
|
||||
glog.V(logger.Debug).Infof("Removed %v (%v)\n", p, reason)
|
||||
srvjslog.LogJson(&logger.P2PDisconnected{
|
||||
RemoteId: p.ID().String(),
|
||||
NumConnections: srv.PeerCount(),
|
||||
|
|
|
|||
|
|
@ -26,54 +26,17 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// glog.SetV(6)
|
||||
// glog.SetToStderr(true)
|
||||
}
|
||||
|
||||
type testTransport struct {
|
||||
id discover.NodeID
|
||||
*rlpx
|
||||
|
||||
closeErr error
|
||||
}
|
||||
|
||||
func newTestTransport(id discover.NodeID, fd net.Conn) transport {
|
||||
wrapped := newRLPX(fd).(*rlpx)
|
||||
wrapped.rw = newRLPXFrameRW(fd, secrets{
|
||||
MAC: zero16,
|
||||
AES: zero16,
|
||||
IngressMAC: sha3.NewKeccak256(),
|
||||
EgressMAC: sha3.NewKeccak256(),
|
||||
})
|
||||
return &testTransport{id: id, rlpx: wrapped}
|
||||
}
|
||||
|
||||
func (c *testTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *discover.Node) (discover.NodeID, error) {
|
||||
return c.id, nil
|
||||
}
|
||||
|
||||
func (c *testTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) {
|
||||
return &protoHandshake{ID: c.id, Name: "test"}, nil
|
||||
}
|
||||
|
||||
func (c *testTransport) close(err error) {
|
||||
c.rlpx.fd.Close()
|
||||
c.closeErr = err
|
||||
}
|
||||
|
||||
func startTestServer(t *testing.T, id discover.NodeID, pf func(*Peer)) *Server {
|
||||
func startTestServer(t *testing.T, trustedNodes []*discover.Node, pf func(*Peer) DiscReason) *Server {
|
||||
server := &Server{
|
||||
Name: "test",
|
||||
MaxPeers: 10,
|
||||
ListenAddr: "127.0.0.1:0",
|
||||
PrivateKey: newkey(),
|
||||
newPeerHook: pf,
|
||||
newTransport: func(fd net.Conn) transport { return newTestTransport(id, fd) },
|
||||
TrustedNodes: trustedNodes,
|
||||
peerRunFunction: pf,
|
||||
}
|
||||
if err := server.Start(); err != nil {
|
||||
t.Fatalf("Could not start server: %v", err)
|
||||
|
|
@ -84,8 +47,10 @@ func startTestServer(t *testing.T, id discover.NodeID, pf func(*Peer)) *Server {
|
|||
func TestServerListen(t *testing.T) {
|
||||
// start the test server
|
||||
connected := make(chan *Peer)
|
||||
remid := randomID()
|
||||
srv := startTestServer(t, remid, func(p *Peer) {
|
||||
remkey := newkey()
|
||||
remid := discover.PubkeyID(&remkey.PublicKey)
|
||||
quitPeers := make(chan struct{})
|
||||
srv := startTestServer(t, nil, func(p *Peer) DiscReason {
|
||||
if p.ID() != remid {
|
||||
t.Error("peer func called with wrong node id")
|
||||
}
|
||||
|
|
@ -93,16 +58,21 @@ func TestServerListen(t *testing.T) {
|
|||
t.Error("peer func called with nil conn")
|
||||
}
|
||||
connected <- p
|
||||
<-quitPeers
|
||||
return DiscQuitting
|
||||
})
|
||||
defer close(connected)
|
||||
defer srv.Stop()
|
||||
defer close(quitPeers)
|
||||
|
||||
// dial the test server
|
||||
conn, err := net.DialTimeout("tcp", srv.ListenAddr, 5*time.Second)
|
||||
fd, err := net.DialTimeout("tcp", srv.ListenAddr, 5*time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("could not dial: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
defer fd.Close()
|
||||
conn := newDevConn(fd, remkey, &srv.PrivateKey.PublicKey)
|
||||
conn.doProtoHandshake(&protoHandshake{Version: baseProtocolVersion, ID: remid})
|
||||
|
||||
select {
|
||||
case peer := <-connected:
|
||||
|
|
@ -120,6 +90,18 @@ func TestServerListen(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestServerDial(t *testing.T) {
|
||||
// start the server
|
||||
connected := make(chan *Peer)
|
||||
quitPeers := make(chan struct{})
|
||||
srv := startTestServer(t, nil, func(p *Peer) DiscReason {
|
||||
connected <- p
|
||||
<-quitPeers
|
||||
return DiscQuitting
|
||||
})
|
||||
defer close(connected)
|
||||
defer srv.Stop()
|
||||
defer close(quitPeers)
|
||||
|
||||
// run a one-shot TCP server to handle the connection.
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
|
|
@ -127,22 +109,19 @@ func TestServerDial(t *testing.T) {
|
|||
}
|
||||
defer listener.Close()
|
||||
accepted := make(chan net.Conn)
|
||||
remkey := newkey()
|
||||
remid := discover.PubkeyID(&remkey.PublicKey)
|
||||
go func() {
|
||||
conn, err := listener.Accept()
|
||||
fd, err := listener.Accept()
|
||||
if err != nil {
|
||||
t.Error("accept error:", err)
|
||||
return
|
||||
}
|
||||
accepted <- conn
|
||||
accepted <- fd
|
||||
conn := newDevConn(fd, remkey, nil)
|
||||
conn.doProtoHandshake(&protoHandshake{Version: baseProtocolVersion, ID: remid, Name: "test"})
|
||||
}()
|
||||
|
||||
// start the server
|
||||
connected := make(chan *Peer)
|
||||
remid := randomID()
|
||||
srv := startTestServer(t, remid, func(p *Peer) { connected <- p })
|
||||
defer close(connected)
|
||||
defer srv.Stop()
|
||||
|
||||
// tell the server to connect
|
||||
tcpAddr := listener.Addr().(*net.TCPAddr)
|
||||
srv.AddPeer(&discover.Node{ID: remid, IP: tcpAddr.IP, TCP: uint16(tcpAddr.Port)})
|
||||
|
|
@ -263,21 +242,21 @@ func (t *testTask) Do(srv *Server) {
|
|||
// at capacity. Trusted connections should still be accepted.
|
||||
func TestServerAtCap(t *testing.T) {
|
||||
trustedID := randomID()
|
||||
srv := &Server{
|
||||
PrivateKey: newkey(),
|
||||
MaxPeers: 10,
|
||||
NoDial: true,
|
||||
TrustedNodes: []*discover.Node{{ID: trustedID}},
|
||||
}
|
||||
if err := srv.Start(); err != nil {
|
||||
t.Fatalf("could not start: %v", err)
|
||||
}
|
||||
quitPeers := make(chan struct{})
|
||||
srv := startTestServer(t, []*discover.Node{{ID: trustedID}}, func(*Peer) DiscReason {
|
||||
<-quitPeers
|
||||
return DiscQuitting
|
||||
})
|
||||
defer srv.Stop()
|
||||
defer close(quitPeers)
|
||||
|
||||
newconn := func(id discover.NodeID) *conn {
|
||||
fd, _ := net.Pipe()
|
||||
tx := newTestTransport(id, fd)
|
||||
return &conn{fd: fd, transport: tx, flags: inboundConn, id: id, cont: make(chan error)}
|
||||
return &conn{
|
||||
transport: &fakeTransport{id: id},
|
||||
flags: inboundConn,
|
||||
id: id,
|
||||
cont: make(chan error),
|
||||
}
|
||||
}
|
||||
|
||||
// Inject a few connections to fill up the peer set.
|
||||
|
|
@ -300,16 +279,15 @@ func TestServerAtCap(t *testing.T) {
|
|||
if !c.is(trustedConn) {
|
||||
t.Error("Server did not set trusted flag")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestServerSetupConn(t *testing.T) {
|
||||
id := randomID()
|
||||
srvkey := newkey()
|
||||
remkey, srvkey := newkey(), newkey()
|
||||
id := discover.PubkeyID(&remkey.PublicKey)
|
||||
srvid := discover.PubkeyID(&srvkey.PublicKey)
|
||||
tests := []struct {
|
||||
dontstart bool
|
||||
tt *setupTransport
|
||||
tt *fakeTransport
|
||||
flags connFlag
|
||||
dialDest *discover.Node
|
||||
|
||||
|
|
@ -318,45 +296,45 @@ func TestServerSetupConn(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
dontstart: true,
|
||||
tt: &setupTransport{id: id},
|
||||
tt: &fakeTransport{id: id},
|
||||
wantCalls: "close,",
|
||||
wantCloseErr: errServerStopped,
|
||||
},
|
||||
{
|
||||
tt: &setupTransport{id: id, encHandshakeErr: errors.New("read error")},
|
||||
tt: &fakeTransport{id: id, encHandshakeErr: errors.New("read error")},
|
||||
flags: inboundConn,
|
||||
wantCalls: "doEncHandshake,close,",
|
||||
wantCloseErr: errors.New("read error"),
|
||||
},
|
||||
{
|
||||
tt: &setupTransport{id: id},
|
||||
tt: &fakeTransport{id: id},
|
||||
dialDest: &discover.Node{ID: randomID()},
|
||||
flags: dynDialedConn,
|
||||
wantCalls: "doEncHandshake,close,",
|
||||
wantCloseErr: DiscUnexpectedIdentity,
|
||||
},
|
||||
{
|
||||
tt: &setupTransport{id: id, phs: &protoHandshake{ID: randomID()}},
|
||||
tt: &fakeTransport{id: id, phs: &protoHandshake{ID: randomID()}},
|
||||
dialDest: &discover.Node{ID: id},
|
||||
flags: dynDialedConn,
|
||||
wantCalls: "doEncHandshake,doProtoHandshake,close,",
|
||||
wantCloseErr: DiscUnexpectedIdentity,
|
||||
},
|
||||
{
|
||||
tt: &setupTransport{id: id, protoHandshakeErr: errors.New("foo")},
|
||||
tt: &fakeTransport{id: id, protoHandshakeErr: errors.New("foo")},
|
||||
dialDest: &discover.Node{ID: id},
|
||||
flags: dynDialedConn,
|
||||
wantCalls: "doEncHandshake,doProtoHandshake,close,",
|
||||
wantCloseErr: errors.New("foo"),
|
||||
},
|
||||
{
|
||||
tt: &setupTransport{id: srvid, phs: &protoHandshake{ID: srvid}},
|
||||
tt: &fakeTransport{id: srvid, phs: &protoHandshake{ID: srvid}},
|
||||
flags: inboundConn,
|
||||
wantCalls: "doEncHandshake,close,",
|
||||
wantCloseErr: DiscSelf,
|
||||
},
|
||||
{
|
||||
tt: &setupTransport{id: id, phs: &protoHandshake{ID: id}},
|
||||
tt: &fakeTransport{id: id, phs: &protoHandshake{ID: id}},
|
||||
flags: inboundConn,
|
||||
wantCalls: "doEncHandshake,doProtoHandshake,close,",
|
||||
wantCloseErr: DiscUselessPeer,
|
||||
|
|
@ -369,15 +347,13 @@ func TestServerSetupConn(t *testing.T) {
|
|||
MaxPeers: 10,
|
||||
NoDial: true,
|
||||
Protocols: []Protocol{discard},
|
||||
newTransport: func(fd net.Conn) transport { return test.tt },
|
||||
}
|
||||
if !test.dontstart {
|
||||
if err := srv.Start(); err != nil {
|
||||
t.Fatalf("couldn't start server: %v", err)
|
||||
}
|
||||
}
|
||||
p1, _ := net.Pipe()
|
||||
srv.setupConn(p1, test.flags, test.dialDest)
|
||||
srv.setupConn(test.tt, test.flags, test.dialDest)
|
||||
if !reflect.DeepEqual(test.tt.closeErr, test.wantCloseErr) {
|
||||
t.Errorf("test %d: close error mismatch: got %q, want %q", i, test.tt.closeErr, test.wantCloseErr)
|
||||
}
|
||||
|
|
@ -387,7 +363,7 @@ func TestServerSetupConn(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type setupTransport struct {
|
||||
type fakeTransport struct {
|
||||
id discover.NodeID
|
||||
encHandshakeErr error
|
||||
|
||||
|
|
@ -398,28 +374,30 @@ type setupTransport struct {
|
|||
closeErr error
|
||||
}
|
||||
|
||||
func (c *setupTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *discover.Node) (discover.NodeID, error) {
|
||||
func (c *fakeTransport) Handshake() error {
|
||||
c.calls += "doEncHandshake,"
|
||||
return c.id, c.encHandshakeErr
|
||||
return c.encHandshakeErr
|
||||
}
|
||||
func (c *setupTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) {
|
||||
func (c *fakeTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) {
|
||||
c.calls += "doProtoHandshake,"
|
||||
if c.protoHandshakeErr != nil {
|
||||
return nil, c.protoHandshakeErr
|
||||
}
|
||||
return c.phs, nil
|
||||
}
|
||||
func (c *setupTransport) close(err error) {
|
||||
func (c *fakeTransport) close(err error) {
|
||||
c.calls += "close,"
|
||||
c.closeErr = err
|
||||
}
|
||||
|
||||
// setupConn shouldn't write to/read from the connection.
|
||||
func (c *setupTransport) WriteMsg(Msg) error {
|
||||
panic("WriteMsg called on setupTransport")
|
||||
func (c *fakeTransport) RemoteID() *ecdsa.PublicKey {
|
||||
key, _ := c.id.Pubkey()
|
||||
return key
|
||||
}
|
||||
func (c *setupTransport) ReadMsg() (Msg, error) {
|
||||
panic("ReadMsg called on setupTransport")
|
||||
func (c *fakeTransport) RemoteAddr() net.Addr {
|
||||
return &net.TCPAddr{Port: 33, IP: net.IP{0, 0, 0, 1}}
|
||||
}
|
||||
func (c *fakeTransport) LocalAddr() net.Addr {
|
||||
return &net.TCPAddr{Port: 44, IP: net.IP{0, 0, 0, 2}}
|
||||
}
|
||||
|
||||
func newkey() *ecdsa.PrivateKey {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,13 @@ func ListSize(contentSize uint64) uint64 {
|
|||
return uint64(headsize(contentSize)) + contentSize
|
||||
}
|
||||
|
||||
func IntSize(i uint64) uint64 {
|
||||
if i < 128 {
|
||||
return 1
|
||||
}
|
||||
return 1 + uint64(intsize(i))
|
||||
}
|
||||
|
||||
// Split returns the content of first RLP value and any
|
||||
// bytes after the value as subslices of b.
|
||||
func Split(b []byte) (k Kind, content, rest []byte, err error) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue