anytls 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +11 -0
- data/LICENSE +21 -0
- data/README.md +143 -0
- data/bin/anytls-client +7 -0
- data/bin/anytls-server +7 -0
- data/docs/ARCHITECTURE.md +105 -0
- data/docs/SOURCES.md +15 -0
- data/docs/VALIDATION.md +100 -0
- data/docs/validation/runtime-ruby34.json +8 -0
- data/docs/validation/runtime-ruby40.json +8 -0
- data/docs/validation/stress-c1.json +25 -0
- data/docs/validation/stress-c128.json +26 -0
- data/docs/validation/stress-c32.json +26 -0
- data/docs/validation/stress-sequential-10000.json +26 -0
- data/lib/anytls/address.rb +53 -0
- data/lib/anytls/buffered_io.rb +185 -0
- data/lib/anytls/byte_buffer.rb +61 -0
- data/lib/anytls/cli/client_listener.rb +91 -0
- data/lib/anytls/cli/options.rb +160 -0
- data/lib/anytls/cli/runner.rb +89 -0
- data/lib/anytls/cli/shutdown.rb +36 -0
- data/lib/anytls/client.rb +129 -0
- data/lib/anytls/connection_uri.rb +96 -0
- data/lib/anytls/deadline.rb +34 -0
- data/lib/anytls/dialer.rb +70 -0
- data/lib/anytls/errors.rb +23 -0
- data/lib/anytls/inbound/http_proxy.rb +224 -0
- data/lib/anytls/inbound/mixed.rb +60 -0
- data/lib/anytls/inbound/socks4.rb +55 -0
- data/lib/anytls/inbound/socks5.rb +138 -0
- data/lib/anytls/io_helpers.rb +87 -0
- data/lib/anytls/log.rb +15 -0
- data/lib/anytls/padding/scheme.rb +121 -0
- data/lib/anytls/padding/writer.rb +55 -0
- data/lib/anytls/protocol/authentication.rb +44 -0
- data/lib/anytls/protocol/decoder.rb +39 -0
- data/lib/anytls/protocol/frame.rb +40 -0
- data/lib/anytls/protocol/settings.rb +53 -0
- data/lib/anytls/protocol/socks_address.rb +59 -0
- data/lib/anytls/relay.rb +49 -0
- data/lib/anytls/server.rb +220 -0
- data/lib/anytls/session.rb +593 -0
- data/lib/anytls/session_pool.rb +166 -0
- data/lib/anytls/stream.rb +151 -0
- data/lib/anytls/tls_config.rb +76 -0
- data/lib/anytls/transport/tls_connection.rb +117 -0
- data/lib/anytls/uot/connection.rb +122 -0
- data/lib/anytls/uot/server.rb +97 -0
- data/lib/anytls/version.rb +6 -0
- data/lib/anytls.rb +40 -0
- metadata +203 -0
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnyTLS
|
|
4
|
+
# 每条连接由唯一所有者线程推进 TLS、帧处理和流状态。
|
|
5
|
+
# 其他线程提交受容量限制的操作,并等待本地发送完成。
|
|
6
|
+
# 所有者线程中的回调不得同步向同一会话提交任务。
|
|
7
|
+
class Session
|
|
8
|
+
# 与等待者共享的一次性结果;首次完成的结果生效。
|
|
9
|
+
class Completion
|
|
10
|
+
def initialize
|
|
11
|
+
@mutex = Mutex.new
|
|
12
|
+
@changed = ConditionVariable.new
|
|
13
|
+
@done = false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def finish(value = nil, error: nil)
|
|
17
|
+
@mutex.synchronize do
|
|
18
|
+
return if @done
|
|
19
|
+
|
|
20
|
+
@value = value
|
|
21
|
+
@error = error
|
|
22
|
+
@done = true
|
|
23
|
+
@changed.broadcast
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def wait(deadline)
|
|
28
|
+
@mutex.synchronize do
|
|
29
|
+
deadline.wait(@changed, @mutex) until @done
|
|
30
|
+
raise @error if @error
|
|
31
|
+
|
|
32
|
+
@value
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private_constant :Completion
|
|
38
|
+
|
|
39
|
+
attr_reader :sequence
|
|
40
|
+
|
|
41
|
+
def initialize(socket, tls:, password_digest:, scheme:, server: false, host: nil, sequence: 0,
|
|
42
|
+
handshake_timeout: 5, open_timeout: 5, max_streams: 32, receive_limit: 262_144,
|
|
43
|
+
session_receive_limit: 4_194_304, send_limit: 1_048_576, logger: nil,
|
|
44
|
+
on_stream: nil, on_idle: nil, on_close: nil, on_padding: nil)
|
|
45
|
+
@socket = socket
|
|
46
|
+
@tls = tls
|
|
47
|
+
@host = host
|
|
48
|
+
@server = server
|
|
49
|
+
@password_digest = password_digest
|
|
50
|
+
@scheme = scheme
|
|
51
|
+
@sequence = sequence
|
|
52
|
+
@handshake_timeout = handshake_timeout
|
|
53
|
+
@open_timeout = open_timeout
|
|
54
|
+
@max_streams = max_streams
|
|
55
|
+
@receive_limit = receive_limit
|
|
56
|
+
@session_receive_limit = session_receive_limit
|
|
57
|
+
@send_limit = send_limit
|
|
58
|
+
@logger = logger
|
|
59
|
+
@on_stream = on_stream
|
|
60
|
+
@on_idle = on_idle
|
|
61
|
+
@on_close = on_close
|
|
62
|
+
@on_padding = on_padding
|
|
63
|
+
@mutex = Mutex.new
|
|
64
|
+
@changed = ConditionVariable.new
|
|
65
|
+
# 分配唤醒 socket 前校验填充方案,避免配置错误留下待 GC 回收的 fd。
|
|
66
|
+
@padding_writer = Padding::Writer.new(scheme) unless server
|
|
67
|
+
@wake_reader, @wake_writer = Socket.pair(:UNIX, :STREAM, 0)
|
|
68
|
+
@operations = []
|
|
69
|
+
@transmissions = []
|
|
70
|
+
@streams = {}
|
|
71
|
+
@pending_open = {}
|
|
72
|
+
@queued_bytes = 0
|
|
73
|
+
@control_bytes = 0
|
|
74
|
+
@last_id = 0
|
|
75
|
+
@active_count = 0
|
|
76
|
+
@peer_version = 1
|
|
77
|
+
@closed = false
|
|
78
|
+
@stopping = false
|
|
79
|
+
@settings_received = false
|
|
80
|
+
@first_open = true
|
|
81
|
+
@ready = Completion.new
|
|
82
|
+
@diagnostic_errors = []
|
|
83
|
+
@decoder = Protocol::Decoder.new
|
|
84
|
+
begin
|
|
85
|
+
@thread = Thread.new { run }
|
|
86
|
+
rescue ThreadError
|
|
87
|
+
@wake_reader.close
|
|
88
|
+
@wake_writer.close
|
|
89
|
+
@socket.close unless @socket.closed?
|
|
90
|
+
raise
|
|
91
|
+
end
|
|
92
|
+
@thread.name = "anytls-session-#{sequence}"
|
|
93
|
+
@thread.report_on_exception = false
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# 就绪仅表示本地 TLS 和认证记录处理已完成,
|
|
97
|
+
# 不证明对端已确认认证或连接出站目标。
|
|
98
|
+
def wait_ready(timeout: (@handshake_timeout * 2) + 1)
|
|
99
|
+
@ready.wait(Deadline.new(timeout))
|
|
100
|
+
self
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def closed?
|
|
104
|
+
@mutex.synchronize { @closed || @stopping }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def peer_version
|
|
108
|
+
@mutex.synchronize { @peer_version }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def stream_count
|
|
112
|
+
@mutex.synchronize { @active_count }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def open_stream(address, timeout: @open_timeout)
|
|
116
|
+
submit(:open, address, deadline: Deadline.new(timeout), size: 512)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def write_stream(id, bytes, deadline:)
|
|
120
|
+
submit(:write, [id, bytes], deadline: deadline, size: bytes.bytesize + Protocol::HEADER_SIZE)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def close_stream(id)
|
|
124
|
+
submit(:close, id, deadline: Deadline.new(5), size: Protocol::HEADER_SIZE)
|
|
125
|
+
rescue Error, IOError
|
|
126
|
+
close
|
|
127
|
+
nil
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def report_connected(id, error: nil)
|
|
131
|
+
submit(:report, [id, error], deadline: Deadline.new(5), size: 1024)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def heartbeat(timeout: 5)
|
|
135
|
+
submit(:heartbeat, nil, deadline: Deadline.new(timeout), size: Protocol::HEADER_SIZE)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def diagnostic_errors
|
|
139
|
+
@mutex.synchronize { @diagnostic_errors.dup.freeze }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def inspect
|
|
143
|
+
"#<#{self.class} sequence=#{sequence} role=#{@server ? 'server' : 'client'} closed=#{closed?}>"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def close(wait: true)
|
|
147
|
+
@mutex.synchronize do
|
|
148
|
+
@stopping = true
|
|
149
|
+
@changed.broadcast
|
|
150
|
+
end
|
|
151
|
+
wake
|
|
152
|
+
@thread.join if wait && Thread.current != @thread
|
|
153
|
+
nil
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
# 接收操作前,按编码字节数及最大填充预留容量。
|
|
159
|
+
# 等待队列容量会消耗调用者原有的单调时间预算。
|
|
160
|
+
def submit(kind, argument, deadline:, size:)
|
|
161
|
+
completion = Completion.new
|
|
162
|
+
cost = size + (@server ? 0 : @scheme.max_padding_bytes)
|
|
163
|
+
@mutex.synchronize do
|
|
164
|
+
raise BufferLimitExceeded, "write exceeds session queue capacity" if cost > @send_limit
|
|
165
|
+
|
|
166
|
+
deadline.wait(@changed, @mutex) while @queued_bytes + cost > @send_limit && !@stopping && !@closed
|
|
167
|
+
raise ClosedError, "session closed" if @stopping || @closed
|
|
168
|
+
|
|
169
|
+
deadline.check!
|
|
170
|
+
@queued_bytes += cost
|
|
171
|
+
@operations << { kind: kind, argument: argument, deadline: deadline, completion: completion, cost: cost }
|
|
172
|
+
end
|
|
173
|
+
wake
|
|
174
|
+
begin
|
|
175
|
+
completion.wait(deadline)
|
|
176
|
+
rescue TimeoutError
|
|
177
|
+
# 已接收的操作可能已影响对端;不得自动重放。
|
|
178
|
+
close(wait: false)
|
|
179
|
+
raise
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def wake
|
|
184
|
+
@wake_writer.write_nonblock(".", exception: false)
|
|
185
|
+
rescue IOError, SystemCallError
|
|
186
|
+
nil
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def run
|
|
190
|
+
@transport = Transport::TLSConnection.new(@socket, config: @tls, server: @server, host: @host,
|
|
191
|
+
cancel_io: @wake_reader, cancelled: -> { closed? })
|
|
192
|
+
@transport.handshake(timeout: @handshake_timeout)
|
|
193
|
+
@transport.authenticate(@password_digest, scheme: @scheme, timeout: @handshake_timeout)
|
|
194
|
+
@ready.finish(self)
|
|
195
|
+
event_loop
|
|
196
|
+
rescue StandardError => error
|
|
197
|
+
@failure = translate_error(error)
|
|
198
|
+
@ready.finish(error: @failure)
|
|
199
|
+
log(:debug, "AnyTLS session ended: #{@failure.class}")
|
|
200
|
+
ensure
|
|
201
|
+
finalize
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def translate_error(error)
|
|
205
|
+
return error if error.is_a?(Error) || error.is_a?(ClosedError)
|
|
206
|
+
|
|
207
|
+
TransportError.new("TLS session failed (#{error.class})")
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def event_loop
|
|
211
|
+
read_wait = :wait_readable
|
|
212
|
+
until closed?
|
|
213
|
+
process_operations
|
|
214
|
+
expire_opens
|
|
215
|
+
start_transmission unless @current
|
|
216
|
+
@current[:deadline].check! if @current
|
|
217
|
+
|
|
218
|
+
advance_transmission if @current && @transport.advance_write == :complete
|
|
219
|
+
break if @reject_after_flush && !@current && @transmissions.empty?
|
|
220
|
+
|
|
221
|
+
# SSL_write 返回 WANT_WRITE 后,必须先重试原写入,再进行其他 TLS 操作。
|
|
222
|
+
unless @current && @transport.write_wait == :wait_writable
|
|
223
|
+
result = @transport.read_nonblock(16_384)
|
|
224
|
+
if result.nil?
|
|
225
|
+
@decoder.finish!
|
|
226
|
+
raise TransportError, "peer closed TLS session"
|
|
227
|
+
elsif result.is_a?(String)
|
|
228
|
+
@decoder.feed(result).each { |frame| dispatch(frame) }
|
|
229
|
+
next
|
|
230
|
+
else
|
|
231
|
+
read_wait = result
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
next if !@current && !@transmissions.empty?
|
|
235
|
+
|
|
236
|
+
wait_for_work(read_wait)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def wait_for_work(read_wait)
|
|
241
|
+
readers = [@wake_reader]
|
|
242
|
+
writers = []
|
|
243
|
+
if @current && @transport.write_wait == :wait_writable
|
|
244
|
+
writers << @transport.io
|
|
245
|
+
else
|
|
246
|
+
readers << @transport.io if read_wait == :wait_readable || @transport.write_wait == :wait_readable
|
|
247
|
+
writers << @transport.io if read_wait == :wait_writable || (@current && @transport.write_wait.nil?)
|
|
248
|
+
end
|
|
249
|
+
budgets = [@current&.dig(:deadline)&.remaining, @heartbeat&.dig(:deadline)&.remaining,
|
|
250
|
+
*@pending_open.values.map(&:remaining)].compact
|
|
251
|
+
timeout = budgets.min
|
|
252
|
+
ready = IO.select(readers, writers, nil, timeout)
|
|
253
|
+
if ready&.first&.include?(@wake_reader)
|
|
254
|
+
loop do
|
|
255
|
+
value = @wake_reader.read_nonblock(4096, exception: false)
|
|
256
|
+
break unless value.is_a?(String)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def process_operations
|
|
262
|
+
16.times do
|
|
263
|
+
operation = @mutex.synchronize { @operations.shift }
|
|
264
|
+
break unless operation
|
|
265
|
+
|
|
266
|
+
begin
|
|
267
|
+
operation[:deadline].check!
|
|
268
|
+
case operation[:kind]
|
|
269
|
+
when :open then open_operation(operation)
|
|
270
|
+
when :write then write_operation(operation)
|
|
271
|
+
when :close then close_operation(operation)
|
|
272
|
+
when :report then report_operation(operation)
|
|
273
|
+
when :heartbeat then heartbeat_operation(operation)
|
|
274
|
+
end
|
|
275
|
+
rescue StandardError => error
|
|
276
|
+
release_operation(operation, error: error)
|
|
277
|
+
ensure
|
|
278
|
+
operation.delete(:argument)
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def open_operation(operation)
|
|
284
|
+
raise ProtocolError, "only a client can open streams" if @server
|
|
285
|
+
raise BufferLimitExceeded, "session stream limit reached" if @streams.size >= @max_streams
|
|
286
|
+
raise ClosedError, "stream identifiers exhausted" if @last_id == Protocol::MAX_STREAM_ID
|
|
287
|
+
|
|
288
|
+
@last_id += 1
|
|
289
|
+
stream = register_stream(@last_id)
|
|
290
|
+
bytes = String.new(encoding: Encoding::BINARY)
|
|
291
|
+
if @first_open
|
|
292
|
+
settings = { "v" => "2", "client" => "anytls-ruby/#{VERSION}", "padding-md5" => @scheme.md5 }
|
|
293
|
+
bytes << encode(Protocol::SETTINGS, 0, Protocol::Settings.encode(settings))
|
|
294
|
+
@first_open = false
|
|
295
|
+
end
|
|
296
|
+
bytes << encode(Protocol::SYN, stream.id)
|
|
297
|
+
bytes << encode(Protocol::PSH, stream.id, Protocol::SocksAddress.encode(operation[:argument]))
|
|
298
|
+
@pending_open[stream.id] = Deadline.new(@open_timeout) if @peer_version >= 2
|
|
299
|
+
queue_transmission(bytes, operation: operation, value: stream)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def write_operation(operation)
|
|
303
|
+
id, bytes = operation[:argument]
|
|
304
|
+
raise ClosedError, "stream closed" unless @streams.key?(id)
|
|
305
|
+
|
|
306
|
+
queue_transmission(encode(Protocol::PSH, id, bytes), operation: operation, value: bytes.bytesize)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def close_operation(operation)
|
|
310
|
+
id = operation[:argument]
|
|
311
|
+
stream = remove_stream(id)
|
|
312
|
+
if stream
|
|
313
|
+
stream.finish(ClosedError.new("stream closed"), local: true)
|
|
314
|
+
queue_transmission(encode(Protocol::FIN, id), operation: operation, idle: true)
|
|
315
|
+
else
|
|
316
|
+
release_operation(operation)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def report_operation(operation)
|
|
321
|
+
id, error = operation[:argument]
|
|
322
|
+
raise ClosedError, "stream closed" unless @streams.key?(id)
|
|
323
|
+
|
|
324
|
+
if @peer_version >= 2
|
|
325
|
+
message = error ? "outbound connection failed (#{error.class})" : "".b
|
|
326
|
+
queue_transmission(encode(Protocol::SYNACK, id, message), operation: operation)
|
|
327
|
+
else
|
|
328
|
+
release_operation(operation)
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def heartbeat_operation(operation)
|
|
333
|
+
raise ProtocolError, "heartbeat requires negotiated v2" if @peer_version < 2
|
|
334
|
+
raise ProtocolError, "heartbeat already pending" if @heartbeat
|
|
335
|
+
|
|
336
|
+
@heartbeat = operation
|
|
337
|
+
queue_transmission(encode(Protocol::HEART_REQUEST, 0))
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# 应用操作在完整发送前保留队列计费;对端控制帧响应
|
|
341
|
+
# 使用独立预算,因为没有应用提交者承担其容量。
|
|
342
|
+
def queue_transmission(bytes, operation: nil, value: nil, idle: false)
|
|
343
|
+
unless operation
|
|
344
|
+
@control_bytes += bytes.bytesize
|
|
345
|
+
raise BufferLimitExceeded, "control queue limit exceeded" if @control_bytes > 131_072
|
|
346
|
+
end
|
|
347
|
+
@transmissions << { bytes: bytes, bytesize: bytes.bytesize, operation: operation, value: value, idle: idle,
|
|
348
|
+
deadline: operation ? operation[:deadline] : Deadline.new(5) }
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def start_transmission
|
|
352
|
+
@current = @transmissions.shift
|
|
353
|
+
return unless @current
|
|
354
|
+
|
|
355
|
+
@chunks = @padding_writer ? @padding_writer.encode(@current[:bytes]) : [@current[:bytes]]
|
|
356
|
+
@current.delete(:bytes)
|
|
357
|
+
@transport.begin_write(@chunks.shift)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def advance_transmission
|
|
361
|
+
unless @chunks.empty?
|
|
362
|
+
@transport.begin_write(@chunks.shift)
|
|
363
|
+
return
|
|
364
|
+
end
|
|
365
|
+
transmission = @current
|
|
366
|
+
@current = nil
|
|
367
|
+
if transmission[:operation]
|
|
368
|
+
release_operation(transmission[:operation], value: transmission[:value])
|
|
369
|
+
else
|
|
370
|
+
@control_bytes -= transmission[:bytesize]
|
|
371
|
+
end
|
|
372
|
+
notify_idle if transmission[:idle]
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def release_operation(operation, value: nil, error: nil)
|
|
376
|
+
@mutex.synchronize do
|
|
377
|
+
@queued_bytes -= operation[:cost]
|
|
378
|
+
@changed.broadcast
|
|
379
|
+
end
|
|
380
|
+
operation[:completion].finish(value, error: error)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def dispatch(frame)
|
|
384
|
+
return if @reject_after_flush
|
|
385
|
+
|
|
386
|
+
validate_frame!(frame)
|
|
387
|
+
case frame.command
|
|
388
|
+
when Protocol::WASTE then nil
|
|
389
|
+
when Protocol::SETTINGS then receive_settings(frame.payload)
|
|
390
|
+
when Protocol::SERVER_SETTINGS then receive_server_settings(frame.payload)
|
|
391
|
+
when Protocol::SYN then receive_syn(frame.stream_id)
|
|
392
|
+
when Protocol::PSH then receive_data(frame)
|
|
393
|
+
when Protocol::FIN then finish_remote(frame.stream_id)
|
|
394
|
+
when Protocol::SYNACK then receive_ack(frame)
|
|
395
|
+
when Protocol::UPDATE_PADDING_SCHEME then receive_padding(frame.payload)
|
|
396
|
+
when Protocol::ALERT then raise RemoteError, "server rejected session"
|
|
397
|
+
when Protocol::HEART_REQUEST then queue_transmission(encode(Protocol::HEART_RESPONSE, frame.stream_id))
|
|
398
|
+
when Protocol::HEART_RESPONSE
|
|
399
|
+
if @heartbeat
|
|
400
|
+
release_operation(@heartbeat)
|
|
401
|
+
@heartbeat = nil
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def validate_frame!(frame)
|
|
407
|
+
command = frame.command
|
|
408
|
+
known = (Protocol::WASTE..Protocol::SERVER_SETTINGS).cover?(command)
|
|
409
|
+
raise ProtocolError, "unknown command has payload" if !known && !frame.payload.empty?
|
|
410
|
+
return unless known
|
|
411
|
+
|
|
412
|
+
empty_commands = [Protocol::SYN, Protocol::FIN, Protocol::HEART_REQUEST, Protocol::HEART_RESPONSE]
|
|
413
|
+
if empty_commands.include?(command) && !frame.payload.empty?
|
|
414
|
+
raise ProtocolError,
|
|
415
|
+
"command must have empty payload"
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
client_only = [Protocol::SETTINGS, Protocol::SYN]
|
|
419
|
+
server_only = [Protocol::SERVER_SETTINGS, Protocol::SYNACK, Protocol::ALERT, Protocol::UPDATE_PADDING_SCHEME]
|
|
420
|
+
if (@server && server_only.include?(command)) || (!@server && client_only.include?(command))
|
|
421
|
+
raise ProtocolError, "command received in invalid direction"
|
|
422
|
+
end
|
|
423
|
+
if [Protocol::SYN, Protocol::PSH, Protocol::FIN, Protocol::SYNACK].include?(command) && frame.stream_id.zero?
|
|
424
|
+
raise ProtocolError, "stream ID must be positive"
|
|
425
|
+
end
|
|
426
|
+
if [Protocol::SYNACK, Protocol::HEART_REQUEST, Protocol::HEART_RESPONSE].include?(command) && @peer_version < 2
|
|
427
|
+
raise ProtocolError, "command requires negotiated v2"
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
def parse_version(settings)
|
|
432
|
+
version = settings["v"]
|
|
433
|
+
raise ProtocolError, "invalid protocol version" unless version&.match?(/\A[1-9]\d{0,8}\z/)
|
|
434
|
+
|
|
435
|
+
[version.to_i, 2].min
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def receive_settings(payload)
|
|
439
|
+
raise ProtocolError, "duplicate client settings" if @settings_received
|
|
440
|
+
|
|
441
|
+
settings = Protocol::Settings.decode(payload)
|
|
442
|
+
version = parse_version(settings)
|
|
443
|
+
digest = settings["padding-md5"]
|
|
444
|
+
raise ProtocolError, "missing padding digest" unless digest&.match?(/\A[0-9a-f]{32}\z/)
|
|
445
|
+
|
|
446
|
+
@settings_received = true
|
|
447
|
+
@mutex.synchronize { @peer_version = version }
|
|
448
|
+
queue_transmission(encode(Protocol::UPDATE_PADDING_SCHEME, 0, @scheme.raw)) if digest != @scheme.md5
|
|
449
|
+
if version >= 2
|
|
450
|
+
queue_transmission(encode(Protocol::SERVER_SETTINGS, 0,
|
|
451
|
+
Protocol::Settings.encode({ "v" => "2" })))
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
def receive_server_settings(payload)
|
|
456
|
+
raise ProtocolError, "duplicate server settings" if @server_settings_received
|
|
457
|
+
|
|
458
|
+
@server_settings_received = true
|
|
459
|
+
version = parse_version(Protocol::Settings.decode(payload))
|
|
460
|
+
@mutex.synchronize { @peer_version = version }
|
|
461
|
+
return unless version >= 2
|
|
462
|
+
|
|
463
|
+
@streams.each_value do |stream|
|
|
464
|
+
@pending_open[stream.id] = Deadline.new(@open_timeout) if stream.connection_status == :unconfirmed
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def receive_syn(id)
|
|
469
|
+
unless @settings_received
|
|
470
|
+
queue_transmission(encode(Protocol::ALERT, 0, "client settings required"))
|
|
471
|
+
@reject_after_flush = true
|
|
472
|
+
return
|
|
473
|
+
end
|
|
474
|
+
raise ProtocolError, "non-monotonic stream ID" unless id > @last_id
|
|
475
|
+
|
|
476
|
+
@last_id = id
|
|
477
|
+
if @streams.size >= @max_streams
|
|
478
|
+
queue_transmission(encode(Protocol::SYNACK, id, "stream limit reached")) if @peer_version >= 2
|
|
479
|
+
queue_transmission(encode(Protocol::FIN, id))
|
|
480
|
+
return
|
|
481
|
+
end
|
|
482
|
+
stream = register_stream(id)
|
|
483
|
+
if @on_stream&.call(stream, self) == false
|
|
484
|
+
remove_stream(id)&.finish(BufferLimitExceeded.new("server stream limit reached"))
|
|
485
|
+
queue_transmission(encode(Protocol::SYNACK, id, "stream limit reached")) if @peer_version >= 2
|
|
486
|
+
queue_transmission(encode(Protocol::FIN, id))
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
# 所有者线程不得等待消费者;超出预算时拒绝受影响的流,
|
|
491
|
+
# 保留其他流及其已缓冲字节。
|
|
492
|
+
def receive_data(frame)
|
|
493
|
+
stream = @streams[frame.stream_id]
|
|
494
|
+
return unless stream
|
|
495
|
+
|
|
496
|
+
if @streams.values.sum(&:buffered_bytes) + frame.payload.bytesize > @session_receive_limit
|
|
497
|
+
raise BufferLimitExceeded, "session receive buffer limit exceeded"
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
stream.receive(frame.payload)
|
|
501
|
+
rescue BufferLimitExceeded => error
|
|
502
|
+
remove_stream(frame.stream_id)&.finish(error)
|
|
503
|
+
queue_transmission(encode(Protocol::FIN, frame.stream_id), idle: true)
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
def receive_ack(frame)
|
|
507
|
+
@pending_open.delete(frame.stream_id)
|
|
508
|
+
stream = @streams[frame.stream_id]
|
|
509
|
+
return unless stream
|
|
510
|
+
|
|
511
|
+
if frame.payload.empty?
|
|
512
|
+
stream.confirm
|
|
513
|
+
else
|
|
514
|
+
remove_stream(frame.stream_id)&.finish(RemoteError.new("remote outbound connection failed"))
|
|
515
|
+
queue_transmission(encode(Protocol::FIN, frame.stream_id), idle: true)
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# 发布校验后的快照,供客户端未来的会话使用;当前会话的写入器
|
|
520
|
+
# 继续使用原方案和包计数。
|
|
521
|
+
def receive_padding(payload)
|
|
522
|
+
scheme = Padding::Scheme.new(payload)
|
|
523
|
+
@on_padding&.call(scheme)
|
|
524
|
+
rescue ConfigurationError => error
|
|
525
|
+
log(:warn, "ignored invalid remote padding scheme (#{error.class})")
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def finish_remote(id)
|
|
529
|
+
stream = remove_stream(id)
|
|
530
|
+
stream&.finish
|
|
531
|
+
notify_idle if stream
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
def register_stream(id)
|
|
535
|
+
stream = Stream.new(self, id, receive_limit: @receive_limit)
|
|
536
|
+
@streams[id] = stream
|
|
537
|
+
@mutex.synchronize { @active_count = @streams.size }
|
|
538
|
+
stream
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def remove_stream(id)
|
|
542
|
+
@pending_open.delete(id)
|
|
543
|
+
stream = @streams.delete(id)
|
|
544
|
+
@mutex.synchronize { @active_count = @streams.size }
|
|
545
|
+
stream
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def notify_idle
|
|
549
|
+
@on_idle&.call(self) if @streams.empty? && !closed?
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
def expire_opens
|
|
553
|
+
@pending_open.each_value(&:check!)
|
|
554
|
+
@heartbeat&.fetch(:deadline)&.check!
|
|
555
|
+
@stopping = true if @reject_after_flush && @transmissions.empty? && !@current
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def encode(command, id, payload = "".b)
|
|
559
|
+
Protocol::Frame.new(command: command, stream_id: id, payload: payload).encode
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def log(level, message)
|
|
563
|
+
error = Log.emit(@logger, level, message)
|
|
564
|
+
return unless error
|
|
565
|
+
|
|
566
|
+
@mutex.synchronize { @diagnostic_errors << error if @diagnostic_errors.size < 16 }
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
def finalize
|
|
570
|
+
failure = @failure || ClosedError.new("session closed")
|
|
571
|
+
@transport&.close
|
|
572
|
+
@socket.close unless @socket.closed?
|
|
573
|
+
@streams.each_value { |stream| stream.finish(failure) }
|
|
574
|
+
@streams.clear
|
|
575
|
+
operations = @mutex.synchronize do
|
|
576
|
+
@closed = @stopping = true
|
|
577
|
+
@active_count = 0
|
|
578
|
+
queued = @operations
|
|
579
|
+
@operations = []
|
|
580
|
+
@changed.broadcast
|
|
581
|
+
queued
|
|
582
|
+
end
|
|
583
|
+
operations += @transmissions.filter_map { |entry| entry[:operation] }
|
|
584
|
+
operations << @current[:operation] if @current && @current[:operation]
|
|
585
|
+
operations << @heartbeat if @heartbeat
|
|
586
|
+
operations.each { |operation| operation[:completion].finish(error: failure) }
|
|
587
|
+
@ready.finish(error: failure)
|
|
588
|
+
@wake_reader.close
|
|
589
|
+
@wake_writer.close
|
|
590
|
+
@on_close&.call(self)
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
end
|