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,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnyTLS
4
+ # 使用有界片段进行双向转发,并负责关闭两个端点。
5
+ # 任一方向首先结束时启动清理,并等待两个转发线程退出。
6
+ module Relay
7
+ BUFFER_SIZE = 16_384
8
+
9
+ # FIN 关闭整条 AnyTLS 流;任一方向 EOF 都会关闭两个端点。
10
+ # 此处不能模拟 TCP 半关闭,否则会丢失协议状态。
11
+ def self.copy(left, right)
12
+ completed = Queue.new
13
+ workers = []
14
+ terminal_error = nil
15
+ begin
16
+ [[left, right], [right, left]].each do |source, destination|
17
+ workers << Thread.new do
18
+ copy_error = nil
19
+ begin
20
+ loop do
21
+ data = source.readpartial(BUFFER_SIZE)
22
+ break if data.nil? || data.empty?
23
+
24
+ IOHelpers.write_all(destination, data)
25
+ end
26
+ rescue IOError, SystemCallError
27
+ # EOF 或另一方向触发的关闭都会结束该方向的转发。
28
+ rescue StandardError => error
29
+ copy_error = error
30
+ ensure
31
+ completed << copy_error
32
+ end
33
+ end
34
+ end
35
+ terminal_error = completed.pop
36
+ rescue StandardError => error
37
+ terminal_error = error
38
+ ensure
39
+ [left, right].each do |endpoint|
40
+ IOHelpers.close(endpoint)
41
+ rescue StandardError => error
42
+ terminal_error ||= error
43
+ end
44
+ workers.each(&:join)
45
+ end
46
+ raise terminal_error if terminal_error
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnyTLS
4
+ # 持有监听器、已接入会话、出站工作线程和目标 socket。
5
+ # 会话所有者线程分派流,由数量受限的工作线程执行目标 I/O。
6
+ class Server
7
+ attr_reader :port
8
+
9
+ def initialize(password:, tls:, host: "0.0.0.0", port: 8443, padding: nil, connect_timeout: 5,
10
+ handshake_timeout: 5, max_sessions: 128, max_streams: 256, logger: nil)
11
+ raise ConfigurationError, "password must be a nonempty String" unless password.is_a?(String) && !password.empty?
12
+
13
+ @address = Address.new(host: host, port: port)
14
+ [max_sessions, max_streams].each do |number|
15
+ unless number.is_a?(Integer) && number.positive?
16
+ raise ConfigurationError,
17
+ "resource limits must be positive integers"
18
+ end
19
+ end
20
+ [connect_timeout, handshake_timeout].each do |value|
21
+ Deadline.new(value)
22
+ raise ConfigurationError, "connection timeouts must be positive" unless value&.positive?
23
+ end
24
+ @port = port
25
+ @tls = tls
26
+ @scheme = padding || Padding::Scheme.default
27
+ @digest = Digest::SHA256.digest(password).freeze
28
+ @connect_timeout = connect_timeout
29
+ @handshake_timeout = handshake_timeout
30
+ @max_sessions = max_sessions
31
+ @max_streams = max_streams
32
+ @logger = logger
33
+ @mutex = Mutex.new
34
+ @changed = ConditionVariable.new
35
+ @sessions = {}
36
+ @workers = []
37
+ @targets = {}
38
+ @active_streams = 0
39
+ @sequence = 0
40
+ @stopping = false
41
+ @stopped = false
42
+ @stop_owner = nil
43
+ end
44
+
45
+ def run
46
+ owns_listener = false
47
+ @mutex.synchronize do
48
+ raise ClosedError, "server stopped" if @stopping
49
+ raise Error, "server already running" if @listener
50
+
51
+ @listener = TCPServer.new(@address.host, @address.port)
52
+ owns_listener = true
53
+ @port = @listener.local_address.ip_port
54
+ end
55
+ loop do
56
+ socket = nil
57
+ begin
58
+ socket = @listener.accept
59
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
60
+ accept_connection(socket)
61
+ socket = nil # 所有权已交给会话,或连接已被拒绝并关闭。
62
+ rescue Errno::EMFILE, Errno::ENFILE, ThreadError => error
63
+ socket&.close unless socket&.closed?
64
+ now = Deadline.now
65
+ if !@resource_warning_at || now - @resource_warning_at >= 5
66
+ Log.emit(@logger, :warn, "accept capacity exhausted (#{error.class})")
67
+ @resource_warning_at = now
68
+ end
69
+ @mutex.synchronize { @changed.wait(@mutex, 0.05) unless @stopping }
70
+ break if @mutex.synchronize { @stopping }
71
+ ensure
72
+ socket&.close unless socket&.closed?
73
+ end
74
+ end
75
+ rescue IOError, SystemCallError
76
+ raise unless owns_listener && @mutex.synchronize { @stopping }
77
+ ensure
78
+ stop(grace: 0) if owns_listener && !@mutex.synchronize { @stopping }
79
+ end
80
+
81
+ # 首次调用者负责停止流程;后续调用者等待同一流程完成。
82
+ # 停止接入后,已有流仍可在宽限期内继续传输。
83
+ def stop(grace: 5)
84
+ deadline = Deadline.new(grace)
85
+ owns_stop = false
86
+ sessions = @mutex.synchronize do
87
+ return if @stopped || @stop_owner == Thread.current
88
+
89
+ if @stopping
90
+ @changed.wait(@mutex) until @stopped
91
+ return
92
+ end
93
+
94
+ owns_stop = true
95
+ @stop_owner = Thread.current
96
+ @stopping = true
97
+ @changed.broadcast
98
+ @listener&.close unless @listener&.closed?
99
+ while @active_streams.positive?
100
+ remaining = deadline.remaining
101
+ break if remaining&.zero?
102
+
103
+ @changed.wait(@mutex, remaining)
104
+ end
105
+ @sessions.keys
106
+ end
107
+ sessions.each { |session| session.close(wait: false) }
108
+ sessions.each(&:close)
109
+ workers, targets = @mutex.synchronize { [@workers.dup, @targets.keys] }
110
+ targets.each { |socket| IOHelpers.close(socket) unless socket.closed? }
111
+ workers.each { |thread| thread.join unless thread == Thread.current }
112
+ nil
113
+ ensure
114
+ if owns_stop
115
+ @mutex.synchronize do
116
+ @stopped = true
117
+ @stop_owner = nil
118
+ @changed.broadcast
119
+ end
120
+ end
121
+ end
122
+
123
+ def stats
124
+ @mutex.synchronize { { sessions: @sessions.size, active_streams: @active_streams }.freeze }
125
+ end
126
+
127
+ def inspect
128
+ "#<#{self.class} listen=#{@address.host}:#{port}>"
129
+ end
130
+
131
+ private
132
+
133
+ def accept_connection(socket)
134
+ @mutex.synchronize do
135
+ if @stopping || @sessions.size >= @max_sessions
136
+ socket.close
137
+ return
138
+ end
139
+ @sequence += 1
140
+ session = Session.new(socket, tls: @tls, password_digest: @digest, scheme: @scheme, server: true,
141
+ sequence: @sequence, handshake_timeout: @handshake_timeout, logger: @logger,
142
+ on_stream: method(:start_stream), on_close: method(:remove_session))
143
+ @sessions[session] = true
144
+ end
145
+ rescue StandardError
146
+ socket.close unless socket.closed?
147
+ raise
148
+ end
149
+
150
+ def remove_session(session)
151
+ @mutex.synchronize do
152
+ @sessions.delete(session)
153
+ @changed.broadcast
154
+ end
155
+ end
156
+
157
+ def start_stream(stream, session)
158
+ @mutex.synchronize do
159
+ # 此回调运行于会话所有者线程;拒绝流时不能同步向该线程
160
+ # 再次提交任务,否则会等待自身执行。
161
+ accepted = !@stopping && @active_streams < @max_streams
162
+ return false unless accepted
163
+
164
+ @active_streams += 1
165
+ @workers.select!(&:alive?)
166
+ @workers << Thread.new do
167
+ handle_stream(stream, session)
168
+ rescue Error, IOError, SystemCallError, SocketError => error
169
+ Log.emit(@logger, :debug, "AnyTLS outbound ended: #{error.class}")
170
+ ensure
171
+ begin
172
+ stream.close
173
+ ensure
174
+ @mutex.synchronize do
175
+ @active_streams -= 1
176
+ @changed.broadcast
177
+ end
178
+ end
179
+ end
180
+ true
181
+ rescue ThreadError
182
+ @active_streams -= 1
183
+ @changed.broadcast
184
+ false
185
+ end
186
+ end
187
+
188
+ def handle_stream(stream, session)
189
+ reported = false
190
+ destination = Protocol::SocksAddress.read(stream, timeout: @connect_timeout)
191
+ if destination.host == "sp.v2.udp-over-tcp.arpa"
192
+ UoT::Server.new(logger: @logger).handle(
193
+ stream,
194
+ on_ready: lambda {
195
+ session.report_connected(stream.id)
196
+ reported = true
197
+ },
198
+ on_failure: lambda { |error|
199
+ session.report_connected(stream.id, error: error)
200
+ reported = true
201
+ }
202
+ )
203
+ else
204
+ target = Dialer.connect(destination, timeout: @connect_timeout)
205
+ @mutex.synchronize { @targets[target] = true }
206
+ session.report_connected(stream.id)
207
+ reported = true
208
+ Relay.copy(stream, target)
209
+ end
210
+ rescue Error, IOError, SystemCallError, SocketError => error
211
+ session.report_connected(stream.id, error: error) unless reported || stream.closed?
212
+ raise
213
+ ensure
214
+ if target
215
+ IOHelpers.close(target) unless target.closed?
216
+ @mutex.synchronize { @targets.delete(target) }
217
+ end
218
+ end
219
+ end
220
+ end