plugeth/p2p/protocol.go
2014-11-21 21:52:45 +01:00

315 lines
7.7 KiB
Go

package p2p
import (
"bytes"
"net"
"sort"
"time"
"github.com/ethereum/go-ethereum/ethutil"
)
// Protocol is implemented by P2P subprotocols.
type Protocol interface {
// Start is called when the protocol becomes active.
// It should read and write messages from rw.
// Messages must be fully consumed.
//
// The connection is closed when Start returns. It should return
// any protocol-level error (such as an I/O error) that is
// encountered.
Start(peer *Peer, rw MsgReadWriter) error
// Offset should return the number of message codes
// used by the protocol.
Offset() MsgCode
}
type MsgReader interface {
ReadMsg() (Msg, error)
}
type MsgWriter interface {
WriteMsg(Msg) error
}
// MsgReadWriter is passed to protocols. Protocol implementations can
// use it to write messages back to a connected peer.
type MsgReadWriter interface {
MsgReader
MsgWriter
}
type MsgHandler func(code MsgCode, data *ethutil.Value) error
// MsgLoop reads messages off the given reader and
// calls the handler function for each decoded message until
// it returns an error or the peer connection is closed.
//
// If a message is larger than the given maximum size, RunProtocol
// returns an appropriate error.n
func MsgLoop(r MsgReader, maxsize uint32, handler MsgHandler) error {
for {
msg, err := r.ReadMsg()
if err != nil {
return err
}
if msg.Size > maxsize {
return NewPeerError(InvalidMsg, "size %d exceeds maximum size of %d", msg.Size, maxsize)
}
value, err := msg.Data()
if err != nil {
return err
}
if err := handler(msg.Code, value); err != nil {
return err
}
}
}
// the ÐΞVp2p base protocol
type baseProtocol struct {
rw MsgReadWriter
peer *Peer
}
type bpMsg struct {
code MsgCode
data *ethutil.Value
}
const (
p2pVersion = 0
pingTimeout = 2 * time.Second
pingGracePeriod = 2 * time.Second
)
const (
// message codes
handshakeMsg = iota
discMsg
pingMsg
pongMsg
getPeersMsg
peersMsg
)
const (
baseProtocolOffset MsgCode = 16
baseProtocolMaxMsgSize = 500 * 1024
)
type DiscReason byte
const (
// Values are given explicitly instead of by iota because these values are
// defined by the wire protocol spec; it is easier for humans to ensure
// correctness when values are explicit.
DiscRequested = 0x00
DiscNetworkError = 0x01
DiscProtocolError = 0x02
DiscUselessPeer = 0x03
DiscTooManyPeers = 0x04
DiscAlreadyConnected = 0x05
DiscIncompatibleVersion = 0x06
DiscInvalidIdentity = 0x07
DiscQuitting = 0x08
DiscUnexpectedIdentity = 0x09
DiscSelf = 0x0a
DiscReadTimeout = 0x0b
DiscSubprotocolError = 0x10
)
var discReasonToString = [DiscSubprotocolError + 1]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 "Unknown"
}
return discReasonToString[d]
}
func (bp *baseProtocol) Offset() MsgCode {
return baseProtocolOffset
}
func (bp *baseProtocol) Start(peer *Peer, rw MsgReadWriter) error {
bp.peer, bp.rw = peer, rw
// Do the handshake.
// TODO: disconnect is valid before handshake, too.
rw.WriteMsg(bp.peer.server.handshakeMsg())
msg, err := rw.ReadMsg()
if err != nil {
return err
}
if msg.Code != handshakeMsg {
return NewPeerError(ProtocolBreach, " first message must be handshake")
}
data, err := msg.Data()
if err != nil {
return NewPeerError(InvalidMsg, "%v", err)
}
if err := bp.handleHandshake(data); err != nil {
return err
}
msgin := make(chan bpMsg)
done := make(chan error, 1)
go func() {
done <- MsgLoop(rw, baseProtocolMaxMsgSize,
func(code MsgCode, data *ethutil.Value) error {
msgin <- bpMsg{code, data}
return nil
})
}()
return bp.loop(msgin, done)
}
func (bp *baseProtocol) loop(msgin <-chan bpMsg, quit <-chan error) error {
logger.Debugf("pingpong keepalive started at %v\n", time.Now())
messenger := bp.rw.(*proto).messenger
pingTimer := time.NewTimer(pingTimeout)
pinged := true
for {
select {
case msg := <-msgin:
if err := bp.handle(msg.code, msg.data); err != nil {
return err
}
case err := <-quit:
return err
case <-messenger.pulse:
pingTimer.Reset(pingTimeout)
pinged = false
case <-pingTimer.C:
if pinged {
return NewPeerError(PingTimeout, "")
}
logger.Debugf("pinging at %v\n", time.Now())
if err := bp.rw.WriteMsg(NewMsg(pingMsg)); err != nil {
return NewPeerError(WriteError, "%v", err)
}
pinged = true
pingTimer.Reset(pingTimeout)
}
}
}
func (bp *baseProtocol) handle(code MsgCode, data *ethutil.Value) error {
switch code {
case handshakeMsg:
return NewPeerError(ProtocolBreach, " extra handshake received")
case discMsg:
logger.Infof("Disconnect requested from peer %v, reason", DiscReason(data.Get(0).Uint()))
bp.peer.server.PeerDisconnect() <- DisconnectRequest{
addr: bp.peer.Address,
reason: DiscRequested,
}
case pingMsg:
return bp.rw.WriteMsg(NewMsg(pongMsg))
case pongMsg:
// reply for ping
case getPeersMsg:
// Peer asked for list of connected peers.
peersRLP := bp.peer.server.encodedPeerList()
if peersRLP != nil {
msg := Msg{
Code: peersMsg,
Size: uint32(len(peersRLP)),
Payload: bytes.NewReader(peersRLP),
}
return bp.rw.WriteMsg(msg)
}
case peersMsg:
bp.handlePeers(data)
default:
return NewPeerError(InvalidMsgCode, "unknown message code %v", code)
}
return nil
}
func (bp *baseProtocol) handlePeers(data *ethutil.Value) {
it := data.NewIterator()
for it.Next() {
ip := net.IP(it.Value().Get(0).Bytes())
port := it.Value().Get(1).Uint()
address := &net.TCPAddr{IP: ip, Port: int(port)}
go bp.peer.server.PeerConnect(address)
}
}
func (bp *baseProtocol) handleHandshake(c *ethutil.Value) error {
var (
remoteVersion = c.Get(0).Uint()
id = c.Get(1).Str()
caps = c.Get(2)
port = c.Get(3).Uint()
pubkey = c.Get(4).Bytes()
)
// Check correctness of p2p protocol version
if remoteVersion != p2pVersion {
return NewPeerError(P2PVersionMismatch, "Require protocol %d, received %d\n", p2pVersion, remoteVersion)
}
// Handle the pub key (validation, uniqueness)
if len(pubkey) == 0 {
return NewPeerError(PubkeyMissing, "not supplied in handshake.")
}
if len(pubkey) != 64 {
return NewPeerError(PubkeyInvalid, "require 512 bit, got %v", len(pubkey)*8)
}
// self connect detection
if bytes.Compare(bp.peer.server.ClientIdentity().Pubkey()[1:], pubkey) == 0 {
return NewPeerError(PubkeyForbidden, "not allowed to connect to self")
}
// register pubkey on server. this also sets the pubkey on the peer (need lock)
if err := bp.peer.server.RegisterPubkey(bp.peer, pubkey); err != nil {
return NewPeerError(PubkeyForbidden, err.Error())
}
// check port
if bp.peer.Inbound {
uint16port := uint16(port)
if bp.peer.Port > 0 && bp.peer.Port != uint16port {
return NewPeerError(PortMismatch, "port mismatch: %v != %v", bp.peer.Port, port)
} else {
bp.peer.Port = uint16port
}
}
capsIt := caps.NewIterator()
for capsIt.Next() {
cap := capsIt.Value().Str()
bp.peer.Caps = append(bp.peer.Caps, cap)
}
sort.Strings(bp.peer.Caps)
bp.rw.(*proto).messenger.setRemoteProtocols(bp.peer.Caps)
bp.peer.Id = id
return nil
}