go-ethereum/p2p/rlpx/rlpx.go
Felix Lange f296300509 p2p/rlpx: prefer returning new queued packets over errors
This fixes intermittent failures in the p2p tests.
2015-12-08 20:29:07 +01:00

408 lines
11 KiB
Go

// 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)
}