Skip to content

Commit 520f60c

Browse files
committed
node: send pings, disconnect peer if it doesn't complete handshake or pong in time (lian#132)
1 parent 8ebaff2 commit 520f60c

File tree

4 files changed

+44
-20
lines changed

4 files changed

+44
-20
lines changed

bin/bitcoin_node

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ optparse = OptionParser.new do |opts|
6767
options[:check_blocks] = check.to_i
6868
end
6969

70+
opts.on("--connection-timeout TIMEOUT",
71+
"Disconnect peer if it doesn't respond to handshake or ping within TIMEOUT seconds (default: 10)") do |timeout|
72+
options[:connection_timeout] = timeout.to_i
73+
end
74+
7075
opts.on("--ho", "--headers-only",
7176
"Download only block headers") do
7277
options[:headers_only] = true
@@ -109,7 +114,7 @@ optparse = OptionParser.new do |opts|
109114
end
110115
end
111116

112-
[:queue, :inv_queue, :blocks, :addrs, :connect].each do |name|
117+
[:queue, :inv_queue, :addrs, :connect, :relay, :ping].each do |name|
113118
opts.on("--i#{name.to_s[0]}", "--interval-#{name} [SECONDS]",
114119
"Interval for #{name} worker (default: #{options[:intervals][name]})") do |sec|
115120
options[:intervals][name] = sec.to_i

lib/bitcoin/network/command_handler.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -322,18 +322,19 @@ def handle_config
322322
# bitcoin_node connections
323323
def handle_connections
324324
@node.connections.sort{|x,y| y.uptime <=> x.uptime}.map{|c|
325-
{
325+
res = {
326326
type: c.direction, host: c.host, port: c.port, state: c.state,
327327
uptime: c.uptime,
328-
version: {
328+
version: c.version ? {
329329
version: c.version.version,
330330
services: c.version.services,
331331
time: c.version.time,
332332
nonce: c.version.nonce,
333333
block: c.version.last_block,
334-
client: (c.version.user_agent rescue '?'),
334+
client: (c.version.user_agent),
335335
relay: c.version.relay,
336-
}
336+
} : nil,
337+
latency: c.latency_ms.to_i,
337338
}
338339
}
339340
end

lib/bitcoin/network/connection_handler.rb

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ module Bitcoin::Network
77
# Node network connection to a peer. Handles all the communication with a specific peer.
88
class ConnectionHandler < EM::Connection
99

10-
LATENCY_MAX = (5*60*1000) # 5min in ms
11-
1210
include Bitcoin
1311
include Bitcoin::Storage
1412

@@ -35,10 +33,10 @@ def initialize node, host, port, direction
3533
@parser = Bitcoin::Protocol::Parser.new(self)
3634
@state = :new
3735
@version = nil
38-
@started = nil
36+
@started = Time.now
3937
@port, @host = *Socket.unpack_sockaddr_in(get_peername) if get_peername
4038
@ping_nonce = nil
41-
@latency_ms = nil
39+
@latency_ms = @node.config[:connection_timeout] * 1000
4240
@lock = Monitor.new
4341
@last_getblocks = [] # the last few getblocks messages received
4442
rescue
@@ -48,9 +46,7 @@ def initialize node, host, port, direction
4846

4947
# check if connection is wanted, begin handshake if it is, disconnect if not
5048
def post_init
51-
if incoming?
52-
begin_handshake
53-
end
49+
begin_handshake if incoming?
5450
rescue
5551
log.fatal { "Error in #post_init" }
5652
p $!; puts *$@
@@ -83,15 +79,23 @@ def unbind
8379
end
8480

8581
# begin handshake
86-
# TODO: disconnect if we don't complete within a reasonable time
8782
def begin_handshake
8883
if incoming? && !@node.accept_connections?
8984
return close_connection unless @node.config[:connect].include?([@host, @port.to_s])
9085
end
9186
log.info { "Established #{@direction} connection" }
9287
@node.connections << self
9388
@state = :handshake
89+
90+
EM.add_timer(@node.config[:connection_timeout]) do
91+
unless @state == :connected
92+
log.debug { "Handshake timed out." }
93+
close_connection
94+
end
95+
end
96+
9497
send_version
98+
9599
rescue
96100
log.fatal { "Error in #begin_handshake" }
97101
p $!; puts *$@
@@ -102,6 +106,7 @@ def complete_handshake
102106
if @state == :handshake
103107
log.debug { 'Handshake completed' }
104108
@state = :connected
109+
@latency_ms = Time.now - @started
105110
@started = Time.now
106111
@node.push_notification(:connection, info.merge(type: :connected))
107112
@node.addrs << addr
@@ -301,18 +306,23 @@ def send_getaddr
301306
send_data(Protocol.pkt("getaddr", ""))
302307
end
303308

304-
# send +ping+ message
305-
# TODO: wait for pong and disconnect if it doesn't arrive (and version is new enough)
306-
def send_ping
309+
# send +ping+ message. if +wait+ is true, wait for pong and disconnect
310+
# if it doesn't arrive in time (and version is new enough)
311+
def send_ping wait = true
307312
if @version.version > Bitcoin::Protocol::BIP0031_VERSION
308-
@latency_ms = LATENCY_MAX
309313
@ping_nonce = rand(0xffffffff)
310314
@ping_time = Time.now
311315
log.debug { "<< ping (#{@ping_nonce})" }
316+
EM.add_timer(@node.config[:connection_timeout]) do
317+
unless @latency_ms / 1000 < @node.config[:connection_timeout]
318+
log.debug { "Ping #{@ping_nonce} timed out." }
319+
close_connection
320+
end
321+
end
312322
send_data(Protocol.ping_pkt(@ping_nonce))
313323
else
314-
# set latency to 5 seconds, terrible but this version should be obsolete now
315-
@latency_ms = (5*1000)
324+
# set latency to maximum allowed, terrible but this version should be obsolete now
325+
@latency_ms = @node.config[:connection_timeout]
316326
log.debug { "<< ping" }
317327
send_data(Protocol.ping_pkt)
318328
end

lib/bitcoin/network/node.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,12 @@ class Node
8484
:addrs => 5,
8585
:connect => 5,
8686
:relay => 0,
87+
:ping => 90,
8788
},
8889
:import => nil,
8990
:skip_validation => false,
9091
:check_blocks => 1000,
92+
:connection_timeout => 10,
9193
}
9294

9395
def initialize config = {}
@@ -177,7 +179,7 @@ def uptime
177179

178180
def start_timers
179181
return EM.add_timer(1) { start_timers } if @importing
180-
[:queue, :inv_queue, :addrs, :connect, :relay].each do |name|
182+
[:queue, :inv_queue, :addrs, :connect, :relay, :ping].each do |name|
181183
interval = @config[:intervals][name].to_f
182184
next if !interval || interval == 0.0
183185
@timers[name] = EM.add_periodic_timer(interval, method("work_#{name}"))
@@ -379,6 +381,12 @@ def getblocks locator = store.get_locator
379381
end
380382
end
381383

384+
# send ping to every connected peer (which will disconnect if it doesn't respond in time)
385+
def work_ping
386+
log.debug { "ping worker running" }
387+
@connections.select(&:connected?).each(&:send_ping)
388+
end
389+
382390
# check if the addr store is full and request new addrs
383391
# from a random peer if it isn't
384392
def work_addrs

0 commit comments

Comments
 (0)