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
data/lib/anytls/relay.rb
ADDED
|
@@ -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
|