go-ethereum/p2p/rlpx/framing.go
2015-12-07 15:00:33 +01:00

483 lines
13 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
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)
}