Add an idle timer for peers.
If we don't hear from a peer for 5 minutes, we disconnect them. To keep traffic flowing we send a ping every 2 minutes if we have not send any other message that should get a reply.
This commit is contained in:
parent
bc89dedf9a
commit
5a9cc91e62
3 changed files with 120 additions and 26 deletions
|
@ -543,7 +543,7 @@ func (b *blockManager) handleInvMsg(imsg *invMsg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(gdmsg.InvList) > 0 {
|
if len(gdmsg.InvList) > 0 {
|
||||||
imsg.peer.QueueMessage(gdmsg)
|
imsg.peer.QueueMessage(gdmsg, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
140
peer.go
140
peer.go
|
@ -32,6 +32,14 @@ const (
|
||||||
// maxKnownInventory is the maximum number of items to keep in the known
|
// maxKnownInventory is the maximum number of items to keep in the known
|
||||||
// inventory cache.
|
// inventory cache.
|
||||||
maxKnownInventory = 20000
|
maxKnownInventory = 20000
|
||||||
|
|
||||||
|
// idleTimeoutMinutes is the number of minutes of inactivity before
|
||||||
|
// we time out a peer.
|
||||||
|
idleTimeoutMinutes = 5
|
||||||
|
|
||||||
|
// pingTimeoutMinutes is the number of minutes since we last sent a
|
||||||
|
// message requiring a reply before we will ping a host.
|
||||||
|
pingTimeoutMinutes = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// userAgent is the user agent string used to identify ourselves to other
|
// userAgent is the user agent string used to identify ourselves to other
|
||||||
|
@ -203,7 +211,7 @@ func (p *peer) pushVersionMsg() error {
|
||||||
// Advertise that we're a full node.
|
// Advertise that we're a full node.
|
||||||
msg.Services = btcwire.SFNodeNetwork
|
msg.Services = btcwire.SFNodeNetwork
|
||||||
|
|
||||||
p.QueueMessage(msg)
|
p.QueueMessage(msg, nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,7 +267,7 @@ func (p *peer) handleVersionMsg(msg *btcwire.MsgVersion) {
|
||||||
p.na = na
|
p.na = na
|
||||||
|
|
||||||
// Send verack.
|
// Send verack.
|
||||||
p.QueueMessage(btcwire.NewMsgVerAck())
|
p.QueueMessage(btcwire.NewMsgVerAck(), nil)
|
||||||
|
|
||||||
// Outbound connections.
|
// Outbound connections.
|
||||||
if !p.inbound {
|
if !p.inbound {
|
||||||
|
@ -283,7 +291,7 @@ func (p *peer) handleVersionMsg(msg *btcwire.MsgVersion) {
|
||||||
// include a timestamp with addresses.
|
// include a timestamp with addresses.
|
||||||
hasTimestamp := p.protocolVersion >= btcwire.NetAddressTimeVersion
|
hasTimestamp := p.protocolVersion >= btcwire.NetAddressTimeVersion
|
||||||
if p.server.addrManager.NeedMoreAddresses() && hasTimestamp {
|
if p.server.addrManager.NeedMoreAddresses() && hasTimestamp {
|
||||||
p.QueueMessage(btcwire.NewMsgGetAddr())
|
p.QueueMessage(btcwire.NewMsgGetAddr(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the address as a known good address.
|
// Mark the address as a known good address.
|
||||||
|
@ -307,7 +315,7 @@ func (p *peer) handleVersionMsg(msg *btcwire.MsgVersion) {
|
||||||
|
|
||||||
// pushTxMsg sends a tx message for the provided transaction hash to the
|
// pushTxMsg sends a tx message for the provided transaction hash to the
|
||||||
// connected peer. An error is returned if the transaction hash is not known.
|
// connected peer. An error is returned if the transaction hash is not known.
|
||||||
func (p *peer) pushTxMsg(sha *btcwire.ShaHash) error {
|
func (p *peer) pushTxMsg(sha *btcwire.ShaHash, doneChan chan bool) error {
|
||||||
// Attempt to fetch the requested transaction from the pool. A
|
// Attempt to fetch the requested transaction from the pool. A
|
||||||
// call could be made to check for existence first, but simply trying
|
// call could be made to check for existence first, but simply trying
|
||||||
// to fetch a missing transaction results in the same behavior.
|
// to fetch a missing transaction results in the same behavior.
|
||||||
|
@ -317,14 +325,14 @@ func (p *peer) pushTxMsg(sha *btcwire.ShaHash) error {
|
||||||
"pool: %v", sha, err)
|
"pool: %v", sha, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.QueueMessage(tx)
|
p.QueueMessage(tx, doneChan)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// pushBlockMsg sends a block message for the provided block hash to the
|
// pushBlockMsg sends a block message for the provided block hash to the
|
||||||
// connected peer. An error is returned if the block hash is not known.
|
// connected peer. An error is returned if the block hash is not known.
|
||||||
func (p *peer) pushBlockMsg(sha *btcwire.ShaHash) error {
|
func (p *peer) pushBlockMsg(sha *btcwire.ShaHash, doneChan chan bool) error {
|
||||||
// What should this function do about the rate limiting the
|
// What should this function do about the rate limiting the
|
||||||
// number of blocks queued for this peer?
|
// number of blocks queued for this peer?
|
||||||
// Current thought is have a counting mutex in the peer
|
// Current thought is have a counting mutex in the peer
|
||||||
|
@ -345,7 +353,15 @@ func (p *peer) pushBlockMsg(sha *btcwire.ShaHash) error {
|
||||||
sha, err)
|
sha, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.QueueMessage(blk.MsgBlock())
|
|
||||||
|
// We only send the channel for this message if we aren't sending
|
||||||
|
// an inv straight after.
|
||||||
|
var dc chan bool
|
||||||
|
sendInv := p.continueHash != nil && p.continueHash.IsEqual(sha)
|
||||||
|
if !sendInv {
|
||||||
|
dc = doneChan
|
||||||
|
}
|
||||||
|
p.QueueMessage(blk.MsgBlock(), dc)
|
||||||
|
|
||||||
// When the peer requests the final block that was advertised in
|
// When the peer requests the final block that was advertised in
|
||||||
// response to a getblocks message which requested more blocks than
|
// response to a getblocks message which requested more blocks than
|
||||||
|
@ -358,8 +374,13 @@ func (p *peer) pushBlockMsg(sha *btcwire.ShaHash) error {
|
||||||
invMsg := btcwire.NewMsgInv()
|
invMsg := btcwire.NewMsgInv()
|
||||||
iv := btcwire.NewInvVect(btcwire.InvTypeBlock, hash)
|
iv := btcwire.NewInvVect(btcwire.InvTypeBlock, hash)
|
||||||
invMsg.AddInvVect(iv)
|
invMsg.AddInvVect(iv)
|
||||||
p.QueueMessage(invMsg)
|
p.QueueMessage(invMsg, doneChan)
|
||||||
p.continueHash = nil
|
p.continueHash = nil
|
||||||
|
} else if doneChan != nil {
|
||||||
|
// Avoid deadlock when caller waits on channel.
|
||||||
|
go func() {
|
||||||
|
doneChan <- false
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -394,7 +415,7 @@ func (p *peer) PushGetBlocksMsg(locator btcchain.BlockLocator, stopHash *btcwire
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.QueueMessage(msg)
|
p.QueueMessage(msg, nil)
|
||||||
|
|
||||||
// Update the previous getblocks request information for filtering
|
// Update the previous getblocks request information for filtering
|
||||||
// duplicates.
|
// duplicates.
|
||||||
|
@ -422,7 +443,7 @@ func (p *peer) handleMemPoolMsg(msg *btcwire.MsgMemPool) {
|
||||||
|
|
||||||
// Send the inventory message if there is anything to send.
|
// Send the inventory message if there is anything to send.
|
||||||
if len(invMsg.InvList) > 0 {
|
if len(invMsg.InvList) > 0 {
|
||||||
p.QueueMessage(invMsg)
|
p.QueueMessage(invMsg, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -494,14 +515,20 @@ func (p *peer) handleInvMsg(msg *btcwire.MsgInv) {
|
||||||
func (p *peer) handleGetDataMsg(msg *btcwire.MsgGetData) {
|
func (p *peer) handleGetDataMsg(msg *btcwire.MsgGetData) {
|
||||||
notFound := btcwire.NewMsgNotFound()
|
notFound := btcwire.NewMsgNotFound()
|
||||||
|
|
||||||
|
doneChan := make(chan bool)
|
||||||
out:
|
out:
|
||||||
for _, iv := range msg.InvList {
|
for i, iv := range msg.InvList {
|
||||||
|
var c chan bool
|
||||||
|
// If this will be the last message we send.
|
||||||
|
if i == len(msg.InvList)-1 && len(notFound.InvList) == 0 {
|
||||||
|
c = doneChan
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
switch iv.Type {
|
switch iv.Type {
|
||||||
case btcwire.InvTypeTx:
|
case btcwire.InvTypeTx:
|
||||||
err = p.pushTxMsg(&iv.Hash)
|
err = p.pushTxMsg(&iv.Hash, c)
|
||||||
case btcwire.InvTypeBlock:
|
case btcwire.InvTypeBlock:
|
||||||
err = p.pushBlockMsg(&iv.Hash)
|
err = p.pushBlockMsg(&iv.Hash, c)
|
||||||
default:
|
default:
|
||||||
log.Warnf("PEER: Unknown type in inventory request %d",
|
log.Warnf("PEER: Unknown type in inventory request %d",
|
||||||
iv.Type)
|
iv.Type)
|
||||||
|
@ -512,8 +539,15 @@ out:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(notFound.InvList) != 0 {
|
if len(notFound.InvList) != 0 {
|
||||||
p.QueueMessage(notFound)
|
p.QueueMessage(notFound, doneChan)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for messages to be sent. We can send quite a lot of data at this
|
||||||
|
// point and this will keep the peer busy for a decent amount of time.
|
||||||
|
// We don't process anything else by them in this time so that we
|
||||||
|
// have an idea of when we should hear back from them - else the idle
|
||||||
|
// timeout could fire when we were only half done sending the blocks.
|
||||||
|
<-doneChan
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleGetBlocksMsg is invoked when a peer receives a getdata bitcoin message.
|
// handleGetBlocksMsg is invoked when a peer receives a getdata bitcoin message.
|
||||||
|
@ -592,11 +626,11 @@ func (p *peer) handleGetBlocksMsg(msg *btcwire.MsgGetBlocks) {
|
||||||
continueHash := invMsg.InvList[invListLen-1].Hash
|
continueHash := invMsg.InvList[invListLen-1].Hash
|
||||||
p.continueHash = &continueHash
|
p.continueHash = &continueHash
|
||||||
}
|
}
|
||||||
p.QueueMessage(invMsg)
|
p.QueueMessage(invMsg, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleGetBlocksMsg is invoked when a peer receives a getheaders bitcoin
|
// handleGetHeadersMsg is invoked when a peer receives a getheaders bitcoin
|
||||||
// message.
|
// message.
|
||||||
func (p *peer) handleGetHeadersMsg(msg *btcwire.MsgGetHeaders) {
|
func (p *peer) handleGetHeadersMsg(msg *btcwire.MsgGetHeaders) {
|
||||||
// Attempt to look up the height of the provided stop hash.
|
// Attempt to look up the height of the provided stop hash.
|
||||||
|
@ -621,7 +655,7 @@ func (p *peer) handleGetHeadersMsg(msg *btcwire.MsgGetHeaders) {
|
||||||
hdr := block.MsgBlock().Header // copy
|
hdr := block.MsgBlock().Header // copy
|
||||||
hdr.TxnCount = 0
|
hdr.TxnCount = 0
|
||||||
headersMsg.AddBlockHeader(&hdr)
|
headersMsg.AddBlockHeader(&hdr)
|
||||||
p.QueueMessage(headersMsg)
|
p.QueueMessage(headersMsg, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -683,7 +717,7 @@ func (p *peer) handleGetHeadersMsg(msg *btcwire.MsgGetHeaders) {
|
||||||
// next loop iteration.
|
// next loop iteration.
|
||||||
start += int64(len(hashList))
|
start += int64(len(hashList))
|
||||||
}
|
}
|
||||||
p.QueueMessage(headersMsg)
|
p.QueueMessage(headersMsg, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleGetAddrMsg is invoked when a peer receives a getaddr bitcoin message
|
// handleGetAddrMsg is invoked when a peer receives a getaddr bitcoin message
|
||||||
|
@ -727,14 +761,14 @@ func (p *peer) pushAddrMsg(addresses []*btcwire.NetAddress) error {
|
||||||
|
|
||||||
// Split into multiple messages as needed.
|
// Split into multiple messages as needed.
|
||||||
if numAdded > 0 && numAdded%btcwire.MaxAddrPerMsg == 0 {
|
if numAdded > 0 && numAdded%btcwire.MaxAddrPerMsg == 0 {
|
||||||
p.QueueMessage(msg)
|
p.QueueMessage(msg, nil)
|
||||||
msg.ClearAddresses()
|
msg.ClearAddresses()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send message with remaining addresses if needed.
|
// Send message with remaining addresses if needed.
|
||||||
if numAdded%btcwire.MaxAddrPerMsg != 0 {
|
if numAdded%btcwire.MaxAddrPerMsg != 0 {
|
||||||
p.QueueMessage(msg)
|
p.QueueMessage(msg, nil)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -789,7 +823,7 @@ func (p *peer) handlePingMsg(msg *btcwire.MsgPing) {
|
||||||
// Only Reply with pong is message comes from a new enough client.
|
// Only Reply with pong is message comes from a new enough client.
|
||||||
if p.protocolVersion > btcwire.BIP0031Version {
|
if p.protocolVersion > btcwire.BIP0031Version {
|
||||||
// Include nonce from ping so pong can be identified.
|
// Include nonce from ping so pong can be identified.
|
||||||
p.QueueMessage(btcwire.NewMsgPong(msg.Nonce))
|
p.QueueMessage(btcwire.NewMsgPong(msg.Nonce), nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -899,9 +933,20 @@ func (p *peer) isAllowedByRegression(err error) bool {
|
||||||
// inHandler handles all incoming messages for the peer. It must be run as a
|
// inHandler handles all incoming messages for the peer. It must be run as a
|
||||||
// goroutine.
|
// goroutine.
|
||||||
func (p *peer) inHandler() {
|
func (p *peer) inHandler() {
|
||||||
|
idleTimer := time.AfterFunc(idleTimeoutMinutes*time.Minute, func() {
|
||||||
|
// XXX technically very very very slightly racy, doesn't really
|
||||||
|
// matter.
|
||||||
|
if p.versionKnown {
|
||||||
|
log.Warnf("Peer %s no answer for %d minutes, "+
|
||||||
|
"disconnecting", idleTimeoutMinutes, p)
|
||||||
|
}
|
||||||
|
p.Disconnect()
|
||||||
|
})
|
||||||
out:
|
out:
|
||||||
for atomic.LoadInt32(&p.disconnect) == 0 {
|
for atomic.LoadInt32(&p.disconnect) == 0 {
|
||||||
rmsg, buf, err := p.readMessage()
|
rmsg, buf, err := p.readMessage()
|
||||||
|
// Stop the timer now, if we go around again we will reset it.
|
||||||
|
idleTimer.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// In order to allow regression tests with malformed
|
// In order to allow regression tests with malformed
|
||||||
// messages, don't disconnect the peer when we're in
|
// messages, don't disconnect the peer when we're in
|
||||||
|
@ -910,6 +955,7 @@ out:
|
||||||
if cfg.RegressionTest && p.isAllowedByRegression(err) {
|
if cfg.RegressionTest && p.isAllowedByRegression(err) {
|
||||||
log.Errorf("PEER: Allowed regression test "+
|
log.Errorf("PEER: Allowed regression test "+
|
||||||
"error: %v", err)
|
"error: %v", err)
|
||||||
|
idleTimer.Reset(idleTimeoutMinutes * time.Minute)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -998,8 +1044,13 @@ out:
|
||||||
}
|
}
|
||||||
p.server.addrManager.Connected(p.na)
|
p.server.addrManager.Connected(p.na)
|
||||||
}
|
}
|
||||||
|
// ok we got a message, reset the timer.
|
||||||
|
// timer just calls p.Disconnect() after logging.
|
||||||
|
idleTimer.Reset(idleTimeoutMinutes * time.Minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
idleTimer.Stop()
|
||||||
|
|
||||||
// Ensure connection is closed and notify server and block manager that
|
// Ensure connection is closed and notify server and block manager that
|
||||||
// the peer is done.
|
// the peer is done.
|
||||||
p.Disconnect()
|
p.Disconnect()
|
||||||
|
@ -1017,10 +1068,51 @@ out:
|
||||||
// allowing the sender to continue running asynchronously.
|
// allowing the sender to continue running asynchronously.
|
||||||
func (p *peer) outHandler() {
|
func (p *peer) outHandler() {
|
||||||
trickleTicker := time.NewTicker(time.Second * 10)
|
trickleTicker := time.NewTicker(time.Second * 10)
|
||||||
|
pingTimer := time.AfterFunc(pingTimeoutMinutes*time.Minute, func() {
|
||||||
|
nonce, err := btcwire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Not sending ping on timeout to %s: %v",
|
||||||
|
p, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.QueueMessage(btcwire.NewMsgPing(nonce), nil)
|
||||||
|
})
|
||||||
out:
|
out:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case msg := <-p.outputQueue:
|
case msg := <-p.outputQueue:
|
||||||
|
// If the message is one we should get a reply for
|
||||||
|
// then reset the timer, we only want to send pings
|
||||||
|
// when otherwise we would not recieve a reply from
|
||||||
|
// the peer. We specifically do not count block or inv
|
||||||
|
// messages here since they are not sure of a reply if
|
||||||
|
// the inv is of no interest explicitly solicited invs
|
||||||
|
// should elicit a reply but we don't track them
|
||||||
|
// specially.
|
||||||
|
reset := true
|
||||||
|
switch msg.msg.(type) {
|
||||||
|
case *btcwire.MsgVersion:
|
||||||
|
// should get an ack
|
||||||
|
case *btcwire.MsgGetAddr:
|
||||||
|
// should get addresses
|
||||||
|
case *btcwire.MsgPing:
|
||||||
|
// expects pong
|
||||||
|
case *btcwire.MsgMemPool:
|
||||||
|
// Should return an inv.
|
||||||
|
case *btcwire.MsgGetData:
|
||||||
|
// Should get us block, tx, or not found.
|
||||||
|
case *btcwire.MsgGetHeaders:
|
||||||
|
// Should get us headers back.
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Not one of the above, no sure reply.
|
||||||
|
// We want to ping if nothing else
|
||||||
|
// interesting happens.
|
||||||
|
reset = false
|
||||||
|
}
|
||||||
|
if reset {
|
||||||
|
pingTimer.Reset(pingTimeoutMinutes * time.Minute)
|
||||||
|
}
|
||||||
p.writeMessage(msg.msg)
|
p.writeMessage(msg.msg)
|
||||||
if msg.doneChan != nil {
|
if msg.doneChan != nil {
|
||||||
msg.doneChan <- true
|
msg.doneChan <- true
|
||||||
|
@ -1072,6 +1164,8 @@ out:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pingTimer.Stop()
|
||||||
|
|
||||||
// Drain any wait channels before we go away so we don't leave something
|
// Drain any wait channels before we go away so we don't leave something
|
||||||
// waiting for us.
|
// waiting for us.
|
||||||
cleanup:
|
cleanup:
|
||||||
|
@ -1091,8 +1185,8 @@ cleanup:
|
||||||
// QueueMessage adds the passed bitcoin message to the peer send queue. It
|
// QueueMessage adds the passed bitcoin message to the peer send queue. It
|
||||||
// uses a buffered channel to communicate with the output handler goroutine so
|
// uses a buffered channel to communicate with the output handler goroutine so
|
||||||
// it is automatically rate limited and safe for concurrent access.
|
// it is automatically rate limited and safe for concurrent access.
|
||||||
func (p *peer) QueueMessage(msg btcwire.Message) {
|
func (p *peer) QueueMessage(msg btcwire.Message, doneChan chan bool) {
|
||||||
p.outputQueue <- outMsg{msg: msg}
|
p.outputQueue <- outMsg{msg: msg, doneChan: doneChan}
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueueInventory adds the passed inventory to the inventory send queue which
|
// QueueInventory adds the passed inventory to the inventory send queue which
|
||||||
|
|
|
@ -193,7 +193,7 @@ func (s *server) handleBroadcastMsg(peers *list.List, bmsg *broadcastMsg) {
|
||||||
excluded = true
|
excluded = true
|
||||||
}
|
}
|
||||||
if !excluded {
|
if !excluded {
|
||||||
p.QueueMessage(bmsg.message)
|
p.QueueMessage(bmsg.message, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue