sping 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
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: e5ab53904c197ff35bab0d5fc29f26b5ada7c1ded24548b30d42df12d3df6837
4
- data.tar.gz: 8bd57a7d33c5f39f1eb76ad2551550fafecc5836f373b3c3675648e620afebe1
3
+ metadata.gz: 74faa9103509651e47d2f098c22b66fae80ca1ba0b71b132aa636d26bf1e0f04
4
+ data.tar.gz: 9ae6b44c52ded9e1999eb3fe467216b6d640747d11a8b69f4eed864efeb30a03
5
5
  SHA512:
6
- metadata.gz: 90e021acc0d54dace745238c35566efacdd5be77d8db2f9ecd6e6aa1ed8d71ee89c552fb4674be23e1adcf244987154e1975364731eff78e76ad5dd7006ec3df
7
- data.tar.gz: 336910fef1e87548f060a4a70547b272d78362828274dd5b21ed011a821a66ed71c7ece447045ca717a169676fa872594cbbd9e649e701731c63cd812c7f7bba
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::TimeoutError, 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::TimeoutError, 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.2
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