sping 1.0.4 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +16 -1
- data/lib/errors.rb +25 -1
- data/lib/last_acks.rb +14 -7
- data/lib/session.rb +93 -15
- data/lib/session_manager.rb +46 -10
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6dda8939b3290683a544ac5ba7192ca3ceab50489b3dbd62a0dd04696f6a7786
|
4
|
+
data.tar.gz: de774cc1260933cdcd386e39cfd040e0af8510ba9e73e30f615e2f8da27c074f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e100242edcfe0e85965c6520dc9e95bdcab2c1aea58e5df338eb1dba9efb2dca1b6de0c453a333aa83553b9187d62b731f6fbd4dd81510ee600572b7f0d8e3a5
|
7
|
+
data.tar.gz: 0d01578a50563b1e7f1de1219423e00f66ecffec49ccf25b74e0052df858dc317c71bea80dcec2e17762d66b4b7b97322d8a389484dc72ae158157de605098b0
|
data/README.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
1
|
# sping
|
2
2
|
|
3
|
-
|
3
|
+
Program for measuring asymmetric latencies.
|
4
|
+
|
5
|
+
This is a reimplementation in Ruby of the reference implementation of the sping protocol in Go. The program provides both the client and server part to measure asymmetric latencies between two peers.
|
6
|
+
|
7
|
+
## Protocol
|
8
|
+
|
9
|
+
The protocol consists of three phases:
|
10
|
+
1. A TCP handshake
|
11
|
+
2. A UDP handshake
|
12
|
+
2. Sending the pings
|
13
|
+
|
14
|
+
The TCP handshake is performed first. Here the peers negotiate a session ID. To do this, the client sends a request to the server and then invites it to a session. If the server accepts the invitation, it sends a session ID to the client. This is used to uniquely identify the connection between the peers. For example, a peer may change its port or IP address. The session ID is used so that the peers can continue to identify each other. The session is activated at the end of the TCP handshake.
|
15
|
+
|
16
|
+
A UDP handshake is then performed. In this, the client sends a UDP packet, which is encoded with [MessagePack](https://msgpack.org/), to the server. The server sends the packet back to the client for confirmation. One of the purposes of this is to punch a hole in a firewall so that it allows UDP packets between the peers. After a successful UDP handshake, the session is double activated. Only then does the sending of ping packets begin.
|
17
|
+
|
18
|
+
Finally, the peers send pings to each other (in this case UDP packets containing time messages). These time messages contain, among other things, the time at which the message was sent, but also the last 32 time messages received from the peer. This information can be used to determine both the asymmetric latency and the asymmetric packet loss.
|
data/lib/errors.rb
CHANGED
@@ -1,20 +1,44 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
# sharable_constant_value: literal
|
3
3
|
|
4
|
+
# SPing / Splitted Ping is a protocol for measuring asymmetric latencies.
|
4
5
|
module SPing
|
6
|
+
# Generic SPing error.
|
5
7
|
class SPingError < StandardError; end
|
6
8
|
|
9
|
+
# Error that occurred while managing the sessions.
|
7
10
|
class SessionManagerError < SPingError; end
|
11
|
+
|
12
|
+
# Error that is thrown when there are not enough session IDs available to establish a
|
13
|
+
# connection to a peer or to accept new ones.
|
14
|
+
# This indicates either a programmer error or an overload of the server.
|
8
15
|
class OutOfSessions < SessionManagerError; end
|
9
16
|
|
17
|
+
# Error that occurs in connection with a session.
|
10
18
|
class SessionError < SPingError; end
|
19
|
+
|
20
|
+
# Error during the TCP handshake
|
11
21
|
class TCPHandshakeError < SessionError; end
|
12
22
|
|
23
|
+
# Error due to the peer (i.e. not self-inflicted).
|
13
24
|
class PeerError < SPingError; end
|
25
|
+
|
26
|
+
# Package was not coded correctly.
|
14
27
|
class InvalidPacketError < PeerError; end
|
28
|
+
|
29
|
+
# The package was coded correctly, but the content is invalid.
|
15
30
|
class InvalidMessageError < InvalidPacketError; end
|
31
|
+
|
32
|
+
# A packet has been received which cannot be clearly assigned to SPing
|
33
|
+
# and therefore cannot be processed further.
|
16
34
|
class UnknownPacketError < InvalidPacketError; end
|
35
|
+
|
36
|
+
# The peer has signaled an error.
|
17
37
|
class SignalizedError < PeerError; end
|
18
|
-
|
38
|
+
|
39
|
+
# The peer uses a version of sping that is not supported.
|
40
|
+
class UnsupportedVersionError < PeerError; end
|
41
|
+
|
42
|
+
# The package could not be assigned to a current session.
|
19
43
|
class UnknownSessionError < PeerError; end
|
20
44
|
end
|
data/lib/last_acks.rb
CHANGED
@@ -1,16 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
# sharable_constant_value: literal
|
3
3
|
|
4
|
+
# SPing / Splitted Ping is a protocol for measuring asymmetric latencies.
|
4
5
|
module SPing
|
6
|
+
# A kind of circular buffer, which consists of 32 acks at any given time. If no acks are available,
|
7
|
+
# empty ones are created. If a new ack is added (which would result in 33 acks), one is removed.
|
5
8
|
class LastAcks
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
@size = size
|
9
|
+
# Creates a new circular buffer for acks
|
10
|
+
def initialize
|
11
|
+
# Array in which the acks are stored.
|
10
12
|
@acks = []
|
13
|
+
# Mutex, which ensures that the array is not accessed simultaneously.
|
11
14
|
@acks_mutex = Mutex.new
|
12
15
|
|
13
|
-
|
16
|
+
# Initiallize the circular buffer with 32 empty acks.
|
17
|
+
32.times do |index|
|
14
18
|
@acks[index] = {
|
15
19
|
'R' => 0,
|
16
20
|
'U' => Time.at(0),
|
@@ -19,17 +23,20 @@ module SPing
|
|
19
23
|
end
|
20
24
|
end
|
21
25
|
|
26
|
+
# Returns a copy of the last 32 acks.
|
27
|
+
# @return [Array] Last 32 Acks
|
22
28
|
def acks
|
23
29
|
@acks_mutex.synchronize do
|
24
30
|
return @acks.dup
|
25
31
|
end
|
26
32
|
end
|
27
33
|
|
34
|
+
# Adds a new ack and deletes the oldest one
|
35
|
+
# @param ack [Hash] New ack to be added
|
28
36
|
def add_ack(ack)
|
29
37
|
@acks_mutex.synchronize do
|
30
38
|
@acks << ack
|
31
|
-
|
32
|
-
@acks.shift if @acks.length > @size
|
39
|
+
@acks.shift
|
33
40
|
end
|
34
41
|
end
|
35
42
|
end
|
data/lib/session.rb
CHANGED
@@ -1,68 +1,122 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
# sharable_constant_value: literal
|
3
3
|
|
4
|
+
# SPing / Splitted Ping is a protocol for measuring asymmetric latencies.
|
4
5
|
module SPing
|
6
|
+
# Inclusion of all possible SPing-specific errors.
|
5
7
|
require_relative 'errors'
|
6
8
|
|
9
|
+
# Models or represents a session with a peer.
|
7
10
|
class Session
|
8
|
-
|
9
|
-
|
11
|
+
# Time when the session was created. This is necessary so that the session GC knows when a
|
12
|
+
# non-initialized session can be deleted.
|
13
|
+
# @return [Time]
|
14
|
+
attr_reader :created
|
15
|
+
# Time when the last ping packet was received by the peer. This is important so that the session
|
16
|
+
# GC knows when a session is considered inactive and can be deleted.
|
17
|
+
# @return [Time]
|
18
|
+
attr_reader :last_rx
|
19
|
+
# Indicates whether the session has been initialized. true if we have initiated it.
|
20
|
+
# false if the peer has initiated it.
|
21
|
+
# @return [TrueClass, FalseClass]
|
22
|
+
attr_reader :madebyme
|
23
|
+
# Indicates whether a TCP handshake has been carried out (successfully).
|
24
|
+
# @return [TrueClass, FalseClass]
|
25
|
+
attr_reader :tcp_handshake_complete
|
26
|
+
# Indicates whether a UDP handshake has been carried out (successfully).
|
27
|
+
# @return [TrueClass, FalseClass]
|
28
|
+
attr_reader :udp_handshake_complete
|
29
|
+
# The session ID in the range from 0 to 2**32 - 1.
|
30
|
+
# @return [Integer]
|
31
|
+
attr_reader :session_id
|
10
32
|
|
11
33
|
require 'socket'
|
12
34
|
require 'timeout'
|
13
35
|
require 'msgpack'
|
14
36
|
require_relative 'last_acks'
|
15
37
|
|
38
|
+
# Creates a new session
|
39
|
+
# @param host [#to_s] Host of the peer
|
40
|
+
# @param port [#to_i] Port of the peer
|
41
|
+
# @param socket [UDPSocket] UDP socket via which the pings and the UDP handshake are to be sent.
|
42
|
+
# @param madebyme [TrueClass, FalseClass] Indicates whether we have initiated the session.
|
16
43
|
def initialize(host, port, socket, madebyme)
|
17
|
-
|
18
|
-
@
|
44
|
+
# Assign the passed parameters to instance variables.
|
45
|
+
@host = host.to_s
|
46
|
+
@port = port.to_i
|
19
47
|
@socket = socket
|
20
|
-
@created = Time.now
|
21
48
|
@madebyme = madebyme
|
22
49
|
|
50
|
+
# Initialize assignment of other instance variables
|
51
|
+
@created = Time.now
|
52
|
+
@double_activated = nil
|
53
|
+
|
23
54
|
@tcp_handshake_complete = false
|
24
55
|
@udp_handshake_complete = false
|
25
56
|
|
26
57
|
@last_acks = SPing::LastAcks.new
|
27
58
|
end
|
28
59
|
|
60
|
+
# Initiates a TCP handshake.
|
61
|
+
# @param socket [TCPSocket] Socket via which the handshake is to be sent
|
62
|
+
# (and responses are to be expected).
|
63
|
+
# @param session_id [Integer] Session ID which is to be sent to the peer
|
64
|
+
# and which is to be used for the current session.
|
29
65
|
def do_tcp_handshake1(socket, session_id)
|
66
|
+
# Send the banner
|
30
67
|
socket.write "sping-0.3-https://codeberg.org/mark22k/sping\r\n"
|
68
|
+
# and make sure that it is no longer in the send buffer.
|
31
69
|
socket.flush
|
32
70
|
|
71
|
+
# See if the peer invites us to create a session with them.
|
33
72
|
invite = socket.readpartial 6
|
73
|
+
unless invite.chomp == 'INVITE'
|
74
|
+
socket.write "I_DONT_UNDERSTAND\r\n"
|
75
|
+
raise TCPHandshakeError, 'Peer didn\'t invite us.'
|
76
|
+
end
|
34
77
|
|
35
|
-
|
36
|
-
|
78
|
+
# Send the session ID
|
37
79
|
@session_id = session_id
|
38
|
-
|
39
80
|
socket.write "#{session_id}\r\n"
|
81
|
+
# and make sure that it is no longer in the send buffer.
|
40
82
|
socket.flush
|
41
83
|
|
84
|
+
# This means that the TCP handshake is successful. The socket can be closed
|
85
|
+
# and the corresponding instance variable can be set.
|
42
86
|
socket.close
|
43
87
|
|
44
88
|
@tcp_handshake_complete = true
|
45
89
|
end
|
46
90
|
|
91
|
+
# Receives a TCP handshake from a peer.
|
92
|
+
# The peer specified when the session is created is consulted for this purpose.
|
47
93
|
def do_tcp_handshake2
|
94
|
+
# Establish a connection and receive the banner.
|
48
95
|
socket = TCPSocket.new @host, @port
|
96
|
+
|
97
|
+
# If the banner is too large, close the socket and throw an error.
|
98
|
+
# The handshake was not successful.
|
49
99
|
banner = socket.readpartial 9001
|
50
100
|
if banner.length > 9000
|
51
101
|
socket.close
|
52
102
|
raise TCPHandshakeError, 'Host banner too big'
|
53
103
|
end
|
54
104
|
|
105
|
+
# If the banner does not match the SPing service, close the socket and
|
106
|
+
# throw an error. The handshake was not successful.
|
55
107
|
unless banner.start_with? 'sping-0.3-'
|
56
108
|
socket.close
|
57
|
-
raise TCPHandshakeError, 'Host banner not sping'
|
109
|
+
raise TCPHandshakeError, 'Host banner not sping or unsupported version of sping.'
|
58
110
|
end
|
59
111
|
|
60
112
|
@remote_version = banner.chomp
|
61
113
|
$logger.info "Peer uses the following program version: #{@remote_version.dump}"
|
62
114
|
|
115
|
+
# Invite the peer to start a session with us.
|
63
116
|
socket.write "INVITE\r\n"
|
64
117
|
socket.flush
|
65
118
|
|
119
|
+
# If the session ID is too long or none was received, close the socket and throw an error.
|
66
120
|
invite_buf = socket.readpartial 32
|
67
121
|
if invite_buf.length > 31 || invite_buf.empty?
|
68
122
|
socket.close
|
@@ -76,15 +130,21 @@ module SPing
|
|
76
130
|
@tcp_handshake_complete = true
|
77
131
|
end
|
78
132
|
|
133
|
+
# Sets the endpoint consisting of the host and port of the remote end. This is done
|
134
|
+
# each time a new packet is received and ensures that the current endpoint is always available.
|
135
|
+
# @param host [#to_s]
|
136
|
+
# @param port [#to_i]
|
79
137
|
def set_endpoint(host, port)
|
80
|
-
@host = host
|
81
|
-
@port = port
|
138
|
+
@host = host.to_s
|
139
|
+
@port = port.to_i
|
82
140
|
end
|
83
141
|
|
84
|
-
|
142
|
+
# Starts a thread which sends the UDP handshake at regular intervals.
|
143
|
+
# @param send_interval [#to_i] The interval at which the UDP handshakes are to be sent.
|
144
|
+
def start_udp_handshake_sender(send_interval = 1)
|
85
145
|
raise 'UDP handshake sender is already running.' if @udp_handshake_sender
|
86
146
|
|
87
|
-
@udp_handshake_sender = Thread.new(send_interval) do |th_send_interval|
|
147
|
+
@udp_handshake_sender = Thread.new(send_interval.to_i) do |th_send_interval|
|
88
148
|
loop do
|
89
149
|
send_udp_handshake
|
90
150
|
sleep th_send_interval
|
@@ -92,6 +152,7 @@ module SPing
|
|
92
152
|
end
|
93
153
|
end
|
94
154
|
|
155
|
+
# Send a single UDP handshake
|
95
156
|
def send_udp_handshake
|
96
157
|
packet = {
|
97
158
|
'Y' => 'h'.ord,
|
@@ -104,6 +165,7 @@ module SPing
|
|
104
165
|
@socket.send packet, 0, @host, @port
|
105
166
|
end
|
106
167
|
|
168
|
+
# Stop the UDP handshake sender. UDP handshakes are then no longer sent at any interval.
|
107
169
|
def stop_udp_handshake_sender
|
108
170
|
raise 'UDP handshake sender is not running and therefore cannot be terminated.' unless @udp_handshake_sender
|
109
171
|
|
@@ -111,11 +173,14 @@ module SPing
|
|
111
173
|
@udp_handshake_sender = nil
|
112
174
|
end
|
113
175
|
|
176
|
+
# Informs the session that a UDP handshake has been received.
|
114
177
|
def udp_handshake_recived
|
115
178
|
stop_udp_handshake_sender if @madebyme
|
116
179
|
@udp_handshake_complete = true
|
180
|
+
@double_activated = Time.now
|
117
181
|
end
|
118
182
|
|
183
|
+
# Starts a thread which sends ping or time messages to the peer at one-second intervals.
|
119
184
|
def start_pinger
|
120
185
|
raise 'Pinger is already running.' if @pinger
|
121
186
|
|
@@ -127,6 +192,7 @@ module SPing
|
|
127
192
|
end
|
128
193
|
end
|
129
194
|
|
195
|
+
# Stops the thread, which sends ping or time messages to the peer at regular intervals.
|
130
196
|
def stop_pinger
|
131
197
|
raise 'Pinger sender is not running and therefore cannot be terminated.' unless @pinger
|
132
198
|
|
@@ -134,6 +200,7 @@ module SPing
|
|
134
200
|
@pinger = nil
|
135
201
|
end
|
136
202
|
|
203
|
+
# Sends a ping message to the peer.
|
137
204
|
def ping
|
138
205
|
current_id = (Time.now.to_i % 255) + 1
|
139
206
|
data = {
|
@@ -150,11 +217,16 @@ module SPing
|
|
150
217
|
@socket.send data, 0, @host, @port
|
151
218
|
end
|
152
219
|
|
220
|
+
# Stops all threads associated with the session. This means that the session to the peer is as good as dead.
|
153
221
|
def stop
|
154
222
|
@pinger&.kill
|
155
223
|
@udp_handshake_sender&.kill
|
156
224
|
end
|
157
225
|
|
226
|
+
# Handler that receives and processes a receiving ping packet or time message. The current statistics are output.
|
227
|
+
# @param packet [Hash]
|
228
|
+
# @param rxtime [Time]
|
229
|
+
# @param peeraddr [#to_s]
|
158
230
|
def handle_ping(packet, rxtime, peeraddr)
|
159
231
|
if !(packet.keys - %w[M Y E I T A S]).empty? ||
|
160
232
|
# M, Y are already checked in handle_packet from session manager
|
@@ -211,11 +283,17 @@ module SPing
|
|
211
283
|
rx_loss = 0
|
212
284
|
exchanges = 0
|
213
285
|
|
214
|
-
|
286
|
+
last_local_acks = @last_acks.acks
|
287
|
+
|
288
|
+
# A calculation can only take place when enough data is available, i.e. when:
|
289
|
+
# - We have sent 32 time messages (this is the case after 32 seconds)
|
290
|
+
# - The peer has sent 32 time messages (this is the case after 32 seconds)
|
291
|
+
# - If there is a time message (this is the case when we are in this function)
|
292
|
+
if @double_activated && (Time.now - @double_activated).to_i > 32
|
215
293
|
# We have enough data
|
216
294
|
exchanges = 32
|
217
295
|
remote_acks = remote_last_acks.map { |ack| ack['R'] }
|
218
|
-
local_acks =
|
296
|
+
local_acks = last_local_acks.map { |ack| ack['R'] }
|
219
297
|
|
220
298
|
tip_id = (Time.now.to_i % 255) + 1
|
221
299
|
starting = tip_id - 32
|
data/lib/session_manager.rb
CHANGED
@@ -1,17 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
# sharable_constant_value: literal
|
3
3
|
|
4
|
+
# SPing / Splitted Ping is a protocol for measuring asymmetric latencies.
|
4
5
|
module SPing
|
6
|
+
# Inclusion of all possible SPing-specific errors.
|
5
7
|
require_relative 'errors'
|
6
8
|
|
9
|
+
# Container, which contains a collection of sessions and manages them.
|
7
10
|
class SessionManager
|
8
11
|
require 'socket'
|
9
12
|
require 'timeout'
|
10
13
|
require_relative 'session'
|
11
14
|
|
15
|
+
# Creates a new Session Manager, which can (and will) manage a number of sessions.
|
16
|
+
# @param host [#to_s] Host to which messages are to be bound and from which messages are sent accordingly.
|
17
|
+
# @param port [#to_i] Port
|
12
18
|
def initialize(host = '::', port = 6924)
|
13
|
-
@host = host
|
14
|
-
@port = port
|
19
|
+
@host = host.to_s
|
20
|
+
@port = port.to_i
|
15
21
|
|
16
22
|
@sessions = {}
|
17
23
|
@sessions_mutex = Mutex.new
|
@@ -20,9 +26,12 @@ module SPing
|
|
20
26
|
@socket.bind @host, @port
|
21
27
|
end
|
22
28
|
|
29
|
+
# Initiates a new session with a peer.
|
30
|
+
# @param host [#to_s]
|
31
|
+
# @param port [#to_s, #to_i]
|
23
32
|
def new_session(host, port = 6924)
|
24
33
|
$logger.info "Add new session for host #{host} port #{port}."
|
25
|
-
session = SPing::Session.new host, port, @socket, true
|
34
|
+
session = SPing::Session.new host.to_s, port.to_i, @socket, true
|
26
35
|
|
27
36
|
counter = 0
|
28
37
|
loop do
|
@@ -48,6 +57,8 @@ module SPing
|
|
48
57
|
$logger.error "Out of session: #{e.message}"
|
49
58
|
end
|
50
59
|
|
60
|
+
# Stops all threads connected to a session ID and removes the session from the Session Manager administration.
|
61
|
+
# @param session_id [Integer] Session ID of the session to be removed.
|
51
62
|
def del_session(session_id)
|
52
63
|
$logger.debug "Delete session with session id #{session_id}"
|
53
64
|
|
@@ -57,18 +68,21 @@ module SPing
|
|
57
68
|
end
|
58
69
|
end
|
59
70
|
|
71
|
+
# Stops all sessions or the threads that are connected to them.
|
60
72
|
def stop_sessions
|
61
73
|
@sessions.each do |_session_id, session|
|
62
74
|
session.stop
|
63
75
|
end
|
64
76
|
end
|
65
77
|
|
78
|
+
# Waits and blocks until the Session Manager is no longer running.
|
66
79
|
def join
|
67
80
|
@runner&.join
|
68
81
|
@gc_thread&.join
|
69
82
|
@server_thread&.join
|
70
83
|
end
|
71
84
|
|
85
|
+
# Starts the Session Manager.
|
72
86
|
def run
|
73
87
|
raise 'Client is already running.' if @runner
|
74
88
|
raise 'GC is already running.' if @gc
|
@@ -88,6 +102,7 @@ module SPing
|
|
88
102
|
end
|
89
103
|
end
|
90
104
|
|
105
|
+
# Starts the server functionality of the Session Manager.
|
91
106
|
def run_server
|
92
107
|
raise 'Server is already running.' if @server_thread
|
93
108
|
raise 'Server already exist.' if @server
|
@@ -105,6 +120,7 @@ module SPing
|
|
105
120
|
return @server_thread
|
106
121
|
end
|
107
122
|
|
123
|
+
# Stops the server functionality of the Session Manager.
|
108
124
|
def stop_server
|
109
125
|
raise 'Server thread is not running.' unless @server_thread
|
110
126
|
|
@@ -117,6 +133,7 @@ module SPing
|
|
117
133
|
@server = nil
|
118
134
|
end
|
119
135
|
|
136
|
+
# Stops the Session Manager. The server functionality must be stopped separately.
|
120
137
|
def stop
|
121
138
|
raise 'Session manager is not running.' unless @runner
|
122
139
|
|
@@ -129,6 +146,12 @@ module SPing
|
|
129
146
|
@gc_thread = nil
|
130
147
|
end
|
131
148
|
|
149
|
+
private
|
150
|
+
|
151
|
+
# Generates a new session ID that was not yet in use at the time of the check.
|
152
|
+
# If none could be generated within half a minute - for example because all
|
153
|
+
# session IDs have already been assigned - an error is thrown.
|
154
|
+
# @return [Integer]
|
132
155
|
def generate_session_id
|
133
156
|
Timeout.timeout(30, OutOfSessions, 'No session ID could be generated which has not yet been used.') do
|
134
157
|
loop do
|
@@ -138,19 +161,23 @@ module SPing
|
|
138
161
|
end
|
139
162
|
end
|
140
163
|
|
141
|
-
|
142
|
-
|
164
|
+
# Removes obsolete sessions. This includes sessions that are older than one minute but
|
165
|
+
# have not been activated a double time. And sessions that have not
|
166
|
+
# received any packets for half a minute.
|
143
167
|
def do_gc
|
144
168
|
$logger.debug 'Remove outdated sessions.'
|
145
169
|
# rubocop:disable Style/HashEachMethods
|
146
|
-
# You cannot run through a map and edit it at the same time. Since this is
|
170
|
+
# You cannot run through a map and edit it at the same time. Since this is
|
171
|
+
# necessary due to the threading,
|
147
172
|
# a snapshot of the key variable is run through.
|
148
173
|
@sessions.keys.each do |session_id|
|
149
174
|
# rubocop:enable Style/HashEachMethods
|
150
175
|
|
151
176
|
session = @sessions[session_id]
|
152
|
-
# It is possible that sessions no longer exist here, as we may have
|
153
|
-
#
|
177
|
+
# It is possible that sessions no longer exist here, as we may have
|
178
|
+
# session IDs that have already been deleted.
|
179
|
+
# However, we can ignore this aspect here, as we are the only
|
180
|
+
# function that deletes sessions.
|
154
181
|
if !(session.tcp_handshake_complete && session.udp_handshake_complete)
|
155
182
|
# Handshake incomplete
|
156
183
|
if (Time.now.to_i - session.created.to_i) > 60
|
@@ -159,13 +186,16 @@ module SPing
|
|
159
186
|
del_session session_id
|
160
187
|
end
|
161
188
|
elsif (Time.now.to_i - session.last_rx.to_i) > 30
|
162
|
-
# 30 seconds have elapsed since the last ping from the peer was received.
|
189
|
+
# 30 seconds have elapsed since the last ping from the peer was received.
|
190
|
+
# The peer is probably dead.
|
163
191
|
$logger.debug "Session id #{session_id} without activity for over thirty seconds."
|
164
192
|
del_session session_id
|
165
193
|
end
|
166
194
|
end
|
167
195
|
end
|
168
196
|
|
197
|
+
# Receives a TCP handshake for a session and then has it
|
198
|
+
# managed by the Session Manager.
|
169
199
|
def request_session(session)
|
170
200
|
session.do_tcp_handshake2
|
171
201
|
|
@@ -179,6 +209,9 @@ module SPing
|
|
179
209
|
return nil
|
180
210
|
end
|
181
211
|
|
212
|
+
# Handler for a received packet. The function catches errors and outputs them.
|
213
|
+
# It also forwards the packet to the corresponding handler according to the
|
214
|
+
# session ID and type of packet.
|
182
215
|
def handle_packet(buf)
|
183
216
|
rxtime = Time.now
|
184
217
|
|
@@ -217,6 +250,7 @@ module SPing
|
|
217
250
|
$logger.error "Perr error: #{e.message}"
|
218
251
|
end
|
219
252
|
|
253
|
+
# Handler for a receiving UDP handshake.
|
220
254
|
def handle_udp_handshake(packet, peeraddr, peerport)
|
221
255
|
if !(packet.keys - %w[M Y V S]).empty? ||
|
222
256
|
# M, Y are already checked in handle_packet from session manager
|
@@ -227,7 +261,7 @@ module SPing
|
|
227
261
|
session_id = packet['S']
|
228
262
|
session = @sessions[session_id]
|
229
263
|
raise UnknownSessionError, 'UDP handshake received for uninitiated session.' unless session
|
230
|
-
raise
|
264
|
+
raise UnsupportedVersionError, "UDP handshake uses an unsupported version: #{packet['V']}" if packet['V'] != 3
|
231
265
|
|
232
266
|
session.set_endpoint peeraddr, peerport
|
233
267
|
|
@@ -249,6 +283,7 @@ module SPing
|
|
249
283
|
session.start_pinger
|
250
284
|
end
|
251
285
|
|
286
|
+
# Handler for a receiving ping or a receiving time message.
|
252
287
|
def handle_ping(packet, rxtime, peeraddr, peerport)
|
253
288
|
session_id = packet['S']
|
254
289
|
session = @sessions[session_id]
|
@@ -260,6 +295,7 @@ module SPing
|
|
260
295
|
session.handle_ping packet, rxtime, peeraddr
|
261
296
|
end
|
262
297
|
|
298
|
+
# Handler to establish a session for the request of a peer.
|
263
299
|
def handle_client(client)
|
264
300
|
host = client.peeraddr[3]
|
265
301
|
port = client.peeraddr[1]
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sping
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marek Küthe
|
@@ -30,8 +30,9 @@ dependencies:
|
|
30
30
|
- - ">="
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: 1.7.2
|
33
|
-
description: This is a reimplementation of
|
34
|
-
in
|
33
|
+
description: This is a reimplementation in Ruby of the reference implementation of
|
34
|
+
the sping protocol in Go. The program provides both the client and server part to
|
35
|
+
measure asymmetric latencies between two peers.
|
35
36
|
email: m.k@mk16.de
|
36
37
|
executables:
|
37
38
|
- sping
|
@@ -72,5 +73,5 @@ requirements: []
|
|
72
73
|
rubygems_version: 3.4.22
|
73
74
|
signing_key:
|
74
75
|
specification_version: 4
|
75
|
-
summary:
|
76
|
+
summary: Program for measuring asymmetric latencies.
|
76
77
|
test_files: []
|