p2p: add DialRatio for configuration of inbound vs. dialed connections

This commit is contained in:
Felix Lange 2018-01-18 10:27:37 +01:00
parent 4e7c220e39
commit 02be17884b

View file

@ -40,11 +40,10 @@ const (
refreshPeersInterval = 30 * time.Second refreshPeersInterval = 30 * time.Second
staticPeerCheckInterval = 15 * time.Second staticPeerCheckInterval = 15 * time.Second
// Maximum number of concurrently handshaking inbound connections. // Connectivity defaults.
maxAcceptConns = 50
// Maximum number of concurrently dialing outbound connections.
maxActiveDialTasks = 16 maxActiveDialTasks = 16
defaultMaxPendingPeers = 50
defaultDialRatio = 3
// Maximum time allowed for reading a complete message. // Maximum time allowed for reading a complete message.
// This is effectively the amount of time a connection can be idle. // This is effectively the amount of time a connection can be idle.
@ -70,6 +69,11 @@ type Config struct {
// Zero defaults to preset values. // Zero defaults to preset values.
MaxPendingPeers int `toml:",omitempty"` MaxPendingPeers int `toml:",omitempty"`
// DialRatio controls the ratio of inbound to dialed connections.
// Example: a DialRatio of 2 allows 1/2 of connections to be dialed.
// Setting DialRatio to zero defaults it to 3.
DialRatio int `toml:",omitempty"`
// NoDiscovery can be used to disable the peer discovery mechanism. // NoDiscovery can be used to disable the peer discovery mechanism.
// Disabling is useful for protocol debugging (manual topology). // Disabling is useful for protocol debugging (manual topology).
NoDiscovery bool NoDiscovery bool
@ -476,10 +480,7 @@ func (srv *Server) Start() (err error) {
srv.DiscV5 = ntab srv.DiscV5 = ntab
} }
dynPeers := (srv.MaxPeers + 1) / 2 dynPeers := srv.maxDialedConns()
if srv.NoDiscovery {
dynPeers = 0
}
dialer := newDialState(srv.StaticNodes, srv.BootstrapNodes, srv.ntab, dynPeers, srv.NetRestrict) dialer := newDialState(srv.StaticNodes, srv.BootstrapNodes, srv.ntab, dynPeers, srv.NetRestrict)
// handshake // handshake
@ -536,6 +537,7 @@ func (srv *Server) run(dialstate dialer) {
defer srv.loopWG.Done() defer srv.loopWG.Done()
var ( var (
peers = make(map[discover.NodeID]*Peer) peers = make(map[discover.NodeID]*Peer)
inboundCount = 0
trusted = make(map[discover.NodeID]bool, len(srv.TrustedNodes)) trusted = make(map[discover.NodeID]bool, len(srv.TrustedNodes))
taskdone = make(chan task, maxActiveDialTasks) taskdone = make(chan task, maxActiveDialTasks)
runningTasks []task runningTasks []task
@ -621,14 +623,14 @@ running:
} }
// TODO: track in-progress inbound node IDs (pre-Peer) to avoid dialing them. // TODO: track in-progress inbound node IDs (pre-Peer) to avoid dialing them.
select { select {
case c.cont <- srv.encHandshakeChecks(peers, c): case c.cont <- srv.encHandshakeChecks(peers, inboundCount, c):
case <-srv.quit: case <-srv.quit:
break running break running
} }
case c := <-srv.addpeer: case c := <-srv.addpeer:
// At this point the connection is past the protocol handshake. // At this point the connection is past the protocol handshake.
// Its capabilities are known and the remote identity is verified. // Its capabilities are known and the remote identity is verified.
err := srv.protoHandshakeChecks(peers, c) err := srv.protoHandshakeChecks(peers, inboundCount, c)
if err == nil { if err == nil {
// The handshakes are done and it passed all checks. // The handshakes are done and it passed all checks.
p := newPeer(c, srv.Protocols) p := newPeer(c, srv.Protocols)
@ -639,8 +641,11 @@ running:
} }
name := truncateName(c.name) name := truncateName(c.name)
srv.log.Debug("Adding p2p peer", "name", name, "addr", c.fd.RemoteAddr(), "peers", len(peers)+1) srv.log.Debug("Adding p2p peer", "name", name, "addr", c.fd.RemoteAddr(), "peers", len(peers)+1)
peers[c.id] = p
go srv.runPeer(p) go srv.runPeer(p)
peers[c.id] = p
if p.Inbound() {
inboundCount++
}
} }
// The dialer logic relies on the assumption that // The dialer logic relies on the assumption that
// dial tasks complete after the peer has been added or // dial tasks complete after the peer has been added or
@ -655,6 +660,9 @@ running:
d := common.PrettyDuration(mclock.Now() - pd.created) d := common.PrettyDuration(mclock.Now() - pd.created)
pd.log.Debug("Removing p2p peer", "duration", d, "peers", len(peers)-1, "req", pd.requested, "err", pd.err) pd.log.Debug("Removing p2p peer", "duration", d, "peers", len(peers)-1, "req", pd.requested, "err", pd.err)
delete(peers, pd.ID()) delete(peers, pd.ID())
if pd.Inbound() {
inboundCount--
}
} }
} }
@ -681,20 +689,22 @@ running:
} }
} }
func (srv *Server) protoHandshakeChecks(peers map[discover.NodeID]*Peer, c *conn) error { func (srv *Server) protoHandshakeChecks(peers map[discover.NodeID]*Peer, inboundCount int, c *conn) error {
// Drop connections with no matching protocols. // Drop connections with no matching protocols.
if len(srv.Protocols) > 0 && countMatchingProtocols(srv.Protocols, c.caps) == 0 { if len(srv.Protocols) > 0 && countMatchingProtocols(srv.Protocols, c.caps) == 0 {
return DiscUselessPeer return DiscUselessPeer
} }
// Repeat the encryption handshake checks because the // Repeat the encryption handshake checks because the
// peer set might have changed between the handshakes. // peer set might have changed between the handshakes.
return srv.encHandshakeChecks(peers, c) return srv.encHandshakeChecks(peers, inboundCount, c)
} }
func (srv *Server) encHandshakeChecks(peers map[discover.NodeID]*Peer, c *conn) error { func (srv *Server) encHandshakeChecks(peers map[discover.NodeID]*Peer, inboundCount int, c *conn) error {
switch { switch {
case !c.is(trustedConn|staticDialedConn) && len(peers) >= srv.MaxPeers: case !c.is(trustedConn|staticDialedConn) && len(peers) >= srv.MaxPeers:
return DiscTooManyPeers return DiscTooManyPeers
case !c.is(trustedConn) && c.is(inboundConn) && inboundCount >= srv.maxInboundConns():
return DiscTooManyPeers
case peers[c.id] != nil: case peers[c.id] != nil:
return DiscAlreadyConnected return DiscAlreadyConnected
case c.id == srv.Self().ID: case c.id == srv.Self().ID:
@ -704,6 +714,21 @@ func (srv *Server) encHandshakeChecks(peers map[discover.NodeID]*Peer, c *conn)
} }
} }
func (srv *Server) maxInboundConns() int {
return srv.MaxPeers - srv.maxDialedConns()
}
func (srv *Server) maxDialedConns() int {
if srv.NoDiscovery || srv.NoDial {
return 0
}
r := srv.DialRatio
if r == 0 {
r = defaultDialRatio
}
return srv.MaxPeers / r
}
type tempError interface { type tempError interface {
Temporary() bool Temporary() bool
} }
@ -714,10 +739,7 @@ func (srv *Server) listenLoop() {
defer srv.loopWG.Done() defer srv.loopWG.Done()
srv.log.Info("RLPx listener up", "self", srv.makeSelf(srv.listener, srv.ntab)) srv.log.Info("RLPx listener up", "self", srv.makeSelf(srv.listener, srv.ntab))
// This channel acts as a semaphore limiting tokens := defaultMaxPendingPeers
// active inbound connections that are lingering pre-handshake.
// If all slots are taken, no further connections are accepted.
tokens := maxAcceptConns
if srv.MaxPendingPeers > 0 { if srv.MaxPendingPeers > 0 {
tokens = srv.MaxPendingPeers tokens = srv.MaxPendingPeers
} }
@ -758,9 +780,6 @@ func (srv *Server) listenLoop() {
fd = newMeteredConn(fd, true) fd = newMeteredConn(fd, true)
srv.log.Trace("Accepted connection", "addr", fd.RemoteAddr()) srv.log.Trace("Accepted connection", "addr", fd.RemoteAddr())
// Spawn the handler. It will give the slot back when the connection
// has been established.
go func() { go func() {
srv.SetupConn(fd, inboundConn, nil) srv.SetupConn(fd, inboundConn, nil)
slots <- struct{}{} slots <- struct{}{}