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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +11 -0
  3. data/LICENSE +21 -0
  4. data/README.md +143 -0
  5. data/bin/anytls-client +7 -0
  6. data/bin/anytls-server +7 -0
  7. data/docs/ARCHITECTURE.md +105 -0
  8. data/docs/SOURCES.md +15 -0
  9. data/docs/VALIDATION.md +100 -0
  10. data/docs/validation/runtime-ruby34.json +8 -0
  11. data/docs/validation/runtime-ruby40.json +8 -0
  12. data/docs/validation/stress-c1.json +25 -0
  13. data/docs/validation/stress-c128.json +26 -0
  14. data/docs/validation/stress-c32.json +26 -0
  15. data/docs/validation/stress-sequential-10000.json +26 -0
  16. data/lib/anytls/address.rb +53 -0
  17. data/lib/anytls/buffered_io.rb +185 -0
  18. data/lib/anytls/byte_buffer.rb +61 -0
  19. data/lib/anytls/cli/client_listener.rb +91 -0
  20. data/lib/anytls/cli/options.rb +160 -0
  21. data/lib/anytls/cli/runner.rb +89 -0
  22. data/lib/anytls/cli/shutdown.rb +36 -0
  23. data/lib/anytls/client.rb +129 -0
  24. data/lib/anytls/connection_uri.rb +96 -0
  25. data/lib/anytls/deadline.rb +34 -0
  26. data/lib/anytls/dialer.rb +70 -0
  27. data/lib/anytls/errors.rb +23 -0
  28. data/lib/anytls/inbound/http_proxy.rb +224 -0
  29. data/lib/anytls/inbound/mixed.rb +60 -0
  30. data/lib/anytls/inbound/socks4.rb +55 -0
  31. data/lib/anytls/inbound/socks5.rb +138 -0
  32. data/lib/anytls/io_helpers.rb +87 -0
  33. data/lib/anytls/log.rb +15 -0
  34. data/lib/anytls/padding/scheme.rb +121 -0
  35. data/lib/anytls/padding/writer.rb +55 -0
  36. data/lib/anytls/protocol/authentication.rb +44 -0
  37. data/lib/anytls/protocol/decoder.rb +39 -0
  38. data/lib/anytls/protocol/frame.rb +40 -0
  39. data/lib/anytls/protocol/settings.rb +53 -0
  40. data/lib/anytls/protocol/socks_address.rb +59 -0
  41. data/lib/anytls/relay.rb +49 -0
  42. data/lib/anytls/server.rb +220 -0
  43. data/lib/anytls/session.rb +593 -0
  44. data/lib/anytls/session_pool.rb +166 -0
  45. data/lib/anytls/stream.rb +151 -0
  46. data/lib/anytls/tls_config.rb +76 -0
  47. data/lib/anytls/transport/tls_connection.rb +117 -0
  48. data/lib/anytls/uot/connection.rb +122 -0
  49. data/lib/anytls/uot/server.rb +97 -0
  50. data/lib/anytls/version.rb +6 -0
  51. data/lib/anytls.rb +40 -0
  52. metadata +203 -0
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnyTLS
4
+ # 独占租用会话,优先复用创建序号最新的空闲会话。
5
+ # 正在创建的会话计入容量;空闲保留策略不会主动拨号补齐。
6
+ class SessionPool
7
+ def initialize(max_sessions: 128, min_idle_sessions: 5, idle_timeout: 30, idle_check_interval: 30, reuse: true,
8
+ &factory)
9
+ raise ArgumentError, "session factory required" unless factory
10
+ unless max_sessions.is_a?(Integer) && max_sessions.positive?
11
+ raise ConfigurationError,
12
+ "max_sessions must be positive"
13
+ end
14
+ unless min_idle_sessions.is_a?(Integer) && min_idle_sessions >= 0
15
+ raise ConfigurationError, "min_idle_sessions must be nonnegative"
16
+ end
17
+
18
+ [idle_timeout, idle_check_interval].each do |seconds|
19
+ Deadline.new(seconds)
20
+ raise ConfigurationError, "idle periods must be positive" unless seconds&.positive?
21
+ end
22
+ raise ConfigurationError, "reuse must be boolean" unless [true, false].include?(reuse)
23
+
24
+ @max_sessions = max_sessions
25
+ @min_idle_sessions = min_idle_sessions
26
+ @idle_timeout = idle_timeout
27
+ @interval = idle_check_interval
28
+ @reuse = reuse
29
+ @factory = factory
30
+ @mutex = Mutex.new
31
+ @changed = ConditionVariable.new
32
+ @sessions = {}
33
+ @idle = {}
34
+ @creating = 0
35
+ @sequence = 0
36
+ @closed = false
37
+ end
38
+
39
+ # 锁内预留容量,锁外拨号。池关闭后,
40
+ # 迟到的创建结果会被拒绝并关闭,不会交给租用者。
41
+ def acquire(timeout: 5)
42
+ deadline = Deadline.new(timeout)
43
+ sequence = @mutex.synchronize do
44
+ start_maintenance
45
+ loop do
46
+ raise ClosedError, "client closed" if @closed
47
+
48
+ if @reuse && !@idle.empty?
49
+ session = @idle.keys.max_by(&:sequence)
50
+ @idle.delete(session)
51
+ if session.closed?
52
+ @sessions.delete(session)
53
+ next
54
+ end
55
+ @sessions[session] = :leased
56
+ return session
57
+ end
58
+ if @sessions.size + @creating < @max_sessions
59
+ @creating += 1
60
+ @sequence += 1
61
+ break @sequence
62
+ end
63
+ deadline.wait(@changed, @mutex)
64
+ end
65
+ end
66
+
67
+ session = nil
68
+ begin
69
+ session = @factory.call(sequence, deadline.remaining)
70
+ @mutex.synchronize do
71
+ raise ClosedError, "client closed during dial" if @closed || session.closed?
72
+
73
+ @sessions[session] = :leased
74
+ end
75
+ session
76
+ rescue StandardError
77
+ session&.close
78
+ raise
79
+ ensure
80
+ @mutex.synchronize do
81
+ @creating -= 1
82
+ @changed.broadcast
83
+ end
84
+ end
85
+ end
86
+
87
+ # 会话已无流时归还;需要丢弃的会话在锁外关闭,
88
+ # 以便其所有者回调安全地从池中移除会话。
89
+ def release(session)
90
+ discard = @mutex.synchronize do
91
+ if @closed || !@reuse || session.closed?
92
+ @sessions.delete(session)
93
+ @idle.delete(session)
94
+ true
95
+ elsif @sessions.key?(session)
96
+ @sessions[session] = :idle
97
+ @idle[session] = Deadline.now
98
+ false
99
+ end
100
+ ensure
101
+ @changed.broadcast
102
+ end
103
+ session.close(wait: false) if discard
104
+ end
105
+
106
+ def remove(session)
107
+ @mutex.synchronize do
108
+ @sessions.delete(session)
109
+ @idle.delete(session)
110
+ @changed.broadcast
111
+ end
112
+ end
113
+
114
+ def stats
115
+ @mutex.synchronize do
116
+ { sessions: @sessions.size, idle_sessions: @idle.size, creating_sessions: @creating,
117
+ created_sessions: @sequence }.freeze
118
+ end
119
+ end
120
+
121
+ def close
122
+ sessions = @mutex.synchronize do
123
+ @closed = true
124
+ @changed.broadcast
125
+ result = @sessions.keys
126
+ @sessions.clear
127
+ @idle.clear
128
+ result
129
+ end
130
+ sessions.each { |session| session.close(wait: false) }
131
+ sessions.each(&:close)
132
+ @maintenance&.join unless @maintenance == Thread.current
133
+ @mutex.synchronize { @changed.wait(@mutex) while @creating.positive? }
134
+ nil
135
+ end
136
+
137
+ private
138
+
139
+ def start_maintenance
140
+ return if @maintenance || !@reuse || @closed
141
+
142
+ @maintenance = Thread.new do
143
+ loop do
144
+ expired = @mutex.synchronize do
145
+ @changed.wait(@mutex, @interval) unless @closed
146
+ break if @closed
147
+
148
+ retained = @idle.keys.sort_by { |session| -session.sequence }.first(@min_idle_sessions)
149
+ result = @idle.filter_map do |session, since|
150
+ session if !retained.include?(session) && Deadline.now - since >= @idle_timeout
151
+ end
152
+ result.each do |session|
153
+ @idle.delete(session)
154
+ @sessions.delete(session)
155
+ end
156
+ result
157
+ end
158
+ break unless expired
159
+
160
+ expired.each(&:close)
161
+ end
162
+ end
163
+ @maintenance.name = "anytls-pool"
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnyTLS
4
+ # 虚拟字节流,不暴露共享 TLS 文件描述符,也不冒充真实 IO。
5
+ # 写入串行执行并分帧;成功写入仅确认本地传输完成,
6
+ # FIN 同时关闭该虚拟流的两个方向。
7
+ class Stream
8
+ attr_reader :id
9
+
10
+ def initialize(session, id, receive_limit: 262_144)
11
+ @session = session
12
+ @id = id
13
+ @buffer = ByteBuffer.new(limit: receive_limit)
14
+ @mutex = Mutex.new
15
+ @changed = ConditionVariable.new
16
+ @closed = false
17
+ @writing = false
18
+ @connection_status = :unconfirmed
19
+ end
20
+
21
+ def connection_status
22
+ @mutex.synchronize { @connection_status }
23
+ end
24
+
25
+ def closed?
26
+ @mutex.synchronize { @closed }
27
+ end
28
+
29
+ def inspect
30
+ "#<#{self.class} id=#{id} closed=#{closed?}>"
31
+ end
32
+
33
+ def readpartial(length, outbuf = nil, timeout: nil)
34
+ result = @buffer.readpartial(length, deadline: Deadline.new(timeout))
35
+ outbuf ? outbuf.replace(result) : result
36
+ rescue EOFError
37
+ outbuf&.clear
38
+ raise
39
+ end
40
+
41
+ def read(length, outbuf = nil, timeout: nil)
42
+ raise ArgumentError, "length must be nonnegative" unless length.is_a?(Integer) && length >= 0
43
+
44
+ deadline = Deadline.new(timeout)
45
+ result = String.new(encoding: Encoding::BINARY)
46
+ begin
47
+ result << @buffer.readpartial(length - result.bytesize, deadline: deadline) while result.bytesize < length
48
+ rescue EOFError
49
+ if result.empty? && length.positive?
50
+ outbuf&.clear
51
+ return nil
52
+ end
53
+ end
54
+ outbuf ? outbuf.replace(result) : result
55
+ end
56
+
57
+ def write(bytes, timeout: nil)
58
+ raise TypeError, "write expects a String" unless bytes.is_a?(String)
59
+
60
+ deadline = Deadline.new(timeout)
61
+ acquire_writer(deadline)
62
+ begin
63
+ offset = 0
64
+ while offset < bytes.bytesize
65
+ ensure_open!
66
+ chunk = bytes.byteslice(offset, 16_384).b.freeze
67
+ @session.write_stream(id, chunk, deadline: deadline)
68
+ offset += chunk.bytesize
69
+ end
70
+ offset
71
+ ensure
72
+ @mutex.synchronize do
73
+ @writing = false
74
+ @changed.broadcast
75
+ end
76
+ end
77
+ end
78
+
79
+ def flush
80
+ self
81
+ end
82
+
83
+ def close
84
+ return nil if closed?
85
+
86
+ @session.close_stream(id)
87
+ nil
88
+ end
89
+
90
+ # 等待目标连接的 SYNACK,并非认证回执;v1 对端无法确认目标,
91
+ # 因此等待会持续到超时或流关闭。
92
+ def wait_connected(timeout: 5)
93
+ deadline = Deadline.new(timeout)
94
+ @mutex.synchronize do
95
+ deadline.wait(@changed, @mutex) while @connection_status == :unconfirmed && !@closed
96
+ raise @terminal_error if @terminal_error
97
+ raise ClosedError, "stream closed" if @closed
98
+
99
+ @connection_status
100
+ end
101
+ end
102
+
103
+ def buffered_bytes
104
+ @buffer.size
105
+ end
106
+
107
+ # 由会话驱动的操作;这些方法不得回调应用代码。
108
+ def receive(bytes)
109
+ @buffer.push(bytes)
110
+ end
111
+
112
+ def confirm
113
+ @mutex.synchronize do
114
+ @connection_status = :confirmed unless @closed
115
+ @changed.broadcast
116
+ end
117
+ end
118
+
119
+ def finish(error = nil, local: false)
120
+ @mutex.synchronize do
121
+ return if @closed
122
+
123
+ @closed = true
124
+ @terminal_error = error
125
+ @connection_status = :failed if error
126
+ @changed.broadcast
127
+ end
128
+ @buffer.finish(error, discard: local)
129
+ end
130
+
131
+ private
132
+
133
+ def ensure_open!
134
+ @mutex.synchronize do
135
+ raise @terminal_error if @terminal_error
136
+ raise ClosedError, "stream closed" if @closed
137
+ end
138
+ end
139
+
140
+ def acquire_writer(deadline)
141
+ @mutex.synchronize do
142
+ deadline.wait(@changed, @mutex) while @writing && !@closed
143
+ raise @terminal_error if @terminal_error
144
+ raise ClosedError, "stream closed" if @closed
145
+
146
+ deadline.check!
147
+ @writing = true
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnyTLS
4
+ # 在 socket 使用前构建并冻结 TLS 上下文;客户端验证证书链,
5
+ # 传输层校验对端名称,自签名证书必须显式启用。
6
+ class TLSConfig
7
+ attr_reader :sni, :insecure, :context
8
+
9
+ def initialize(server: false, cert: nil, key: nil, ca_file: nil, sni: nil, insecure: false,
10
+ self_signed: false, min_version: nil, max_version: nil)
11
+ [server, insecure, self_signed].each do |value|
12
+ raise ConfigurationError, "TLS flags must be boolean" unless [true, false].include?(value)
13
+ end
14
+ if self_signed && (cert || key)
15
+ raise ConfigurationError,
16
+ "self-signed and certificate files are mutually exclusive"
17
+ end
18
+ raise ConfigurationError, "certificate and key must be supplied together" if cert.nil? != key.nil?
19
+ raise ConfigurationError, "server certificate and key are required" if server && !self_signed && !cert
20
+ raise ConfigurationError, "self-signed is a server option" if self_signed && !server
21
+
22
+ Address.new(host: sni, port: 0) unless sni.nil?
23
+
24
+ @sni = sni&.dup&.freeze
25
+ @insecure = insecure
26
+ @context = OpenSSL::SSL::SSLContext.new
27
+ if server
28
+ @context.cert, @context.key, chain = self_signed ? generate_certificate : load_certificate(cert, key)
29
+ @context.extra_chain_cert = chain if chain&.any?
30
+ unless @context.cert.check_private_key(@context.key)
31
+ raise ConfigurationError,
32
+ "certificate does not match private key"
33
+ end
34
+ else
35
+ # 显式 CA 必须在 set_params 前设置,避免加载到进程共享的默认信任库。
36
+ @context.ca_file = ca_file if ca_file
37
+ @context.set_params(verify_mode: insecure ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER)
38
+ # 显式使用 IP 作为 SNI 配置时,发送的 SNI 与证书验证名称有所区别。
39
+ @context.verify_hostname = false
40
+ end
41
+ @context.min_version = min_version || OpenSSL::SSL::TLS1_2_VERSION
42
+ @context.max_version = max_version if max_version
43
+ @context.freeze
44
+ freeze
45
+ end
46
+
47
+ def inspect
48
+ "#<#{self.class} insecure=#{insecure}>"
49
+ end
50
+
51
+ private
52
+
53
+ def load_certificate(cert, key)
54
+ certificates = cert.is_a?(OpenSSL::X509::Certificate) ? [cert] : OpenSSL::X509::Certificate.load_file(cert)
55
+ private_key = key.respond_to?(:private?) ? key : OpenSSL::PKey.read(File.binread(key))
56
+ [certificates.first, private_key, certificates.drop(1)]
57
+ end
58
+
59
+ def generate_certificate
60
+ key = OpenSSL::PKey::EC.generate("prime256v1")
61
+ certificate = OpenSSL::X509::Certificate.new
62
+ certificate.version = 2
63
+ certificate.serial = SecureRandom.random_number(2**128)
64
+ certificate.subject = certificate.issuer = OpenSSL::X509::Name.parse("/CN=localhost")
65
+ certificate.public_key = key
66
+ certificate.not_before = Time.now - 60
67
+ certificate.not_after = Time.now + (86_400 * 30)
68
+ factory = OpenSSL::X509::ExtensionFactory.new
69
+ factory.subject_certificate = factory.issuer_certificate = certificate
70
+ certificate.add_extension(factory.create_extension("basicConstraints", "CA:TRUE", true))
71
+ certificate.add_extension(factory.create_extension("subjectAltName", "DNS:localhost,IP:127.0.0.1,IP:::1"))
72
+ certificate.sign(key, OpenSSL::Digest.new("SHA256"))
73
+ [certificate, key]
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnyTLS
4
+ module Transport
5
+ # 只有 Session 所有者线程可调用 TLS 方法;待重试的 SSL 写入
6
+ # 在 WANT_READ/WANT_WRITE 重试期间保持完全相同的字节和长度。
7
+ class TLSConnection
8
+ attr_reader :io, :write_wait
9
+
10
+ def initialize(socket, config:, server:, host: nil, cancel_io: nil, cancelled: nil)
11
+ @io = socket
12
+ @config = config
13
+ @server = server
14
+ @cancel_io = cancel_io
15
+ @cancelled = cancelled
16
+ @verification_name = config.sni || host
17
+ @ssl = OpenSSL::SSL::SSLSocket.new(socket, config.context)
18
+ @ssl.sync_close = true
19
+ @ssl.hostname = @verification_name if !server && @verification_name && !ip_literal?(@verification_name)
20
+ end
21
+
22
+ def handshake(timeout: 5)
23
+ deadline = Deadline.new(timeout)
24
+ loop do
25
+ raise ClosedError, "TLS setup cancelled" if @cancelled&.call
26
+
27
+ deadline.check!
28
+ result = @server ? @ssl.accept_nonblock(exception: false) : @ssl.connect_nonblock(exception: false)
29
+ break unless result.is_a?(Symbol)
30
+
31
+ wait_for_io(result, deadline)
32
+ end
33
+ @ssl.post_connection_check(@verification_name) unless @server || @config.insecure
34
+ end
35
+
36
+ def authenticate(password_digest, scheme:, timeout: 5)
37
+ if @server
38
+ Protocol::Authentication.read(self, password_digest: password_digest, timeout: timeout)
39
+ else
40
+ IOHelpers.write_all(self, Protocol::Authentication.encode(password_digest, scheme: scheme), timeout: timeout)
41
+ end
42
+ end
43
+
44
+ def read_nonblock(length, exception: false)
45
+ @ssl.read_nonblock(length, exception: exception)
46
+ end
47
+
48
+ def write_nonblock(bytes, exception: false)
49
+ @ssl.write_nonblock(bytes, exception: exception)
50
+ end
51
+
52
+ def wait_for_io(direction, deadline)
53
+ deadline.check!
54
+ raise ClosedError, "TLS setup cancelled" if @cancelled&.call
55
+
56
+ readers = @cancel_io ? [@cancel_io] : []
57
+ readers << @io if direction == :wait_readable
58
+ writers = direction == :wait_writable ? [@io] : []
59
+ ready = IO.select(readers, writers, nil, deadline.remaining)
60
+ raise ClosedError, "TLS setup cancelled" if @cancelled&.call
61
+ raise TimeoutError, "TLS setup timed out" unless ready
62
+
63
+ nil
64
+ end
65
+
66
+ def pending
67
+ @ssl.pending
68
+ end
69
+
70
+ def inspect
71
+ "#<#{self.class} role=#{@server ? 'server' : 'client'}>"
72
+ end
73
+
74
+ def begin_write(bytes)
75
+ raise ProtocolError, "overlapping TLS writes" if @write_buffer
76
+
77
+ @write_buffer = bytes
78
+ @write_wait = nil
79
+ end
80
+
81
+ def advance_write
82
+ result = @ssl.write_nonblock(@write_buffer, exception: false)
83
+ if result.is_a?(Symbol)
84
+ @write_wait = result
85
+ return result
86
+ end
87
+ raise TransportError, "TLS write made no progress" unless result.positive?
88
+
89
+ @write_wait = nil
90
+ if result == @write_buffer.bytesize
91
+ @write_buffer = nil
92
+ :complete
93
+ else
94
+ @write_buffer = @write_buffer.byteslice(result..)
95
+ :progress
96
+ end
97
+ end
98
+
99
+ def close
100
+ # 失败写入仍占有 SSL 重试状态时,不执行阻塞式 TLS 关闭握手。
101
+ @io.close unless @io.closed?
102
+ @ssl.sysclose
103
+ rescue IOError, SystemCallError, OpenSSL::SSL::SSLError
104
+ nil
105
+ end
106
+
107
+ private
108
+
109
+ def ip_literal?(host)
110
+ IPAddr.new(host)
111
+ true
112
+ rescue IPAddr::InvalidAddressError
113
+ false
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ipaddr"
4
+
5
+ module AnyTLS
6
+ # UDP-over-TCP v2 消息定界;地址类型标记与 SOCKS 地址格式不同。
7
+ module UoT
8
+ MAGIC_HOST = "sp.v2.udp-over-tcp.arpa"
9
+
10
+ # 在字节流上提供数据包接口,读取和写入分别串行化。
11
+ # 数据包部分传输失败时关闭流,防止从错误的消息边界继续解析。
12
+ class Connection
13
+ # 线协议长度上限;操作系统可能施加更低的 UDP 大小限制。
14
+ MAX_PACKET_SIZE = 65_535
15
+ attr_reader :destination
16
+
17
+ def initialize(stream, destination: nil, client: true, timeout: nil)
18
+ @stream = stream
19
+ @destination = destination
20
+ @write_gate = Queue.new
21
+ @read_gate = Queue.new
22
+ @write_gate << true
23
+ @read_gate << true
24
+ if client
25
+ target = destination || Address.new(host: "0.0.0.0", port: 0)
26
+ request = [destination ? 1 : 0].pack("C") + Protocol::SocksAddress.encode(target)
27
+ IOHelpers.write_all(stream, request, timeout: timeout)
28
+ end
29
+ rescue StandardError
30
+ stream.close unless stream.closed?
31
+ raise
32
+ end
33
+
34
+ def self.accept(stream, timeout: nil)
35
+ deadline = Deadline.new(timeout)
36
+ mode = IOHelpers.read_exact(stream, 1, timeout: deadline.remaining).unpack1("C")
37
+ raise ProtocolError, "invalid UoT mode" unless [0, 1].include?(mode)
38
+
39
+ destination = Protocol::SocksAddress.read(stream, timeout: deadline.remaining)
40
+ new(stream, destination: mode == 1 ? destination : nil, client: false)
41
+ end
42
+
43
+ def send_packet(payload, destination: nil, timeout: nil)
44
+ deadline = Deadline.new(timeout)
45
+ payload = String(payload).b
46
+ raise BufferLimitExceeded, "UDP payload exceeds #{MAX_PACKET_SIZE} bytes" if payload.bytesize > MAX_PACKET_SIZE
47
+
48
+ target = destination || @destination
49
+ raise ArgumentError, "destination is required" unless target
50
+ if @destination && (target.host != @destination.host || target.port != @destination.port)
51
+ raise ArgumentError, "connected UoT destination cannot change"
52
+ end
53
+
54
+ bytes = @destination ? +"".b : encode_address(target)
55
+ bytes << [payload.bytesize].pack("n") << payload
56
+ with_gate(@write_gate, deadline) { IOHelpers.write_all(@stream, bytes, timeout: deadline.remaining) }
57
+ payload.bytesize
58
+ end
59
+
60
+ def receive_packet(timeout: nil)
61
+ deadline = Deadline.new(timeout)
62
+ with_gate(@read_gate, deadline) do
63
+ destination = @destination || read_address(deadline)
64
+ length = IOHelpers.read_exact(@stream, 2, timeout: deadline.remaining).unpack1("n")
65
+ raise BufferLimitExceeded, "UDP payload exceeds #{MAX_PACKET_SIZE} bytes" if length > MAX_PACKET_SIZE
66
+
67
+ [IOHelpers.read_exact(@stream, length, timeout: deadline.remaining), destination]
68
+ end
69
+ end
70
+
71
+ def close
72
+ IOHelpers.close(@stream) unless @stream.closed?
73
+ end
74
+
75
+ def closed? = @stream.closed?
76
+
77
+ private
78
+
79
+ def with_gate(gate, deadline)
80
+ deadline.check!
81
+ acquired = gate.pop(timeout: deadline.remaining)
82
+ raise TimeoutError, "datagram operation timed out waiting for another operation" unless acquired
83
+ raise ClosedError, "datagram connection closed" if closed?
84
+
85
+ begin
86
+ yield
87
+ rescue Error, IOError, SystemCallError
88
+ # 部分数据包无法从原消息边界恢复传输。
89
+ close
90
+ raise
91
+ end
92
+ ensure
93
+ gate << true if acquired
94
+ end
95
+
96
+ def encode_address(address)
97
+ encoded = Protocol::SocksAddress.encode(address).dup
98
+ encoded.setbyte(0, { 1 => 0, 4 => 1, 3 => 2 }.fetch(encoded.getbyte(0)))
99
+ encoded
100
+ end
101
+
102
+ def read_address(deadline)
103
+ type = IOHelpers.read_exact(@stream, 1, timeout: deadline.remaining).unpack1("C")
104
+ host = case type
105
+ when 0 then IPAddr.ntop(IOHelpers.read_exact(@stream, 4, timeout: deadline.remaining))
106
+ when 1 then IPAddr.ntop(IOHelpers.read_exact(@stream, 16, timeout: deadline.remaining))
107
+ when 2
108
+ size = IOHelpers.read_exact(@stream, 1, timeout: deadline.remaining).unpack1("C")
109
+ raise ProtocolError, "empty UoT domain" if size.zero?
110
+
111
+ IOHelpers.read_exact(@stream, size, timeout: deadline.remaining)
112
+ else
113
+ raise ProtocolError, "invalid UoT address type"
114
+ end
115
+ port = IOHelpers.read_exact(@stream, 2, timeout: deadline.remaining).unpack1("n")
116
+ Address.new(host: host, port: port)
117
+ rescue ArgumentError => error
118
+ raise ProtocolError, "invalid UoT address", cause: error
119
+ end
120
+ end
121
+ end
122
+ end