mirror of https://github.com/status-im/op-geth.git
463 lines
11 KiB
Go
463 lines
11 KiB
Go
package p2p
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/event"
|
|
"github.com/ethereum/go-ethereum/logger"
|
|
)
|
|
|
|
// peerAddr is the structure of a peer list element.
|
|
// It is also a valid net.Addr.
|
|
type peerAddr struct {
|
|
IP net.IP
|
|
Port uint64
|
|
Pubkey []byte // optional
|
|
}
|
|
|
|
func newPeerAddr(addr net.Addr, pubkey []byte) *peerAddr {
|
|
n := addr.Network()
|
|
if n != "tcp" && n != "tcp4" && n != "tcp6" {
|
|
// for testing with non-TCP
|
|
return &peerAddr{net.ParseIP("127.0.0.1"), 30303, pubkey}
|
|
}
|
|
ta := addr.(*net.TCPAddr)
|
|
return &peerAddr{ta.IP, uint64(ta.Port), pubkey}
|
|
}
|
|
|
|
func (d peerAddr) Network() string {
|
|
if d.IP.To4() != nil {
|
|
return "tcp4"
|
|
} else {
|
|
return "tcp6"
|
|
}
|
|
}
|
|
|
|
func (d peerAddr) String() string {
|
|
return fmt.Sprintf("%v:%d", d.IP, d.Port)
|
|
}
|
|
|
|
func (d peerAddr) RlpData() interface{} {
|
|
return []interface{}{d.IP, d.Port, d.Pubkey}
|
|
}
|
|
|
|
// Peer represents a remote peer.
|
|
type Peer struct {
|
|
// Peers have all the log methods.
|
|
// Use them to display messages related to the peer.
|
|
*logger.Logger
|
|
|
|
infolock sync.Mutex
|
|
identity ClientIdentity
|
|
caps []Cap
|
|
listenAddr *peerAddr // what remote peer is listening on
|
|
dialAddr *peerAddr // non-nil if dialing
|
|
|
|
// The mutex protects the connection
|
|
// so only one protocol can write at a time.
|
|
writeMu sync.Mutex
|
|
conn net.Conn
|
|
bufconn *bufio.ReadWriter
|
|
|
|
// These fields maintain the running protocols.
|
|
protocols []Protocol
|
|
runBaseProtocol bool // for testing
|
|
|
|
runlock sync.RWMutex // protects running
|
|
running map[string]*proto
|
|
|
|
protoWG sync.WaitGroup
|
|
protoErr chan error
|
|
closed chan struct{}
|
|
disc chan DiscReason
|
|
|
|
activity event.TypeMux // for activity events
|
|
|
|
slot int // index into Server peer list
|
|
|
|
// These fields are kept so base protocol can access them.
|
|
// TODO: this should be one or more interfaces
|
|
ourID ClientIdentity // client id of the Server
|
|
ourListenAddr *peerAddr // listen addr of Server, nil if not listening
|
|
newPeerAddr chan<- *peerAddr // tell server about received peers
|
|
otherPeers func() []*Peer // should return the list of all peers
|
|
pubkeyHook func(*peerAddr) error // called at end of handshake to validate pubkey
|
|
}
|
|
|
|
// NewPeer returns a peer for testing purposes.
|
|
func NewPeer(id ClientIdentity, caps []Cap) *Peer {
|
|
conn, _ := net.Pipe()
|
|
peer := newPeer(conn, nil, nil)
|
|
peer.setHandshakeInfo(id, nil, caps)
|
|
close(peer.closed)
|
|
return peer
|
|
}
|
|
|
|
func newServerPeer(server *Server, conn net.Conn, dialAddr *peerAddr) *Peer {
|
|
p := newPeer(conn, server.Protocols, dialAddr)
|
|
p.ourID = server.Identity
|
|
p.newPeerAddr = server.peerConnect
|
|
p.otherPeers = server.Peers
|
|
p.pubkeyHook = server.verifyPeer
|
|
p.runBaseProtocol = true
|
|
|
|
// laddr can be updated concurrently by NAT traversal.
|
|
// newServerPeer must be called with the server lock held.
|
|
if server.laddr != nil {
|
|
p.ourListenAddr = newPeerAddr(server.laddr, server.Identity.Pubkey())
|
|
}
|
|
return p
|
|
}
|
|
|
|
func newPeer(conn net.Conn, protocols []Protocol, dialAddr *peerAddr) *Peer {
|
|
p := &Peer{
|
|
Logger: logger.NewLogger("P2P " + conn.RemoteAddr().String()),
|
|
conn: conn,
|
|
dialAddr: dialAddr,
|
|
bufconn: bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)),
|
|
protocols: protocols,
|
|
running: make(map[string]*proto),
|
|
disc: make(chan DiscReason),
|
|
protoErr: make(chan error),
|
|
closed: make(chan struct{}),
|
|
}
|
|
return p
|
|
}
|
|
|
|
// Identity returns the client identity of the remote peer. The
|
|
// identity can be nil if the peer has not yet completed the
|
|
// handshake.
|
|
func (p *Peer) Identity() ClientIdentity {
|
|
p.infolock.Lock()
|
|
defer p.infolock.Unlock()
|
|
return p.identity
|
|
}
|
|
|
|
// Caps returns the capabilities (supported subprotocols) of the remote peer.
|
|
func (p *Peer) Caps() []Cap {
|
|
p.infolock.Lock()
|
|
defer p.infolock.Unlock()
|
|
return p.caps
|
|
}
|
|
|
|
func (p *Peer) setHandshakeInfo(id ClientIdentity, laddr *peerAddr, caps []Cap) {
|
|
p.infolock.Lock()
|
|
p.identity = id
|
|
p.listenAddr = laddr
|
|
p.caps = caps
|
|
p.infolock.Unlock()
|
|
}
|
|
|
|
// RemoteAddr returns the remote address of the network connection.
|
|
func (p *Peer) RemoteAddr() net.Addr {
|
|
return p.conn.RemoteAddr()
|
|
}
|
|
|
|
// LocalAddr returns the local address of the network connection.
|
|
func (p *Peer) LocalAddr() net.Addr {
|
|
return p.conn.LocalAddr()
|
|
}
|
|
|
|
// Disconnect terminates the peer connection with the given reason.
|
|
// It returns immediately and does not wait until the connection is closed.
|
|
func (p *Peer) Disconnect(reason DiscReason) {
|
|
select {
|
|
case p.disc <- reason:
|
|
case <-p.closed:
|
|
}
|
|
}
|
|
|
|
// String implements fmt.Stringer.
|
|
func (p *Peer) String() string {
|
|
kind := "inbound"
|
|
p.infolock.Lock()
|
|
if p.dialAddr != nil {
|
|
kind = "outbound"
|
|
}
|
|
p.infolock.Unlock()
|
|
return fmt.Sprintf("Peer(%p %v %s)", p, p.conn.RemoteAddr(), kind)
|
|
}
|
|
|
|
const (
|
|
// maximum amount of time allowed for reading a message
|
|
msgReadTimeout = 5 * time.Second
|
|
// maximum amount of time allowed for writing a message
|
|
msgWriteTimeout = 5 * time.Second
|
|
// messages smaller than this many bytes will be read at
|
|
// once before passing them to a protocol.
|
|
wholePayloadSize = 64 * 1024
|
|
)
|
|
|
|
var (
|
|
inactivityTimeout = 2 * time.Second
|
|
disconnectGracePeriod = 2 * time.Second
|
|
)
|
|
|
|
func (p *Peer) loop() (reason DiscReason, err error) {
|
|
defer p.activity.Stop()
|
|
defer p.closeProtocols()
|
|
defer close(p.closed)
|
|
defer p.conn.Close()
|
|
|
|
// read loop
|
|
readMsg := make(chan Msg)
|
|
readErr := make(chan error)
|
|
readNext := make(chan bool, 1)
|
|
protoDone := make(chan struct{}, 1)
|
|
go p.readLoop(readMsg, readErr, readNext)
|
|
readNext <- true
|
|
|
|
if p.runBaseProtocol {
|
|
p.startBaseProtocol()
|
|
}
|
|
|
|
loop:
|
|
for {
|
|
select {
|
|
case msg := <-readMsg:
|
|
// a new message has arrived.
|
|
var wait bool
|
|
if wait, err = p.dispatch(msg, protoDone); err != nil {
|
|
p.Errorf("msg dispatch error: %v\n", err)
|
|
reason = discReasonForError(err)
|
|
break loop
|
|
}
|
|
if !wait {
|
|
// Msg has already been read completely, continue with next message.
|
|
readNext <- true
|
|
}
|
|
p.activity.Post(time.Now())
|
|
case <-protoDone:
|
|
// protocol has consumed the message payload,
|
|
// we can continue reading from the socket.
|
|
readNext <- true
|
|
|
|
case err := <-readErr:
|
|
// read failed. there is no need to run the
|
|
// polite disconnect sequence because the connection
|
|
// is probably dead anyway.
|
|
// TODO: handle write errors as well
|
|
return DiscNetworkError, err
|
|
case err = <-p.protoErr:
|
|
reason = discReasonForError(err)
|
|
break loop
|
|
case reason = <-p.disc:
|
|
break loop
|
|
}
|
|
}
|
|
|
|
// wait for read loop to return.
|
|
close(readNext)
|
|
<-readErr
|
|
// tell the remote end to disconnect
|
|
done := make(chan struct{})
|
|
go func() {
|
|
p.conn.SetDeadline(time.Now().Add(disconnectGracePeriod))
|
|
p.writeMsg(NewMsg(discMsg, reason), disconnectGracePeriod)
|
|
io.Copy(ioutil.Discard, p.conn)
|
|
close(done)
|
|
}()
|
|
select {
|
|
case <-done:
|
|
case <-time.After(disconnectGracePeriod):
|
|
}
|
|
return reason, err
|
|
}
|
|
|
|
func (p *Peer) readLoop(msgc chan<- Msg, errc chan<- error, unblock <-chan bool) {
|
|
for _ = range unblock {
|
|
p.conn.SetReadDeadline(time.Now().Add(msgReadTimeout))
|
|
if msg, err := readMsg(p.bufconn); err != nil {
|
|
errc <- err
|
|
} else {
|
|
msgc <- msg
|
|
}
|
|
}
|
|
close(errc)
|
|
}
|
|
|
|
func (p *Peer) dispatch(msg Msg, protoDone chan struct{}) (wait bool, err error) {
|
|
proto, err := p.getProto(msg.Code)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if msg.Size <= wholePayloadSize {
|
|
// optimization: msg is small enough, read all
|
|
// of it and move on to the next message
|
|
buf, err := ioutil.ReadAll(msg.Payload)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
msg.Payload = bytes.NewReader(buf)
|
|
proto.in <- msg
|
|
} else {
|
|
wait = true
|
|
pr := &eofSignal{msg.Payload, int64(msg.Size), protoDone}
|
|
msg.Payload = pr
|
|
proto.in <- msg
|
|
}
|
|
return wait, nil
|
|
}
|
|
|
|
func (p *Peer) startBaseProtocol() {
|
|
p.runlock.Lock()
|
|
defer p.runlock.Unlock()
|
|
p.running[""] = p.startProto(0, Protocol{
|
|
Length: baseProtocolLength,
|
|
Run: runBaseProtocol,
|
|
})
|
|
}
|
|
|
|
// startProtocols starts matching named subprotocols.
|
|
func (p *Peer) startSubprotocols(caps []Cap) {
|
|
sort.Sort(capsByName(caps))
|
|
|
|
p.runlock.Lock()
|
|
defer p.runlock.Unlock()
|
|
offset := baseProtocolLength
|
|
outer:
|
|
for _, cap := range caps {
|
|
for _, proto := range p.protocols {
|
|
if proto.Name == cap.Name &&
|
|
proto.Version == cap.Version &&
|
|
p.running[cap.Name] == nil {
|
|
p.running[cap.Name] = p.startProto(offset, proto)
|
|
offset += proto.Length
|
|
continue outer
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Peer) startProto(offset uint64, impl Protocol) *proto {
|
|
rw := &proto{
|
|
in: make(chan Msg),
|
|
offset: offset,
|
|
maxcode: impl.Length,
|
|
peer: p,
|
|
}
|
|
p.protoWG.Add(1)
|
|
go func() {
|
|
err := impl.Run(p, rw)
|
|
if err == nil {
|
|
p.Infof("protocol %q returned", impl.Name)
|
|
err = newPeerError(errMisc, "protocol returned")
|
|
} else {
|
|
p.Errorf("protocol %q error: %v\n", impl.Name, err)
|
|
}
|
|
select {
|
|
case p.protoErr <- err:
|
|
case <-p.closed:
|
|
}
|
|
p.protoWG.Done()
|
|
}()
|
|
return rw
|
|
}
|
|
|
|
// getProto finds the protocol responsible for handling
|
|
// the given message code.
|
|
func (p *Peer) getProto(code uint64) (*proto, error) {
|
|
p.runlock.RLock()
|
|
defer p.runlock.RUnlock()
|
|
for _, proto := range p.running {
|
|
if code >= proto.offset && code < proto.offset+proto.maxcode {
|
|
return proto, nil
|
|
}
|
|
}
|
|
return nil, newPeerError(errInvalidMsgCode, "%d", code)
|
|
}
|
|
|
|
func (p *Peer) closeProtocols() {
|
|
p.runlock.RLock()
|
|
for _, p := range p.running {
|
|
close(p.in)
|
|
}
|
|
p.runlock.RUnlock()
|
|
p.protoWG.Wait()
|
|
}
|
|
|
|
// writeProtoMsg sends the given message on behalf of the given named protocol.
|
|
func (p *Peer) writeProtoMsg(protoName string, msg Msg) error {
|
|
p.runlock.RLock()
|
|
proto, ok := p.running[protoName]
|
|
p.runlock.RUnlock()
|
|
if !ok {
|
|
return fmt.Errorf("protocol %s not handled by peer", protoName)
|
|
}
|
|
if msg.Code >= proto.maxcode {
|
|
return newPeerError(errInvalidMsgCode, "code %x is out of range for protocol %q", msg.Code, protoName)
|
|
}
|
|
msg.Code += proto.offset
|
|
return p.writeMsg(msg, msgWriteTimeout)
|
|
}
|
|
|
|
// writeMsg writes a message to the connection.
|
|
func (p *Peer) writeMsg(msg Msg, timeout time.Duration) error {
|
|
p.writeMu.Lock()
|
|
defer p.writeMu.Unlock()
|
|
p.conn.SetWriteDeadline(time.Now().Add(timeout))
|
|
if err := writeMsg(p.bufconn, msg); err != nil {
|
|
return newPeerError(errWrite, "%v", err)
|
|
}
|
|
return p.bufconn.Flush()
|
|
}
|
|
|
|
type proto struct {
|
|
name string
|
|
in chan Msg
|
|
maxcode, offset uint64
|
|
peer *Peer
|
|
}
|
|
|
|
func (rw *proto) WriteMsg(msg Msg) error {
|
|
if msg.Code >= rw.maxcode {
|
|
return newPeerError(errInvalidMsgCode, "not handled")
|
|
}
|
|
msg.Code += rw.offset
|
|
return rw.peer.writeMsg(msg, msgWriteTimeout)
|
|
}
|
|
|
|
func (rw *proto) EncodeMsg(code uint64, data ...interface{}) error {
|
|
return rw.WriteMsg(NewMsg(code, data))
|
|
}
|
|
|
|
func (rw *proto) ReadMsg() (Msg, error) {
|
|
msg, ok := <-rw.in
|
|
if !ok {
|
|
return msg, io.EOF
|
|
}
|
|
msg.Code -= rw.offset
|
|
return msg, nil
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
type eofSignal struct {
|
|
wrapped io.Reader
|
|
count int64
|
|
eof chan<- struct{}
|
|
}
|
|
|
|
// note: when using eofSignal to detect whether a message payload
|
|
// has been read, Read might not be called for zero sized messages.
|
|
|
|
func (r *eofSignal) Read(buf []byte) (int, error) {
|
|
n, err := r.wrapped.Read(buf)
|
|
r.count -= int64(n)
|
|
if (err != nil || r.count <= 0) && r.eof != nil {
|
|
r.eof <- struct{}{} // tell Peer that msg has been consumed
|
|
r.eof = nil
|
|
}
|
|
return n, err
|
|
}
|