mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-24 22:32:15 +00:00
* OriginalAddr -> SocketAddr OriginalAddr records the originally dialed address for outbound peers, rather than the peer's self reported address. For inbound peers, it was nil. Here, we rename it to SocketAddr and for inbound peers, set it to the RemoteAddr of the connection. * use SocketAddr Numerous places in the code call peer.NodeInfo().NetAddress(). However, this call to NetAddress() may perform a DNS lookup if the reported NodeInfo.ListenAddr includes a name. Failure of this lookup returns a nil address, which can lead to panics in the code. Instead, call peer.SocketAddr() to return the static address of the connection. * remove nodeInfo.NetAddress() Expose `transport.NetAddress()`, a static result determined when the transport is created. Removing NetAddress() from the nodeInfo prevents accidental DNS lookups. * fixes from review * linter * fixes from review
388 lines
8.8 KiB
Go
388 lines
8.8 KiB
Go
package p2p
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"time"
|
|
|
|
cmn "github.com/tendermint/tendermint/libs/common"
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
|
|
tmconn "github.com/tendermint/tendermint/p2p/conn"
|
|
)
|
|
|
|
const metricsTickerDuration = 10 * time.Second
|
|
|
|
// Peer is an interface representing a peer connected on a reactor.
|
|
type Peer interface {
|
|
cmn.Service
|
|
FlushStop()
|
|
|
|
ID() ID // peer's cryptographic ID
|
|
RemoteIP() net.IP // remote IP of the connection
|
|
RemoteAddr() net.Addr // remote address of the connection
|
|
|
|
IsOutbound() bool // did we dial the peer
|
|
IsPersistent() bool // do we redial this peer when we disconnect
|
|
|
|
CloseConn() error // close original connection
|
|
|
|
NodeInfo() NodeInfo // peer's info
|
|
Status() tmconn.ConnectionStatus
|
|
SocketAddr() *NetAddress // actual address of the socket
|
|
|
|
Send(byte, []byte) bool
|
|
TrySend(byte, []byte) bool
|
|
|
|
Set(string, interface{})
|
|
Get(string) interface{}
|
|
}
|
|
|
|
//----------------------------------------------------------
|
|
|
|
// peerConn contains the raw connection and its config.
|
|
type peerConn struct {
|
|
outbound bool
|
|
persistent bool
|
|
conn net.Conn // source connection
|
|
|
|
socketAddr *NetAddress
|
|
|
|
// cached RemoteIP()
|
|
ip net.IP
|
|
}
|
|
|
|
func newPeerConn(
|
|
outbound, persistent bool,
|
|
conn net.Conn,
|
|
socketAddr *NetAddress,
|
|
) peerConn {
|
|
|
|
return peerConn{
|
|
outbound: outbound,
|
|
persistent: persistent,
|
|
conn: conn,
|
|
socketAddr: socketAddr,
|
|
}
|
|
}
|
|
|
|
// ID only exists for SecretConnection.
|
|
// NOTE: Will panic if conn is not *SecretConnection.
|
|
func (pc peerConn) ID() ID {
|
|
return PubKeyToID(pc.conn.(*tmconn.SecretConnection).RemotePubKey())
|
|
}
|
|
|
|
// Return the IP from the connection RemoteAddr
|
|
func (pc peerConn) RemoteIP() net.IP {
|
|
if pc.ip != nil {
|
|
return pc.ip
|
|
}
|
|
|
|
host, _, err := net.SplitHostPort(pc.conn.RemoteAddr().String())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
ips, err := net.LookupIP(host)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
pc.ip = ips[0]
|
|
|
|
return pc.ip
|
|
}
|
|
|
|
// peer implements Peer.
|
|
//
|
|
// Before using a peer, you will need to perform a handshake on connection.
|
|
type peer struct {
|
|
cmn.BaseService
|
|
|
|
// raw peerConn and the multiplex connection
|
|
peerConn
|
|
mconn *tmconn.MConnection
|
|
|
|
// peer's node info and the channel it knows about
|
|
// channels = nodeInfo.Channels
|
|
// cached to avoid copying nodeInfo in hasChannel
|
|
nodeInfo NodeInfo
|
|
channels []byte
|
|
|
|
// User data
|
|
Data *cmn.CMap
|
|
|
|
metrics *Metrics
|
|
metricsTicker *time.Ticker
|
|
}
|
|
|
|
type PeerOption func(*peer)
|
|
|
|
func newPeer(
|
|
pc peerConn,
|
|
mConfig tmconn.MConnConfig,
|
|
nodeInfo NodeInfo,
|
|
reactorsByCh map[byte]Reactor,
|
|
chDescs []*tmconn.ChannelDescriptor,
|
|
onPeerError func(Peer, interface{}),
|
|
options ...PeerOption,
|
|
) *peer {
|
|
p := &peer{
|
|
peerConn: pc,
|
|
nodeInfo: nodeInfo,
|
|
channels: nodeInfo.(DefaultNodeInfo).Channels, // TODO
|
|
Data: cmn.NewCMap(),
|
|
metricsTicker: time.NewTicker(metricsTickerDuration),
|
|
metrics: NopMetrics(),
|
|
}
|
|
|
|
p.mconn = createMConnection(
|
|
pc.conn,
|
|
p,
|
|
reactorsByCh,
|
|
chDescs,
|
|
onPeerError,
|
|
mConfig,
|
|
)
|
|
p.BaseService = *cmn.NewBaseService(nil, "Peer", p)
|
|
for _, option := range options {
|
|
option(p)
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
// String representation.
|
|
func (p *peer) String() string {
|
|
if p.outbound {
|
|
return fmt.Sprintf("Peer{%v %v out}", p.mconn, p.ID())
|
|
}
|
|
|
|
return fmt.Sprintf("Peer{%v %v in}", p.mconn, p.ID())
|
|
}
|
|
|
|
//---------------------------------------------------
|
|
// Implements cmn.Service
|
|
|
|
// SetLogger implements BaseService.
|
|
func (p *peer) SetLogger(l log.Logger) {
|
|
p.Logger = l
|
|
p.mconn.SetLogger(l)
|
|
}
|
|
|
|
// OnStart implements BaseService.
|
|
func (p *peer) OnStart() error {
|
|
if err := p.BaseService.OnStart(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := p.mconn.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
go p.metricsReporter()
|
|
return nil
|
|
}
|
|
|
|
// FlushStop mimics OnStop but additionally ensures that all successful
|
|
// .Send() calls will get flushed before closing the connection.
|
|
// NOTE: it is not safe to call this method more than once.
|
|
func (p *peer) FlushStop() {
|
|
p.metricsTicker.Stop()
|
|
p.BaseService.OnStop()
|
|
p.mconn.FlushStop() // stop everything and close the conn
|
|
}
|
|
|
|
// OnStop implements BaseService.
|
|
func (p *peer) OnStop() {
|
|
p.metricsTicker.Stop()
|
|
p.BaseService.OnStop()
|
|
p.mconn.Stop() // stop everything and close the conn
|
|
}
|
|
|
|
//---------------------------------------------------
|
|
// Implements Peer
|
|
|
|
// ID returns the peer's ID - the hex encoded hash of its pubkey.
|
|
func (p *peer) ID() ID {
|
|
return p.nodeInfo.ID()
|
|
}
|
|
|
|
// IsOutbound returns true if the connection is outbound, false otherwise.
|
|
func (p *peer) IsOutbound() bool {
|
|
return p.peerConn.outbound
|
|
}
|
|
|
|
// IsPersistent returns true if the peer is persitent, false otherwise.
|
|
func (p *peer) IsPersistent() bool {
|
|
return p.peerConn.persistent
|
|
}
|
|
|
|
// NodeInfo returns a copy of the peer's NodeInfo.
|
|
func (p *peer) NodeInfo() NodeInfo {
|
|
return p.nodeInfo
|
|
}
|
|
|
|
// SocketAddr returns the address of the socket.
|
|
// For outbound peers, it's the address dialed (after DNS resolution).
|
|
// For inbound peers, it's the address returned by the underlying connection
|
|
// (not what's reported in the peer's NodeInfo).
|
|
func (p *peer) SocketAddr() *NetAddress {
|
|
return p.peerConn.socketAddr
|
|
}
|
|
|
|
// Status returns the peer's ConnectionStatus.
|
|
func (p *peer) Status() tmconn.ConnectionStatus {
|
|
return p.mconn.Status()
|
|
}
|
|
|
|
// Send msg bytes to the channel identified by chID byte. Returns false if the
|
|
// send queue is full after timeout, specified by MConnection.
|
|
func (p *peer) Send(chID byte, msgBytes []byte) bool {
|
|
if !p.IsRunning() {
|
|
// see Switch#Broadcast, where we fetch the list of peers and loop over
|
|
// them - while we're looping, one peer may be removed and stopped.
|
|
return false
|
|
} else if !p.hasChannel(chID) {
|
|
return false
|
|
}
|
|
res := p.mconn.Send(chID, msgBytes)
|
|
if res {
|
|
p.metrics.PeerSendBytesTotal.With("peer_id", string(p.ID())).Add(float64(len(msgBytes)))
|
|
}
|
|
return res
|
|
}
|
|
|
|
// TrySend msg bytes to the channel identified by chID byte. Immediately returns
|
|
// false if the send queue is full.
|
|
func (p *peer) TrySend(chID byte, msgBytes []byte) bool {
|
|
if !p.IsRunning() {
|
|
return false
|
|
} else if !p.hasChannel(chID) {
|
|
return false
|
|
}
|
|
res := p.mconn.TrySend(chID, msgBytes)
|
|
if res {
|
|
p.metrics.PeerSendBytesTotal.With("peer_id", string(p.ID())).Add(float64(len(msgBytes)))
|
|
}
|
|
return res
|
|
}
|
|
|
|
// Get the data for a given key.
|
|
func (p *peer) Get(key string) interface{} {
|
|
return p.Data.Get(key)
|
|
}
|
|
|
|
// Set sets the data for the given key.
|
|
func (p *peer) Set(key string, data interface{}) {
|
|
p.Data.Set(key, data)
|
|
}
|
|
|
|
// hasChannel returns true if the peer reported
|
|
// knowing about the given chID.
|
|
func (p *peer) hasChannel(chID byte) bool {
|
|
for _, ch := range p.channels {
|
|
if ch == chID {
|
|
return true
|
|
}
|
|
}
|
|
// NOTE: probably will want to remove this
|
|
// but could be helpful while the feature is new
|
|
p.Logger.Debug(
|
|
"Unknown channel for peer",
|
|
"channel",
|
|
chID,
|
|
"channels",
|
|
p.channels,
|
|
)
|
|
return false
|
|
}
|
|
|
|
// CloseConn closes original connection. Used for cleaning up in cases where the peer had not been started at all.
|
|
func (p *peer) CloseConn() error {
|
|
return p.peerConn.conn.Close()
|
|
}
|
|
|
|
//---------------------------------------------------
|
|
// methods only used for testing
|
|
// TODO: can we remove these?
|
|
|
|
// CloseConn closes the underlying connection
|
|
func (pc *peerConn) CloseConn() {
|
|
pc.conn.Close() // nolint: errcheck
|
|
}
|
|
|
|
// RemoteAddr returns peer's remote network address.
|
|
func (p *peer) RemoteAddr() net.Addr {
|
|
return p.peerConn.conn.RemoteAddr()
|
|
}
|
|
|
|
// CanSend returns true if the send queue is not full, false otherwise.
|
|
func (p *peer) CanSend(chID byte) bool {
|
|
if !p.IsRunning() {
|
|
return false
|
|
}
|
|
return p.mconn.CanSend(chID)
|
|
}
|
|
|
|
//---------------------------------------------------
|
|
|
|
func PeerMetrics(metrics *Metrics) PeerOption {
|
|
return func(p *peer) {
|
|
p.metrics = metrics
|
|
}
|
|
}
|
|
|
|
func (p *peer) metricsReporter() {
|
|
for {
|
|
select {
|
|
case <-p.metricsTicker.C:
|
|
status := p.mconn.Status()
|
|
var sendQueueSize float64
|
|
for _, chStatus := range status.Channels {
|
|
sendQueueSize += float64(chStatus.SendQueueSize)
|
|
}
|
|
|
|
p.metrics.PeerPendingSendBytes.With("peer_id", string(p.ID())).Set(sendQueueSize)
|
|
case <-p.Quit():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// helper funcs
|
|
|
|
func createMConnection(
|
|
conn net.Conn,
|
|
p *peer,
|
|
reactorsByCh map[byte]Reactor,
|
|
chDescs []*tmconn.ChannelDescriptor,
|
|
onPeerError func(Peer, interface{}),
|
|
config tmconn.MConnConfig,
|
|
) *tmconn.MConnection {
|
|
|
|
onReceive := func(chID byte, msgBytes []byte) {
|
|
reactor := reactorsByCh[chID]
|
|
if reactor == nil {
|
|
// Note that its ok to panic here as it's caught in the conn._recover,
|
|
// which does onPeerError.
|
|
panic(fmt.Sprintf("Unknown channel %X", chID))
|
|
}
|
|
p.metrics.PeerReceiveBytesTotal.With("peer_id", string(p.ID())).Add(float64(len(msgBytes)))
|
|
reactor.Receive(chID, p, msgBytes)
|
|
}
|
|
|
|
onError := func(r interface{}) {
|
|
onPeerError(p, r)
|
|
}
|
|
|
|
return tmconn.NewMConnectionWithConfig(
|
|
conn,
|
|
chDescs,
|
|
onReceive,
|
|
onError,
|
|
config,
|
|
)
|
|
}
|