sping 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/bin/sping +11 -11
  3. data/lib/last_acks.rb +23 -10
  4. data/lib/session.rb +179 -183
  5. data/lib/session_manager.rb +231 -217
  6. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c84629573ffe3769a497d40dbe6e178daac6e3abfc537685459c80054c7a0db
4
- data.tar.gz: 9a9cc52ebb3e2fe2a0e51effe9ce3e00709970d64c8c9e9d24b346976cf3d079
3
+ metadata.gz: 74faa9103509651e47d2f098c22b66fae80ca1ba0b71b132aa636d26bf1e0f04
4
+ data.tar.gz: 9ae6b44c52ded9e1999eb3fe467216b6d640747d11a8b69f4eed864efeb30a03
5
5
  SHA512:
6
- metadata.gz: d9da0a47ba8b5df79e935ba5a9768051f6b84cfddd1a3d50e4386f74d4dd2c806ff9386d8c27268ab87b84a2f8a21def16af3889aed951dd8f59201bb7f21447
7
- data.tar.gz: b71abb2618ba86b862ff8125dd7d15c5c4d5a4f7de065b3d722d0cc2f8a62812ea7a8ffaa77c10737c1c61e9f29bd6b44cbc421f838b4b4f4d7ce225ed07b1e7
6
+ metadata.gz: c08fcd7358309abe6a7f67044bbf267f31cba483786dc387b78ef14e8ce32801b067bd2ca8e2a545e141d3ffe843d004a4427d1a91cba1cbdd8d154679344653
7
+ data.tar.gz: 56ea68be301c25d116657775aabf26a9fb9fdf41cf2224ff9dcd72be1b897f20b076e9d0f4a1154ec777c53f20eeff173ded69985c993c99169198290f66a092
data/bin/sping CHANGED
@@ -7,10 +7,10 @@ require 'msgpack'
7
7
  require_relative '../lib/session_manager'
8
8
 
9
9
  MessagePack::DefaultFactory.register_type(
10
- MessagePack::Timestamp::TYPE,
11
- Time,
12
- packer: MessagePack::Time::Packer,
13
- unpacker: MessagePack::Time::Unpacker
10
+ MessagePack::Timestamp::TYPE,
11
+ Time,
12
+ packer: MessagePack::Time::Packer,
13
+ unpacker: MessagePack::Time::Unpacker
14
14
  )
15
15
 
16
16
  $logger = Logger.new $stdout
@@ -21,18 +21,18 @@ sess_mgr.run
21
21
  sess_mgr.run_server
22
22
 
23
23
  ARGV.each do |host|
24
- sess_mgr.new_session(host)
24
+ sess_mgr.new_session(host)
25
25
  end
26
26
 
27
27
  Signal.trap('INT') do
28
- puts "Stop server..."
29
- sess_mgr.stop_server
28
+ puts 'Stop server...'
29
+ sess_mgr.stop_server
30
30
 
31
- puts "Stop sessions..."
32
- sess_mgr.stop_sessions
31
+ puts 'Stop sessions...'
32
+ sess_mgr.stop_sessions
33
33
 
34
- puts "Stop client..."
35
- sess_mgr.stop
34
+ puts 'Stop client...'
35
+ sess_mgr.stop
36
36
  end
37
37
 
38
38
  sess_mgr.join
data/lib/last_acks.rb CHANGED
@@ -2,20 +2,33 @@
2
2
  # sharable_constant_value: literal
3
3
 
4
4
  class LastAcks
5
+ attr_reader :size
5
6
 
6
- attr_reader :acks, :size
7
+ def initialize(size = 32)
8
+ @size = size
9
+ @acks = []
10
+ @acks_mutex = Mutex.new
7
11
 
8
- def initialize(size = 32)
9
- @size = 32
10
- @acks = []
12
+ @size.times do |index|
13
+ @acks[index] = {
14
+ 'R' => 0,
15
+ 'U' => Time.at(0),
16
+ 'X' => Time.at(0)
17
+ }
11
18
  end
19
+ end
12
20
 
13
- def add_ack(ack)
14
- @acks << ack
15
-
16
- if @acks.length > @size
17
- @acks.shift
18
- end
21
+ def acks
22
+ @acks_mutex.synchronize do
23
+ return @acks.dup
19
24
  end
25
+ end
26
+
27
+ def add_ack(ack)
28
+ @acks_mutex.synchronize do
29
+ @acks << ack
20
30
 
31
+ @acks.shift if @acks.length > @size
32
+ end
33
+ end
21
34
  end
data/lib/session.rb CHANGED
@@ -2,228 +2,224 @@
2
2
  # sharable_constant_value: literal
3
3
 
4
4
  module SPing
5
+ class SessionError < StandardError; end
6
+ class TCPHandshakeError < SessionError; end
5
7
 
6
- class SessionError < StandardError; end
7
- class TCPHandshakeError < SessionError; end
8
+ class Session
9
+ attr_reader :created, :last_rx, :madebyme
10
+ attr_accessor :session_id, :tcp_handshake_complete, :udp_handshake_complete
8
11
 
9
- class Session
12
+ require 'socket'
13
+ require 'timeout'
14
+ require 'msgpack'
15
+ require_relative 'last_acks'
10
16
 
11
- attr_reader :created, :last_rx, :madebyme
12
- attr_accessor :session_id, :tcp_handshake_complete, :udp_handshake_complete
17
+ def initialize(host, port, socket, madebyme)
18
+ @host = host
19
+ @port = port
20
+ @socket = socket
21
+ @created = Time.now
22
+ @madebyme = madebyme
13
23
 
14
- require 'socket'
15
- require 'timeout'
16
- require 'msgpack'
17
- require_relative 'last_acks'
24
+ @tcp_handshake_complete = false
25
+ @udp_handshake_complete = false
18
26
 
19
- def initialize(host, port, socket, madebyme)
20
- @host = host
21
- @port = port
22
- @socket = socket
23
- @created = Time.now
24
- @madebyme = madebyme
27
+ @last_acks = LastAcks.new
28
+ end
25
29
 
26
- @tcp_handshake_complete = false
27
- @udp_handshake_complete = false
30
+ def do_tcp_handshake1(socket, session_id)
31
+ socket.write "sping-0.3-https://codeberg.org/mark22k/sping\r\n"
32
+ socket.flush
28
33
 
29
- @last_acks = LastAcks.new
30
- end
34
+ invite = socket.readpartial 6
31
35
 
32
- def do_tcp_handshake1(socket, session_id)
33
- socket.write "sping-0.3-https://codeberg.org/mark22k/sping\r\n"
34
- socket.flush
36
+ raise TCPHandshakeError, 'Peer didn\'t invite us.' unless invite.chomp == 'INVITE'
35
37
 
36
- invite = socket.readpartial 6
38
+ @session_id = session_id
37
39
 
38
- if invite.chomp != "INVITE"
39
- raise TCPHandshakeError, 'Peer didn\'t invite us.'
40
- end
40
+ socket.write "#{session_id}\r\n"
41
+ socket.flush
41
42
 
42
- @session_id = session_id
43
+ socket.close
43
44
 
44
- socket.write "#{session_id}\r\n"
45
- socket.flush
45
+ @tcp_handshake_complete = true
46
+ end
46
47
 
47
- socket.close
48
+ def do_tcp_handshake2
49
+ socket = TCPSocket.new @host, @port
50
+ banner = socket.readpartial 9001
51
+ if banner.length > 9000
52
+ socket.close
53
+ raise TCPHandshakeError, 'Host banner too big'
54
+ end
48
55
 
49
- @tcp_handshake_complete = true
50
- end
56
+ unless banner.start_with? 'sping-0.3-'
57
+ socket.close
58
+ raise TCPHandshakeError, 'Host banner not sping'
59
+ end
51
60
 
52
- def do_tcp_handshake2
53
- socket = TCPSocket.new @host, @port
54
- banner = socket.readpartial 9001
55
- if banner.length > 9000
56
- socket.close
57
- raise TCPHandshakeError, 'Host banner too big'
58
- end
61
+ @remote_version = banner.chomp
62
+ $logger.info "Peer uses the following program version: #{@remote_version.dump}"
59
63
 
60
- if ! banner.start_with? 'sping-0.3-'
61
- socket.close
62
- raise TCPHandshakeError, 'Host banner not sping'
63
- end
64
+ socket.write "INVITE\r\n"
65
+ socket.flush
64
66
 
65
- @remote_version = banner.chomp
66
- $logger.info "Peer uses the following program version: #{@remote_version.dump}"
67
+ invite_buf = socket.readpartial 32
68
+ if invite_buf.length > 31 || invite_buf.empty?
69
+ socket.close
70
+ raise TCPHandshakeError, 'Invite banner wrong size'
71
+ end
67
72
 
68
- socket.write "INVITE\r\n"
69
- socket.flush
73
+ @session_id = invite_buf.chomp.to_i
70
74
 
71
- inviteBuf = socket.readpartial 32
72
- if inviteBuf.length > 31 || inviteBuf.length.zero?
73
- socket.close
74
- raise TCPHandshakeError, 'Invite banner wrong size'
75
- end
75
+ socket.close
76
76
 
77
- @session_id = inviteBuf.chomp.to_i
77
+ @tcp_handshake_complete = true
78
+ end
78
79
 
79
- socket.close
80
+ def set_endpoint(host, port)
81
+ @host = host
82
+ @port = port
83
+ end
80
84
 
81
- @tcp_handshake_complete = true
82
- end
85
+ def start_udp_handshake_sender(send_interval = 5)
86
+ raise 'UDP handshake sender is already running.' if @udp_handshake_sender
83
87
 
84
- def set_endpoint(host, port)
85
- @host = host
86
- @port = port
88
+ @udp_handshake_sender = Thread.new(send_interval) do |th_send_interval|
89
+ loop do
90
+ send_udp_handshake
91
+ sleep th_send_interval
87
92
  end
93
+ end
94
+ end
88
95
 
89
- def start_udp_handshake_sender(send_interval = 5)
90
- raise 'UDP handshake sender is already running.' if @udp_handshake_sender
96
+ def send_udp_handshake
97
+ packet = {
98
+ 'Y' => 'h'.ord,
99
+ 'M' => 11_181,
100
+ 'V' => 3,
101
+ 'S' => @session_id
102
+ }.to_msgpack
91
103
 
92
- @udp_handshake_sender = Thread.new(send_interval) do |send_interval|
93
- loop do
94
- self.send_udp_handshake
95
- sleep send_interval
96
- end
97
- end
98
- end
104
+ $logger.debug "Send UDP handshake to #{@host} port #{@port}."
105
+ @socket.send packet, 0, @host, @port
106
+ end
99
107
 
100
- def send_udp_handshake
101
- packet = {
102
- 'Y' => 'h'.ord,
103
- 'M' => 11181,
104
- 'V' => 3,
105
- 'S' => @session_id
106
- }.to_msgpack
108
+ def stop_udp_handshake_sender
109
+ raise 'UDP handshake sender is not running and therefore cannot be terminated.' unless @udp_handshake_sender
107
110
 
108
- $logger.debug "Send UDP handshake to #{@host} port #{@port}."
109
- @socket.send packet, 0, @host, @port
110
- end
111
+ @udp_handshake_sender.kill
112
+ @udp_handshake_sender = nil
113
+ end
111
114
 
112
- def stop_udp_handshake_sender
113
- raise 'UDP handshake sender is not running and therefore cannot be terminated.' unless @udp_handshake_sender
114
- @udp_handshake_sender.kill
115
- @udp_handshake_sender = nil
116
- end
115
+ def udp_handshake_recived
116
+ stop_udp_handshake_sender if @madebyme
117
+ @udp_handshake_complete = true
118
+ end
119
+
120
+ def start_pinger
121
+ raise 'Pinger is already running.' if @pinger
117
122
 
118
- def udp_handshake_recived
119
- stop_udp_handshake_sender if @madebyme
120
- @udp_handshake_complete = true
123
+ @pinger = Thread.new do
124
+ loop do
125
+ ping
126
+ sleep 1
121
127
  end
128
+ end
129
+ end
122
130
 
123
- def start_pinger
124
- raise 'Pinger is already running.' if @pinger
131
+ def stop_pinger
132
+ raise 'Pinger sender is not running and therefore cannot be terminated.' unless @pinger
125
133
 
126
- @pinger = Thread.new do
127
- loop do
128
- self.ping
129
- sleep 1
130
- end
131
- end
132
- end
134
+ @pinger.kill
135
+ @pinger = nil
136
+ end
133
137
 
134
- def stop_pinger
135
- raise 'Pinger sender is not running and therefore cannot be terminated.' unless @pinger
136
- @pinger.kill
137
- @pinger = nil
138
- end
138
+ def ping
139
+ current_id = (Time.now.to_i % 255) + 1
140
+ data = {
141
+ 'Y' => 't'.ord,
142
+ 'M' => 11_181,
143
+ 'S' => @session_id,
144
+ 'I' => current_id,
145
+ 'T' => Time.now,
146
+ 'E' => 0,
147
+ 'A' => @last_acks.acks
148
+ }.to_msgpack
149
+
150
+ $logger.debug "Send ping to #{@host} port #{@port}."
151
+ @socket.send data, 0, @host, @port
152
+ end
139
153
 
140
- def ping
141
- current_id = Time.now.to_i % 255 + 1
142
- data = {
143
- 'Y' => 't'.ord,
144
- 'M' => 11181,
145
- 'S' => @session_id,
146
- 'I' => current_id,
147
- 'T' => Time.now,
148
- 'E' => 0,
149
- 'A' => @last_acks.acks
150
- }.to_msgpack
151
-
152
- $logger.debug "Send ping to #{@host} port #{@port}."
153
- @socket.send data, 0, @host, @port
154
- end
154
+ def stop
155
+ @pinger&.kill
156
+ @udp_handshake_sender&.kill
157
+ end
158
+
159
+ def handle_ping(packet, rxtime, peeraddr)
160
+ if packet['E'] != 0
161
+ $logger.error "Package displays an error message: #{packet['E']} Processing aborted."
162
+ return
163
+ end
164
+
165
+ id = packet['I']
166
+ txtime = packet['T']
167
+ remote_last_acks = packet['A']
168
+
169
+ ack = {
170
+ 'R' => id,
171
+ 'U' => txtime,
172
+ 'X' => rxtime
173
+ }
174
+
175
+ @last_acks.add_ack(ack)
176
+ @last_rx = rxtime
177
+
178
+ # This is the A
179
+ # The peer is B
180
+ # TX => Time from A to B
181
+ # RX => Time from B to A
182
+
183
+ # Remove zero acks
184
+ remote_last_acks.reject! { |block_ack| block_ack['R'].zero? }
185
+
186
+ # Sort by TX time
187
+ remote_last_acks.sort_by! { |block_ack| block_ack['U'] }
188
+
189
+ newest_remote_ack = remote_last_acks.last.to_h
190
+
191
+ # Calculate loss
192
+ tx_loss = 0
193
+ rx_loss = 0
194
+ exchanges = 0
195
+
196
+ if remote_last_acks.length == 32
197
+ # We have enough data
198
+ exchanges = 32
199
+ remote_acks = remote_last_acks.map { |ack| ack['R'] }
200
+ local_acks = @last_acks.acks.map { |ack| ack['R'] }
201
+
202
+ tip_id = (Time.now.to_i % 255) + 1
203
+ starting = tip_id - 32
204
+
205
+ last_ids = if tip_id > 32
206
+ (starting..tip_id).to_a
207
+ else
208
+ ((255 + starting)..255).to_a + (1..tip_id).to_a
209
+ end
155
210
 
156
- def stop
157
- @pinger&.kill
158
- @udp_handshake_sender&.kill
211
+ last_ids[0...-1].each do |id|
212
+ tx_loss += 1 unless remote_acks.include? id
159
213
  end
160
214
 
161
- def handle_ping packet, rxtime, peeraddr
162
- if packet['E'] != 0
163
- $logger.error "Package displays an error message: #{packet['E']} Processing aborted."
164
- return
165
- end
166
-
167
- id = packet['I']
168
- txtime = packet['T']
169
- lastAcks = packet['A']
170
-
171
- ack = {
172
- 'R' => id,
173
- 'U' => txtime,
174
- 'X' => rxtime
175
- }
176
-
177
- @last_acks.add_ack(ack)
178
- @last_rx = rxtime
179
-
180
- # This is the A
181
- # The peer is B
182
- # TX => Time from A to B
183
- # RX => Time from B to A
184
-
185
- # Remove zero acks
186
- lastAcks.reject! { |ack| ack['R'] == 0 }
187
-
188
- # Sort by TX time
189
- lastAcks.sort_by! { |ack| ack['U'] }
190
-
191
- newest_remote_ack = lastAcks.last.to_h
192
-
193
- # Calculate loss
194
- tx_loss = 0
195
- rx_loss = 0
196
- exchanges = 0
197
-
198
- if lastAcks.length == 32
199
- # We have enough data
200
- exchanges = 32
201
- remote_acks = lastAcks.map { |ack| ack['R'] }
202
- local_acks = @last_acks.acks.map { |ack| ack['R'] }
203
-
204
- tip_id = Time.now.to_i % 255 + 1
205
- starting = tip_id - 32
206
-
207
- last_ids = nil
208
- if tip_id > 32
209
- last_ids = (starting..tip_id).to_a
210
- else
211
- last_ids = ((255 + starting)..255).to_a + (1..tip_id).to_a
212
- end
213
-
214
- last_ids[0...-1].each do |id|
215
- tx_loss += 1 unless remote_acks.include? id
216
- end
217
-
218
- last_ids[1..-1].each do |id|
219
- rx_loss += 1 unless local_acks.include? id
220
- end
221
- end
222
-
223
- tx_latency = ((newest_remote_ack['X'].to_f - newest_remote_ack['U'].to_f) * 1000.0).round(6)
224
- rx_latency = ((rxtime - txtime) * 1000.0).round(6)
225
- puts "[#{peeraddr}] RX: #{rx_latency}ms TX: #{tx_latency}ms [Loss RX: #{rx_loss}/#{exchanges} | Loss TX: #{tx_loss}/#{exchanges}]"
215
+ last_ids[1..].each do |id|
216
+ rx_loss += 1 unless local_acks.include? id
226
217
  end
218
+ end
227
219
 
220
+ tx_latency = ((newest_remote_ack['X'].to_f - newest_remote_ack['U'].to_f) * 1000.0).round(6)
221
+ rx_latency = ((rxtime - txtime) * 1000.0).round(6)
222
+ puts "[#{peeraddr}] RX: #{rx_latency}ms TX: #{tx_latency}ms [Loss RX: #{rx_loss}/#{exchanges} | Loss TX: #{tx_loss}/#{exchanges}]"
228
223
  end
224
+ end
229
225
  end
@@ -2,261 +2,275 @@
2
2
  # sharable_constant_value: literal
3
3
 
4
4
  module SPing
5
+ class SessionManagerError < StandardError; end
6
+ class OutOfSessions < SessionManagerError; end
5
7
 
6
- class SessionManagerError < StandardError; end
7
- class OutOfSessions < SessionManagerError; end
8
+ class SessionManager
9
+ require 'socket'
10
+ require 'timeout'
11
+ require_relative 'session'
8
12
 
9
- class SessionManager
13
+ def initialize(host = '::', port = 6924)
14
+ @host = host
15
+ @port = port
10
16
 
11
- require 'socket'
12
- require 'timeout'
13
- require_relative 'session'
17
+ @sessions = {}
18
+ @sessions_mutex = Mutex.new
14
19
 
15
- def initialize(port = 6924)
16
- @port = port
20
+ @socket = UDPSocket.new(Socket::AF_INET6)
21
+ @socket.bind @host, @port
22
+ end
17
23
 
18
- @sessions = {}
19
- @pingers = {}
24
+ def new_session(host, port = 6924)
25
+ $logger.info "Add new session for host #{host} port #{port}."
26
+ session = SPing::Session.new host, port, @socket, true
20
27
 
21
- @socket = UDPSocket.new(Socket::AF_INET6)
22
- @socket.bind "::", @port
23
- end
28
+ counter = 0
29
+ loop do
30
+ session_id = request_session session
31
+ break if session_id
24
32
 
25
- def new_session(host, port = 6924)
26
- $logger.info "Add new session for host #{host} port #{port}."
27
- session = SPing::Session.new host, port, @socket, true
28
-
29
- begin
30
- counter = 0
31
- loop do
32
- begin
33
- session.do_tcp_handshake2
34
- rescue Errno::ECONNRESET, EOFError, IO::Timeout, TCPHandshakeError => e
35
- $logger.warn "TCP handshake failed: #{e.message}"
36
- return
37
- end
38
- if ! @sessions.key? session.session_id
39
- break
40
- end
41
- if counter > 5
42
- raise OutOfSessions, 'The peer could not name a session ID that had not yet been assigned.'
43
- end
44
- counter += 1
45
- end
46
- rescue OutOfSessions => e
47
- $logger.error "Out of session: #{e.message}"
48
- return
49
- end
50
-
51
- $logger.info "TCP handshake for host #{host} port #{port} successful. Session ID is #{session.session_id}."
52
-
53
- $logger.info "Perform UDP handshake for session ID #{session.session_id}."
54
- session.start_udp_handshake_sender
55
-
56
- @sessions[session.session_id] = session
33
+ # rubocop:disable Style/IfUnlessModifier
34
+ if counter > 5
35
+ raise OutOfSessions, 'The peer could not name a session ID that had not yet been assigned.'
57
36
  end
37
+ # rubocop:enable Style/IfUnlessModifier
58
38
 
59
- def del_session(session_id)
60
- $logger.debug "Delete session with session id #{session_id}"
39
+ counter += 1
40
+ end
61
41
 
62
- @sessions[session_id].stop
63
- @sessions.delete(session_id)
64
- end
42
+ $logger.info "TCP handshake for host #{host} port #{port} successful. Session ID is #{session.session_id}."
65
43
 
66
- def stop_sessions
67
- @sessions.each do |session_id, session|
68
- session.stop
69
- end
70
- end
44
+ $logger.info "Perform UDP handshake for session ID #{session.session_id}."
45
+ session.start_udp_handshake_sender
46
+ rescue Errno::ECONNRESET, EOFError, IO::TimeoutError, TCPHandshakeError => e
47
+ $logger.warn "TCP handshake failed: #{e.message}"
48
+ rescue OutOfSessions => e
49
+ $logger.error "Out of session: #{e.message}"
50
+ end
71
51
 
72
- def join
73
- @runner&.join
74
- @gc_thread&.join
75
- @server_thread&.join
76
- end
52
+ def del_session(session_id)
53
+ $logger.debug "Delete session with session id #{session_id}"
77
54
 
78
- def run
79
- raise 'Client is already running.' if @runner
80
- raise 'GC is already running.' if @gc
81
-
82
- @runner = Thread.new do
83
- loop do
84
- Thread.new(@socket.recvfrom(10000)) do |buf|
85
- handle_packet buf
86
- end
87
- end
88
- end
89
- @gc_thread = Thread.new do
90
- loop do
91
- do_gc
92
- sleep 10
93
- end
94
- end
95
- end
55
+ @sessions_mutex.synchronize do
56
+ @sessions[session_id].stop
57
+ @sessions.delete(session_id)
58
+ end
59
+ end
96
60
 
97
- def run_server
98
- raise 'Server is already running.' if @server_thread
99
- raise 'Server already exist.' if @server
61
+ def stop_sessions
62
+ @sessions.each do |_session_id, session|
63
+ session.stop
64
+ end
65
+ end
100
66
 
101
- @server = TCPServer.new '::', @port
67
+ def join
68
+ @runner&.join
69
+ @gc_thread&.join
70
+ @server_thread&.join
71
+ end
102
72
 
103
- @server_thread = Thread.new do
104
- loop do
105
- Thread.new(@server.accept) do |client|
106
- handle_client client
107
- end
108
- end
109
- end
73
+ def run
74
+ raise 'Client is already running.' if @runner
75
+ raise 'GC is already running.' if @gc
110
76
 
111
- return @server_thread
77
+ @runner = Thread.new do
78
+ loop do
79
+ Thread.new(@socket.recvfrom(10_000)) do |buf|
80
+ handle_packet buf
81
+ end
112
82
  end
83
+ end
84
+ @gc_thread = Thread.new do
85
+ loop do
86
+ do_gc
87
+ sleep 10
88
+ end
89
+ end
90
+ end
113
91
 
114
- def stop_server
115
- raise 'Server thread is not running.' unless @server_thread
116
-
117
- @server_thread.kill
118
- @server_thread = nil
92
+ def run_server
93
+ raise 'Server is already running.' if @server_thread
94
+ raise 'Server already exist.' if @server
119
95
 
120
- raise 'Server is not running.' unless @server
96
+ @server = TCPServer.new @host, @port
121
97
 
122
- @server.close
123
- @server = nil
98
+ @server_thread = Thread.new do
99
+ loop do
100
+ Thread.new(@server.accept) do |client|
101
+ handle_client client
102
+ end
124
103
  end
104
+ end
125
105
 
126
- def stop
127
- raise 'Session manager is not running.' unless @runner
106
+ return @server_thread
107
+ end
128
108
 
129
- @runner.kill
130
- @runner = nil
109
+ def stop_server
110
+ raise 'Server thread is not running.' unless @server_thread
131
111
 
132
- raise 'Session GC is not running.' unless @gc_thread
112
+ @server_thread.kill
113
+ @server_thread = nil
133
114
 
134
- @gc_thread.kill
135
- @gc_thread = nil
136
- end
115
+ raise 'Server is not running.' unless @server
137
116
 
138
- def generate_session_id
139
- Timeout::timeout(30, OutOfSessions, 'No session ID could be generated which has not yet been used.') do
140
- session_id = rand(2 ** 32 - 1)
141
- while @sessions.key? session_id
142
- session_id = rand(2 ** 32 - 1)
143
- end
117
+ @server.close
118
+ @server = nil
119
+ end
120
+
121
+ def stop
122
+ raise 'Session manager is not running.' unless @runner
123
+
124
+ @runner.kill
125
+ @runner = nil
144
126
 
145
- return session_id
146
- end
127
+ raise 'Session GC is not running.' unless @gc_thread
128
+
129
+ @gc_thread.kill
130
+ @gc_thread = nil
131
+ end
132
+
133
+ def generate_session_id
134
+ Timeout.timeout(30, OutOfSessions, 'No session ID could be generated which has not yet been used.') do
135
+ loop do
136
+ session_id = rand (2**32) - 1
137
+ return session_id unless @sessions.key? session_id
147
138
  end
139
+ end
140
+ end
148
141
 
149
- private
150
-
151
- def do_gc
152
- $logger.debug 'Remove outdated sessions.'
153
- @sessions.keys.each do |session_id|
154
- session = @sessions[session_id]
155
- # It is possible that sessions no longer exist here, as we may have session IDs that have already been deleted.
156
- # However, we can ignore this aspect here, as we are the only function that deletes sessions.
157
- if ! (session.tcp_handshake_complete && session.udp_handshake_complete)
158
- # Handshake incomplete
159
- if (Time.now.to_i - session.created.to_i) > 60
160
- # TCP and/or UDP take more than 60 seconds.
161
- $logger.debug "UDP handshake for session id #{session_id} timed out."
162
- del_session session_id
163
- end
164
- elsif (Time.now.to_i - session.last_rx.to_i) > 30
165
- # 30 seconds have elapsed since the last ping from the peer was received. The peer is probably dead.
166
- $logger.debug "Session id #{session_id} without activity for over thirty seconds."
167
- del_session session_id
168
- end
169
- end
142
+ private
143
+
144
+ def do_gc
145
+ $logger.debug 'Remove outdated sessions.'
146
+ # rubocop:disable Style/HashEachMethods
147
+ # You cannot run through a map and edit it at the same time. Since this is necessary due to the threading,
148
+ # a snapshot of the key variable is run through.
149
+ @sessions.keys.each do |session_id|
150
+ # rubocop:enable Style/HashEachMethods
151
+
152
+ session = @sessions[session_id]
153
+ # It is possible that sessions no longer exist here, as we may have session IDs that have already been deleted.
154
+ # However, we can ignore this aspect here, as we are the only function that deletes sessions.
155
+ if !(session.tcp_handshake_complete && session.udp_handshake_complete)
156
+ # Handshake incomplete
157
+ if (Time.now.to_i - session.created.to_i) > 60
158
+ # TCP and/or UDP take more than 60 seconds.
159
+ $logger.debug "UDP handshake for session id #{session_id} timed out."
160
+ del_session session_id
161
+ end
162
+ elsif (Time.now.to_i - session.last_rx.to_i) > 30
163
+ # 30 seconds have elapsed since the last ping from the peer was received. The peer is probably dead.
164
+ $logger.debug "Session id #{session_id} without activity for over thirty seconds."
165
+ del_session session_id
170
166
  end
167
+ end
168
+ end
171
169
 
172
- def handle_packet buf
173
- rxtime = Time.now
174
-
175
- peeraddr = buf[1][2]
176
- peerport = buf[1][1]
177
- $logger.debug "Packet received from #{peeraddr} port #{peerport}."
178
-
179
- packet = MessagePack.unpack(buf[0])
180
-
181
- # Check whether the Magic Number is the correct one.
182
- if packet['M'] != 11181
183
- $logger.warn 'Package contains incorrect magic number. Processing is canceled.'
184
- return
185
- end
186
-
187
- type = packet['Y'].chr
188
- case type
189
- when 'h'
190
- handle_udp_handshake packet, peeraddr, peerport
191
- when 't'
192
- handle_ping packet, rxtime, peeraddr, peerport
193
- else
194
- $logger.warn "Package is of unknown type: #{type}"
195
- end
170
+ def request_session(session)
171
+ session.do_tcp_handshake2
172
+
173
+ @sessions_mutex.synchronize do
174
+ unless @sessions.key? session.session_id
175
+ @sessions[session.session_id] = session
176
+ return session.session_id
196
177
  end
178
+ end
179
+
180
+ return nil
181
+ end
182
+
183
+ def handle_packet(buf)
184
+ rxtime = Time.now
185
+
186
+ peeraddr = buf[1][2]
187
+ peerport = buf[1][1]
188
+ $logger.debug "Packet received from #{peeraddr} port #{peerport}."
189
+
190
+ packet = MessagePack.unpack(buf[0])
191
+
192
+ # Check whether the Magic Number is the correct one.
193
+ if packet['M'] != 11_181
194
+ $logger.warn 'Package contains incorrect magic number. Processing is canceled.'
195
+ return
196
+ end
197
+
198
+ type = packet['Y'].chr
199
+ case type
200
+ when 'h'
201
+ handle_udp_handshake packet, peeraddr, peerport
202
+ when 't'
203
+ handle_ping packet, rxtime, peeraddr, peerport
204
+ else
205
+ $logger.warn "Package is of unknown type: #{type}"
206
+ end
207
+ end
197
208
 
198
- def handle_udp_handshake packet, peeraddr, peerport
199
- session_id = packet['S']
200
- session = @sessions[session_id]
201
- if session
202
- if packet['V'] != 3
203
- $logger.warn "UDP handshake uses an unsupported version: #{packet['V']}"
204
- return
205
- end
206
-
207
- session.set_endpoint peeraddr, peerport
208
-
209
- if ! session.madebyme
210
- # If the session was not started by me, the other peer expects a
211
- # confirmation of the handshake in which the same is sent back.
212
- $logger.debug "Send UDP Handshake back for session id #{session_id}."
213
- session.send_udp_handshake
214
- end
215
-
216
- if session.udp_handshake_complete
217
- $logger.warn 'UDP handshake is received, although a previous one was already successful.'
218
- return
219
- end
220
-
221
- session.udp_handshake_recived
222
- $logger.info "UDP handshake for session ID #{session_id} was successful."
223
-
224
- session.start_pinger
225
- else
226
- $logger.warn 'UDP handshake received for uninitiated session.'
227
- return
228
- end
209
+ def handle_udp_handshake(packet, peeraddr, peerport)
210
+ session_id = packet['S']
211
+ session = @sessions[session_id]
212
+ if session
213
+ if packet['V'] != 3
214
+ $logger.warn "UDP handshake uses an unsupported version: #{packet['V']}"
215
+ return
229
216
  end
230
217
 
231
- def handle_ping packet, rxtime, peeraddr, peerport
232
- session_id = packet['S']
233
- session = @sessions[session_id]
234
- if session && session.tcp_handshake_complete && session.udp_handshake_complete
235
- session.set_endpoint peeraddr, peerport
236
- session.handle_ping packet, rxtime, peeraddr
237
- else
238
- $logger.warn "Ping packet received for non-initiated session id #{session_id}."
239
- end
218
+ session.set_endpoint peeraddr, peerport
219
+
220
+ unless session.madebyme
221
+ # If the session was not started by me, the other peer expects a
222
+ # confirmation of the handshake in which the same is sent back.
223
+ $logger.debug "Send UDP Handshake back for session id #{session_id}."
224
+ session.send_udp_handshake
240
225
  end
241
226
 
242
- def handle_client(client)
243
- host = client.peeraddr[3]
244
- port = client.peeraddr[1]
245
- session = SPing::Session.new host, port, @socket, false
246
-
247
- $logger.debug "Session request from host #{host} port #{port}."
248
-
249
- begin
250
- session.do_tcp_handshake1 client, self.generate_session_id
251
- rescue Errno::ECONNRESET, EOFError, IO::Timeout, TCPHandshakeError => e
252
- $logger.warn "Could not establish a new session with Peer: #{e.message}"
253
- return
254
- rescue OutOfSessions => e
255
- $logger.error "Could not establish a new session with Peer: #{e.message}"
256
- return
257
- end
258
-
259
- @sessions[session.session_id] = session
227
+ if session.udp_handshake_complete
228
+ $logger.warn 'UDP handshake is received, although a previous one was already successful.'
229
+ return
260
230
  end
231
+
232
+ session.udp_handshake_recived
233
+ $logger.info "UDP handshake for session ID #{session_id} was successful."
234
+
235
+ session.start_pinger
236
+ else
237
+ $logger.warn 'UDP handshake received for uninitiated session.'
238
+ return
239
+ end
240
+ end
241
+
242
+ def handle_ping(packet, rxtime, peeraddr, peerport)
243
+ session_id = packet['S']
244
+ session = @sessions[session_id]
245
+ if session&.tcp_handshake_complete && session&.udp_handshake_complete
246
+ session.set_endpoint peeraddr, peerport
247
+ session.handle_ping packet, rxtime, peeraddr
248
+ else
249
+ $logger.warn "Ping packet received for non-initiated session id #{session_id}."
250
+ end
251
+ end
252
+
253
+ def handle_client(client)
254
+ host = client.peeraddr[3]
255
+ port = client.peeraddr[1]
256
+ session = SPing::Session.new host, port, @socket, false
257
+
258
+ $logger.debug "Session request from host #{host} port #{port}."
259
+
260
+ session_id = nil
261
+
262
+ @sessions_mutex.synchronize do
263
+ session_id = generate_session_id
264
+ @sessions[session_id] = session
265
+ end
266
+
267
+ session.do_tcp_handshake1 client, session_id
268
+ rescue Errno::ECONNRESET, EOFError, IO::TimeoutError, TCPHandshakeError => e
269
+ $logger.warn "Could not establish a new session with Peer: #{e.message}"
270
+ del_session session_id
271
+ rescue OutOfSessions => e
272
+ $logger.error "Could not establish a new session with Peer: #{e.message}"
273
+ del_session session_id
261
274
  end
275
+ end
262
276
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sping
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marek Küthe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-07 00:00:00.000000000 Z
11
+ date: 2023-11-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack