279 lines
6.4 KiB
Go
279 lines
6.4 KiB
Go
|
package p2p
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"net"
|
||
|
"sort"
|
||
|
"sync"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
type Protocol interface {
|
||
|
Start()
|
||
|
Stop()
|
||
|
HandleIn(*Msg, chan *Msg)
|
||
|
HandleOut(*Msg) bool
|
||
|
Offset() MsgCode
|
||
|
Name() string
|
||
|
}
|
||
|
|
||
|
const (
|
||
|
P2PVersion = 0
|
||
|
pingTimeout = 2
|
||
|
pingGracePeriod = 2
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
HandshakeMsg = iota
|
||
|
DiscMsg
|
||
|
PingMsg
|
||
|
PongMsg
|
||
|
GetPeersMsg
|
||
|
PeersMsg
|
||
|
offset = 16
|
||
|
)
|
||
|
|
||
|
type ProtocolState uint8
|
||
|
|
||
|
const (
|
||
|
nullState = iota
|
||
|
handshakeReceived
|
||
|
)
|
||
|
|
||
|
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 = map[DiscReason]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]
|
||
|
}
|
||
|
|
||
|
type BaseProtocol struct {
|
||
|
peer *Peer
|
||
|
state ProtocolState
|
||
|
stateLock sync.RWMutex
|
||
|
}
|
||
|
|
||
|
func NewBaseProtocol(peer *Peer) *BaseProtocol {
|
||
|
self := &BaseProtocol{
|
||
|
peer: peer,
|
||
|
}
|
||
|
|
||
|
return self
|
||
|
}
|
||
|
|
||
|
func (self *BaseProtocol) Start() {
|
||
|
if self.peer != nil {
|
||
|
self.peer.Write("", self.peer.Server().Handshake())
|
||
|
go self.peer.Messenger().PingPong(
|
||
|
pingTimeout*time.Second,
|
||
|
pingGracePeriod*time.Second,
|
||
|
self.Ping,
|
||
|
self.Timeout,
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (self *BaseProtocol) Stop() {
|
||
|
}
|
||
|
|
||
|
func (self *BaseProtocol) Ping() {
|
||
|
msg, _ := NewMsg(PingMsg)
|
||
|
self.peer.Write("", msg)
|
||
|
}
|
||
|
|
||
|
func (self *BaseProtocol) Timeout() {
|
||
|
self.peerError(PingTimeout, "")
|
||
|
}
|
||
|
|
||
|
func (self *BaseProtocol) Name() string {
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
func (self *BaseProtocol) Offset() MsgCode {
|
||
|
return offset
|
||
|
}
|
||
|
|
||
|
func (self *BaseProtocol) CheckState(state ProtocolState) bool {
|
||
|
self.stateLock.RLock()
|
||
|
self.stateLock.RUnlock()
|
||
|
if self.state != state {
|
||
|
return false
|
||
|
} else {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (self *BaseProtocol) HandleIn(msg *Msg, response chan *Msg) {
|
||
|
if msg.Code() == HandshakeMsg {
|
||
|
self.handleHandshake(msg)
|
||
|
} else {
|
||
|
if !self.CheckState(handshakeReceived) {
|
||
|
self.peerError(ProtocolBreach, "message code %v not allowed", msg.Code())
|
||
|
close(response)
|
||
|
return
|
||
|
}
|
||
|
switch msg.Code() {
|
||
|
case DiscMsg:
|
||
|
logger.Infof("Disconnect requested from peer %v, reason", DiscReason(msg.Data().Get(0).Uint()))
|
||
|
self.peer.Server().PeerDisconnect() <- DisconnectRequest{
|
||
|
addr: self.peer.Address,
|
||
|
reason: DiscRequested,
|
||
|
}
|
||
|
case PingMsg:
|
||
|
out, _ := NewMsg(PongMsg)
|
||
|
response <- out
|
||
|
case PongMsg:
|
||
|
case GetPeersMsg:
|
||
|
// Peer asked for list of connected peers
|
||
|
if out, err := self.peer.Server().PeersMessage(); err != nil {
|
||
|
response <- out
|
||
|
}
|
||
|
case PeersMsg:
|
||
|
self.handlePeers(msg)
|
||
|
default:
|
||
|
self.peerError(InvalidMsgCode, "unknown message code %v", msg.Code())
|
||
|
}
|
||
|
}
|
||
|
close(response)
|
||
|
}
|
||
|
|
||
|
func (self *BaseProtocol) HandleOut(msg *Msg) (allowed bool) {
|
||
|
// somewhat overly paranoid
|
||
|
allowed = msg.Code() == HandshakeMsg || msg.Code() == DiscMsg || msg.Code() < self.Offset() && self.CheckState(handshakeReceived)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (self *BaseProtocol) peerError(errorCode ErrorCode, format string, v ...interface{}) {
|
||
|
err := NewPeerError(errorCode, format, v...)
|
||
|
logger.Warnln(err)
|
||
|
fmt.Println(self.peer, err)
|
||
|
if self.peer != nil {
|
||
|
self.peer.PeerErrorChan() <- err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (self *BaseProtocol) handlePeers(msg *Msg) {
|
||
|
it := msg.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 self.peer.Server().PeerConnect(address)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (self *BaseProtocol) handleHandshake(msg *Msg) {
|
||
|
self.stateLock.Lock()
|
||
|
defer self.stateLock.Unlock()
|
||
|
if self.state != nullState {
|
||
|
self.peerError(ProtocolBreach, "extra handshake")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
c := msg.Data()
|
||
|
|
||
|
var (
|
||
|
p2pVersion = c.Get(0).Uint()
|
||
|
id = c.Get(1).Str()
|
||
|
caps = c.Get(2)
|
||
|
port = c.Get(3).Uint()
|
||
|
pubkey = c.Get(4).Bytes()
|
||
|
)
|
||
|
fmt.Printf("handshake received %v, %v, %v, %v, %v ", p2pVersion, id, caps, port, pubkey)
|
||
|
|
||
|
// Check correctness of p2p protocol version
|
||
|
if p2pVersion != P2PVersion {
|
||
|
self.peerError(P2PVersionMismatch, "Require protocol %d, received %d\n", P2PVersion, p2pVersion)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Handle the pub key (validation, uniqueness)
|
||
|
if len(pubkey) == 0 {
|
||
|
self.peerError(PubkeyMissing, "not supplied in handshake.")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if len(pubkey) != 64 {
|
||
|
self.peerError(PubkeyInvalid, "require 512 bit, got %v", len(pubkey)*8)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Self connect detection
|
||
|
if bytes.Compare(self.peer.Server().ClientIdentity().Pubkey()[1:], pubkey) == 0 {
|
||
|
self.peerError(PubkeyForbidden, "not allowed to connect to self")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// register pubkey on server. this also sets the pubkey on the peer (need lock)
|
||
|
if err := self.peer.Server().RegisterPubkey(self.peer, pubkey); err != nil {
|
||
|
self.peerError(PubkeyForbidden, err.Error())
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// check port
|
||
|
if self.peer.Inbound {
|
||
|
uint16port := uint16(port)
|
||
|
if self.peer.Port > 0 && self.peer.Port != uint16port {
|
||
|
self.peerError(PortMismatch, "port mismatch: %v != %v", self.peer.Port, port)
|
||
|
return
|
||
|
} else {
|
||
|
self.peer.Port = uint16port
|
||
|
}
|
||
|
}
|
||
|
|
||
|
capsIt := caps.NewIterator()
|
||
|
for capsIt.Next() {
|
||
|
cap := capsIt.Value().Str()
|
||
|
self.peer.Caps = append(self.peer.Caps, cap)
|
||
|
}
|
||
|
sort.Strings(self.peer.Caps)
|
||
|
self.peer.Messenger().AddProtocols(self.peer.Caps)
|
||
|
|
||
|
self.peer.Id = id
|
||
|
|
||
|
self.state = handshakeReceived
|
||
|
|
||
|
//p.ethereum.PushPeer(p)
|
||
|
// p.ethereum.reactor.Post("peerList", p.ethereum.Peers())
|
||
|
return
|
||
|
}
|