socks_handler 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/.rspec +3 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +323 -0
- data/Rakefile +15 -0
- data/docker-compose.yml +89 -0
- data/lib/socks_handler/address_type.rb +12 -0
- data/lib/socks_handler/authentication_method.rb +12 -0
- data/lib/socks_handler/command.rb +12 -0
- data/lib/socks_handler/direct_access_rule.rb +16 -0
- data/lib/socks_handler/errors.rb +35 -0
- data/lib/socks_handler/proxy_access_rule.rb +20 -0
- data/lib/socks_handler/rule.rb +66 -0
- data/lib/socks_handler/socket_socksify.rb +27 -0
- data/lib/socks_handler/tcp.rb +106 -0
- data/lib/socks_handler/tcpsocket_socksify.rb +23 -0
- data/lib/socks_handler/udp.rb +39 -0
- data/lib/socks_handler/udpsocket.rb +108 -0
- data/lib/socks_handler/udpsocket_socksify.rb +28 -0
- data/lib/socks_handler/username_password_authenticator.rb +31 -0
- data/lib/socks_handler/version.rb +6 -0
- data/lib/socks_handler.rb +114 -0
- data/sig/defs.rbs +509 -0
- data/sig/lib/socks_handler/rule.rbs +5 -0
- data/sig/lib/socks_handler/username_password_authenticator.rbs +6 -0
- data/sig/socket.rbs +4 -0
- data/socks_handler.gemspec +31 -0
- metadata +75 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 38955165952ad878f7e2bef2bd818cc4b15232879c3ba967ab1ca62e1ad90bc1
|
4
|
+
data.tar.gz: 126326454aa18b570e72f02d3336c507c4ed63effbe3f95e09ac1eeb8ea019eb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f1793dfddaede01289c23c9d319b1be7144526fdf65eae3ed0eca36a842cc44d4886447b4397b98c98816b9b556e7448b614f4b4ae07a6ec4a2a4149ca5ea695
|
7
|
+
data.tar.gz: fee6367cfd655a0266be4e71d323a0bfa6ffcf2794ab4fdc57afd2344cfe1fcac93226867d7a5b5c6afd86e1590f8bb23294311a79af7efda379f0c86b578c56
|
data/.rspec
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in socks_handler.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem "rake", "~> 13.0"
|
9
|
+
|
10
|
+
gem "rspec", "~> 3.0"
|
11
|
+
|
12
|
+
# For generating sig/defs.rbs
|
13
|
+
gem "yard"
|
14
|
+
gem "sord"
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 abicky
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,323 @@
|
|
1
|
+
# SocksHandler
|
2
|
+
|
3
|
+
SocksHandler is a flexible socksifier for sockets created by `TCPSocket.new`, `Socket.tcp`, or `UDPSocket.new` that solves the following issues:
|
4
|
+
|
5
|
+
* `SOCKSSocket` is not easy to use
|
6
|
+
- It is unavailable unless ruby is built with `--enable-socks`, and even if it is available, we cannot use domain names that the network where the program runs cannot resolve since socket classes, including `SOCKSSocket`, call `getaddrinfo` at initialization.
|
7
|
+
* Famous socksifiers such as [socksify](https://www.inet.no/dante/doc/1.3.x/socksify.1.html) and [proxychains4](https://github.com/rofl0r/proxychains-ng) don't support rules using domain names
|
8
|
+
- Besides, they don't work on macOS if Ruby is managed by rbenv maybe due to SIP (System Integrity Protection)
|
9
|
+
|
10
|
+
For more details, see the section "Related Work."
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Install the gem and add to the application's Gemfile by executing:
|
15
|
+
|
16
|
+
$ bundle add socks_handler
|
17
|
+
|
18
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
19
|
+
|
20
|
+
$ gem install socks_handler
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
### Socksify TCP Connections
|
25
|
+
|
26
|
+
Assuming that a SOCKS server that can access the host "nginx" is listening on 127.0.0.1:1080. You can prepare such an environment with the following docker-compose.yml:
|
27
|
+
|
28
|
+
```yaml
|
29
|
+
version: "2.4"
|
30
|
+
services:
|
31
|
+
sockd:
|
32
|
+
image: wernight/dante
|
33
|
+
ports:
|
34
|
+
- 1080:1080
|
35
|
+
|
36
|
+
nginx:
|
37
|
+
image: nginx
|
38
|
+
```
|
39
|
+
|
40
|
+
Here is an example to create a socket that can access the host "nginx" from the Docker host:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
require "socks_handler"
|
44
|
+
|
45
|
+
socket = TCPSocket.new("127.0.0.1", 1080) # or Socket.tcp("127.0.0.1", 1080)
|
46
|
+
SocksHandler::TCP.establish_connection(socket, "nginx", 80)
|
47
|
+
|
48
|
+
socket.write(<<~REQUEST.gsub("\n", "\r\n"))
|
49
|
+
HEAD / HTTP/1.1
|
50
|
+
Host: nginx
|
51
|
+
|
52
|
+
REQUEST
|
53
|
+
puts socket.gets #=> HTTP/1.1 200 OK
|
54
|
+
```
|
55
|
+
|
56
|
+
If you want to access the host through the SOCKS server implicitly, you can use `SocksHandler.socksify` as follows:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
require "socks_handler"
|
60
|
+
|
61
|
+
SocksHandler::TCP.socksify([
|
62
|
+
SocksHandler::ProxyAccessRule.new(
|
63
|
+
host_patterns: ["nginx"],
|
64
|
+
socks_server: "127.0.0.1:1080",
|
65
|
+
)
|
66
|
+
])
|
67
|
+
|
68
|
+
socket = TCPSocket.new("nginx", 80)
|
69
|
+
socket.write(<<~REQUEST.gsub("\n", "\r\n"))
|
70
|
+
HEAD / HTTP/1.1
|
71
|
+
Host: nginx
|
72
|
+
|
73
|
+
REQUEST
|
74
|
+
puts socket.gets #=> HTTP/1.1 200 OK
|
75
|
+
```
|
76
|
+
|
77
|
+
With `SocksHandler::TCP.socksify`, other methods using `TCPSocket.new` or `Socket.tcp` also access the remote host through the SOCKS server:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
require "net/http"
|
81
|
+
require "socks_handler"
|
82
|
+
|
83
|
+
SocksHandler::TCP.socksify([
|
84
|
+
SocksHandler::ProxyAccessRule.new(
|
85
|
+
host_patterns: ["nginx"],
|
86
|
+
socks_server: "127.0.0.1:1080",
|
87
|
+
)
|
88
|
+
])
|
89
|
+
|
90
|
+
Net::HTTP.start("nginx", 80) do |http|
|
91
|
+
pp http.head("/") #=> #<Net::HTTPOK 200 OK readbody=true>
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
For more details, see the document of `SocksHandler::TCP.socksify`:
|
96
|
+
|
97
|
+
```
|
98
|
+
$ ri SocksHandler::TCP.socksify
|
99
|
+
```
|
100
|
+
|
101
|
+
### Socksify UDP Connections
|
102
|
+
|
103
|
+
Assuming that a SOCKS server that can access the host "echo", which is a UDP echo server, is listening on 127.0.0.1:1080. You can prepare such an environment with the following docker-compose.yml:
|
104
|
+
|
105
|
+
```yaml
|
106
|
+
version: "2.4"
|
107
|
+
services:
|
108
|
+
sockd:
|
109
|
+
image: wernight/dante
|
110
|
+
ports:
|
111
|
+
- 1080:1080
|
112
|
+
- 1024-1030:1024-1030/udp
|
113
|
+
sysctls:
|
114
|
+
net.ipv4.ip_local_port_range: "1024 1030"
|
115
|
+
|
116
|
+
echo:
|
117
|
+
image: abicky/ncat:latest
|
118
|
+
command: -e /bin/cat -kul 7
|
119
|
+
init: true
|
120
|
+
```
|
121
|
+
|
122
|
+
Here is an example to create a socket that can access the host "nginx" from the Docker host:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
require "socks_handler"
|
126
|
+
|
127
|
+
tcp_socket = TCPSocket.new("127.0.0.1", 1080) # or Socket.tcp("127.0.0.1", 1080)
|
128
|
+
udp_socket = SocksHandler::UDP.associate_udp(tcp_socket, "0.0.0.0", 0)
|
129
|
+
|
130
|
+
udp_socket.send("hello", 0, "echo", 7)
|
131
|
+
puts udp_socket.gets #=> hello
|
132
|
+
```
|
133
|
+
|
134
|
+
|
135
|
+
## Limitation
|
136
|
+
|
137
|
+
As `SocksHandler` only socksifies TCP connections created by `TCPSocket.new` or `Socket.tcp`, it doesn't socksify connections created by native extensions.
|
138
|
+
|
139
|
+
For example, assuming that a SOCKS server that can access the host "mysql" is listening on 127.0.0.1:1080 as follows:
|
140
|
+
|
141
|
+
```yaml
|
142
|
+
version: "2.4"
|
143
|
+
services:
|
144
|
+
sockd:
|
145
|
+
image: wernight/dante
|
146
|
+
|
147
|
+
mysql:
|
148
|
+
image: mysql:8
|
149
|
+
environment:
|
150
|
+
MYSQL_ROOT_PASSWORD: password
|
151
|
+
```
|
152
|
+
|
153
|
+
The following code raises `Mysql2::Error::ConnectionError` because the gem "mysql2" tries to connect to the server via a native extension:
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
require "mysql2"
|
157
|
+
|
158
|
+
SocksHandler.socksify([
|
159
|
+
SocksHandler::ProxyAccessRule.new(
|
160
|
+
host_patterns: ["mysql"],
|
161
|
+
socks_server: "127.0.0.1:1080",
|
162
|
+
)
|
163
|
+
])
|
164
|
+
|
165
|
+
client = Mysql2::Client.new(
|
166
|
+
host: "mysql",
|
167
|
+
port: 3306,
|
168
|
+
username: "root",
|
169
|
+
password: "password",
|
170
|
+
)
|
171
|
+
client.ping
|
172
|
+
#=> Unknown MySQL server host 'mysql' (8) (Mysql2::Error::ConnectionError)
|
173
|
+
```
|
174
|
+
|
175
|
+
## Related Work
|
176
|
+
|
177
|
+
### Gems
|
178
|
+
|
179
|
+
The following projects provide similar gems:
|
180
|
+
|
181
|
+
* [ruby-proxifier](https://github.com/samuelkadolph/ruby-proxifier)
|
182
|
+
- This project seems to no longer be maintained.
|
183
|
+
* [socksify-ruby](https://github.com/astro/socksify-ruby)
|
184
|
+
|
185
|
+
### SOCKSSocket
|
186
|
+
|
187
|
+
On macOS, you can build ruby with `SOCKSSocket` as follows:
|
188
|
+
|
189
|
+
```
|
190
|
+
$ brew install dante bison
|
191
|
+
$ git clone https://github.com/ruby/ruby.git
|
192
|
+
$ cd ruby
|
193
|
+
$ git checkout v3_2_2
|
194
|
+
$ ./configure --enable-socks
|
195
|
+
$ PATH="/usr/local/opt/bison/bin:$PATH" make -j$(nproc) install
|
196
|
+
$ cat <<EOF >/etc/socks.conf
|
197
|
+
route {
|
198
|
+
from: 0.0.0.0/0 to: 0.0.0.0/0 via: 127.0.0.1 port = 1080
|
199
|
+
proxyprotocol: socks_v5
|
200
|
+
method: none
|
201
|
+
}
|
202
|
+
EOF
|
203
|
+
```
|
204
|
+
|
205
|
+
Here is example code to access nginx launched using docker-compose.yml in the section "Usage":
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
require "socket"
|
209
|
+
|
210
|
+
ip = `docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker compose ps -q nginx)`.chomp
|
211
|
+
[ip, "nginx"].each do |host|
|
212
|
+
puts "Send an HTTP request to #{host}"
|
213
|
+
socket = SOCKSSocket.new(host, 80)
|
214
|
+
socket.write(<<~REQUEST.gsub("\n", "\r\n"))
|
215
|
+
HEAD / HTTP/1.1
|
216
|
+
Host: nginx
|
217
|
+
|
218
|
+
REQUEST
|
219
|
+
puts "Received: #{socket.gets}"
|
220
|
+
end
|
221
|
+
```
|
222
|
+
|
223
|
+
As you can see below, we cannot use the domain name "nginx" since socket classes, including `SOCKSSocket`, call `getaddrinfo` at initialization:
|
224
|
+
|
225
|
+
```
|
226
|
+
$ /usr/local/bin/ruby /path/to/code.rb
|
227
|
+
Send an HTTP request to 192.168.160.4
|
228
|
+
Received: HTTP/1.1 200 OK
|
229
|
+
Send an HTTP request to nginx
|
230
|
+
code.rb:6:in `initialize': getaddrinfo: nodename nor servname provided, or not known (SocketError)
|
231
|
+
from code.rb:6:in `new'
|
232
|
+
from code.rb:6:in `block in <main>'
|
233
|
+
from code.rb:4:in `each'
|
234
|
+
from code.rb:4:in `<main>'
|
235
|
+
```
|
236
|
+
|
237
|
+
### ProxyChains-NG
|
238
|
+
|
239
|
+
[ProxyChains-NG](https://github.com/rofl0r/proxychains-ng) is a socksifier that works well even on macOS.
|
240
|
+
|
241
|
+
On macOS, you can install it as follows:
|
242
|
+
|
243
|
+
```
|
244
|
+
$ brew install proxychains-ng
|
245
|
+
```
|
246
|
+
|
247
|
+
Then edit `/usr/local/etc/proxychains.conf` to use the socks server listening on 127.0.0.1:1080:
|
248
|
+
|
249
|
+
```
|
250
|
+
[ProxyList]
|
251
|
+
socks5 127.0.0.1 1080
|
252
|
+
```
|
253
|
+
|
254
|
+
ProxyChains-NG can socksify even connections created by native extensions. Here is example code to demonstrate it:
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
require "net/http"
|
258
|
+
require "mysql2"
|
259
|
+
|
260
|
+
Net::HTTP.start("nginx", 80) do |http|
|
261
|
+
pp http.head("/")
|
262
|
+
end
|
263
|
+
|
264
|
+
client = Mysql2::Client.new(
|
265
|
+
host: "mysql",
|
266
|
+
port: 3306,
|
267
|
+
username: "root",
|
268
|
+
password: "password",
|
269
|
+
)
|
270
|
+
puts client.ping
|
271
|
+
```
|
272
|
+
|
273
|
+
As you can see below, the program can access containers though the socks server:
|
274
|
+
|
275
|
+
```
|
276
|
+
$ proxychains4 /usr/local/bin/ruby /path/to/code.rb
|
277
|
+
[proxychains] config file found: /usr/local/etc/proxychains.conf
|
278
|
+
[proxychains] preloading /usr/local/Cellar/proxychains-ng/4.16/lib/libproxychains4.dylib
|
279
|
+
[proxychains] DLL init: proxychains-ng 4.16
|
280
|
+
[proxychains] Strict chain ... 127.0.0.1:1080 ... nginx:80 ... OK
|
281
|
+
#<Net::HTTPOK 200 OK readbody=true>
|
282
|
+
[proxychains] Strict chain ... 127.0.0.1:1080 ... mysql:3306 ... OK
|
283
|
+
true
|
284
|
+
```
|
285
|
+
|
286
|
+
However, it doesn't work if Ruby is managed by rbenv:
|
287
|
+
|
288
|
+
```
|
289
|
+
$ rbenv local
|
290
|
+
3.2.2
|
291
|
+
$ proxychains4 ruby /path/to/code.rb
|
292
|
+
[proxychains] config file found: /usr/local/etc/proxychains.conf
|
293
|
+
[proxychains] preloading /usr/local/Cellar/proxychains-ng/4.16/lib/libproxychains4.dylib
|
294
|
+
/Users/arabiki/.anyenv/envs/rbenv/versions/3.2.2/lib/ruby/3.2.0/net/http.rb:1271:in `initialize': Failed to open TCP connection to nginx:80 (getaddrinfo: nodename nor servname provided, or not known) (SocketError)
|
295
|
+
-- snip --
|
296
|
+
```
|
297
|
+
|
298
|
+
Maybe the reason is that macOS doesn't allow any system binaries to preload libraries and rbenv uses `/usr/bin/env`:
|
299
|
+
|
300
|
+
```
|
301
|
+
$ proxychains4 env /usr/local/bin/ruby /path/to/code.rb
|
302
|
+
[proxychains] config file found: /usr/local/etc/proxychains.conf
|
303
|
+
[proxychains] preloading /usr/local/Cellar/proxychains-ng/4.16/lib/libproxychains4.dylib
|
304
|
+
/usr/local/lib/ruby/3.2.0/net/http.rb:1271:in `initialize': Failed to open TCP connection to nginx:80 (getaddrinfo: nodename nor servname provided, or not known) (SocketError)
|
305
|
+
-- snip --
|
306
|
+
```
|
307
|
+
|
308
|
+
Although ProxyChains-NG works well in almost all cases, it cannot use domain names to determine whether to access them through a socks proxy.
|
309
|
+
|
310
|
+
|
311
|
+
## Development
|
312
|
+
|
313
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
314
|
+
|
315
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
316
|
+
|
317
|
+
## Contributing
|
318
|
+
|
319
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/abicky/socks_handler.
|
320
|
+
|
321
|
+
## License
|
322
|
+
|
323
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
task default: :spec
|
9
|
+
|
10
|
+
task :generate_rbs do
|
11
|
+
sh "sord sig/defs.rbs"
|
12
|
+
sh %Q{sed -i#{" " if RUBY_PLATFORM.include?("darwin")}'' '1s/^/# This file was generated by "rake generate_rbs"\\n\\n/' sig/defs.rbs}
|
13
|
+
# Workaround for the error "Detected recursive ancestors: ::SocksHandler::UDPSocket < ::SocksHandler::UDPSocket"
|
14
|
+
sh %Q{sed -i#{" " if RUBY_PLATFORM.include?("darwin")}'' 's/UDPSocket < UDPSocket/UDPSocket < ::UDPSocket/' sig/defs.rbs}
|
15
|
+
end
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
version: "2.4"
|
2
|
+
services:
|
3
|
+
create-work-directory:
|
4
|
+
image: wernight/dante
|
5
|
+
entrypoint: ""
|
6
|
+
command: |
|
7
|
+
sh -c '
|
8
|
+
cat <<EOF >/work/entrypoint.sh
|
9
|
+
#!/bin/sh
|
10
|
+
set -e
|
11
|
+
|
12
|
+
# Allow IPv6 access
|
13
|
+
sed -i "s|from: 0.0.0.0/0 to: 0.0.0.0/0|from: 0/0 to: 0/0|" /etc/sockd.conf
|
14
|
+
|
15
|
+
if [ -n "\$$USERNAME" ]; then
|
16
|
+
sed -i "s/^ \#socksmethod: username/ socksmethod: username/" /etc/sockd.conf
|
17
|
+
|
18
|
+
printf "\$$PASSWORD\\n\$$PASSWORD\\n" | adduser \$$USERNAME
|
19
|
+
fi
|
20
|
+
exec sockd
|
21
|
+
|
22
|
+
EOF
|
23
|
+
|
24
|
+
chmod +x /work/entrypoint.sh
|
25
|
+
'
|
26
|
+
volumes:
|
27
|
+
- type: volume
|
28
|
+
source: workdir
|
29
|
+
target: /work
|
30
|
+
|
31
|
+
sockd-auth-none:
|
32
|
+
image: wernight/dante
|
33
|
+
command: /work/entrypoint.sh
|
34
|
+
ports:
|
35
|
+
- 1080:1080
|
36
|
+
- 1024-1030:1024-1030/udp
|
37
|
+
volumes:
|
38
|
+
- type: volume
|
39
|
+
source: workdir
|
40
|
+
target: /work
|
41
|
+
sysctls:
|
42
|
+
net.ipv4.ip_local_port_range: "1024 1030"
|
43
|
+
depends_on:
|
44
|
+
create-work-directory:
|
45
|
+
condition: service_completed_successfully
|
46
|
+
|
47
|
+
sockd-auth-username-password:
|
48
|
+
image: wernight/dante
|
49
|
+
command: /work/entrypoint.sh
|
50
|
+
environment:
|
51
|
+
USERNAME: user
|
52
|
+
PASSWORD: pass
|
53
|
+
ports:
|
54
|
+
- 1081:1080
|
55
|
+
volumes:
|
56
|
+
- type: volume
|
57
|
+
source: workdir
|
58
|
+
target: /work
|
59
|
+
depends_on:
|
60
|
+
create-work-directory:
|
61
|
+
condition: service_completed_successfully
|
62
|
+
|
63
|
+
nginx:
|
64
|
+
image: nginx
|
65
|
+
|
66
|
+
echo:
|
67
|
+
image: abicky/ncat:latest
|
68
|
+
command: -e /bin/cat -kul 7
|
69
|
+
init: true
|
70
|
+
|
71
|
+
ruby:
|
72
|
+
image: ruby:3.2.2
|
73
|
+
command: "sleep 86400"
|
74
|
+
volumes:
|
75
|
+
- type: bind
|
76
|
+
source: "."
|
77
|
+
target: "/work"
|
78
|
+
working_dir: /work
|
79
|
+
init: true
|
80
|
+
volumes:
|
81
|
+
workdir:
|
82
|
+
|
83
|
+
networks:
|
84
|
+
default:
|
85
|
+
enable_ipv6: true
|
86
|
+
ipam:
|
87
|
+
config:
|
88
|
+
- subnet: 2001:3984:3989::/64
|
89
|
+
gateway: 2001:3984:3989::1
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SocksHandler
|
4
|
+
module AuthenticationMethod
|
5
|
+
# @return [Integer]
|
6
|
+
NONE = 0x00
|
7
|
+
# @return [Integer]
|
8
|
+
GSSAPI = 0x01 # Not supported yet (https://www.ietf.org/rfc/rfc1961.txt)
|
9
|
+
# @return [Integer]
|
10
|
+
USERNAME_PASSWORD = 0x02
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "socks_handler/rule"
|
3
|
+
|
4
|
+
module SocksHandler
|
5
|
+
class DirectAccessRule < Rule
|
6
|
+
# @param host_patterns [Array<String, Regexp>]
|
7
|
+
def initialize(host_patterns:)
|
8
|
+
super(host_patterns: host_patterns)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [true]
|
12
|
+
def direct
|
13
|
+
true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SocksHandler
|
4
|
+
class UnsupportedProtocol < StandardError; end
|
5
|
+
|
6
|
+
class NoAcceptableMethods < StandardError; end
|
7
|
+
|
8
|
+
class AuthenticationFailure < StandardError; end
|
9
|
+
|
10
|
+
class RelayRequestFailure < StandardError
|
11
|
+
# @param code [Integer]
|
12
|
+
def initialize(code)
|
13
|
+
case code
|
14
|
+
when 0x01
|
15
|
+
super("general SOCKS server failure")
|
16
|
+
when 0x02
|
17
|
+
super("connection not allowed by ruleset")
|
18
|
+
when 0x03
|
19
|
+
super("Network unreachable")
|
20
|
+
when 0x04
|
21
|
+
super("Host unreachable")
|
22
|
+
when 0x05
|
23
|
+
super("Connection refused")
|
24
|
+
when 0x06
|
25
|
+
super("TTL expired")
|
26
|
+
when 0x07
|
27
|
+
super("Command not supported")
|
28
|
+
when 0x08
|
29
|
+
super("Address type not supported")
|
30
|
+
else
|
31
|
+
super("Unknown error")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "socks_handler/rule"
|
3
|
+
|
4
|
+
module SocksHandler
|
5
|
+
class ProxyAccessRule < Rule
|
6
|
+
# @param host_patterns [Array<String, Regexp>]
|
7
|
+
# @param socks_server [String] a string in the format "<host>:<port>"
|
8
|
+
# @param username [String, nil]
|
9
|
+
# @param password [String, nil]
|
10
|
+
def initialize(host_patterns:, socks_server:, username: nil, password: nil)
|
11
|
+
host, port = socks_server.split(":")
|
12
|
+
super(host_patterns: host_patterns, host: host, port: port.to_i, username: username, password: password)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [false]
|
16
|
+
def direct
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SocksHandler
|
4
|
+
# @!attribute [r] host
|
5
|
+
# @return [String, nil]
|
6
|
+
# @!attribute [r] port
|
7
|
+
# @return [Integer, nil]
|
8
|
+
# @!attribute [r] username
|
9
|
+
# @return [String, nil]
|
10
|
+
# @!attribute [r] password
|
11
|
+
# @return [String, nil]
|
12
|
+
# @!attribute [r] host_patterns
|
13
|
+
# @return [Array<String, Regexp>]
|
14
|
+
class Rule
|
15
|
+
attr_reader :host, :port, :username, :password, :host_patterns
|
16
|
+
|
17
|
+
# @return [Rule]
|
18
|
+
def self.new(**kwargs)
|
19
|
+
raise "Rule is an abstract class" if self == Rule
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param host_patterns [Array<String, Regexp>]
|
24
|
+
# @param host [String, nil]
|
25
|
+
# @param port [Integer, nil]
|
26
|
+
# @param username [String, nil]
|
27
|
+
# @param password [String, nil]
|
28
|
+
def initialize(host_patterns:, host: nil, port: nil, username: nil, password: nil)
|
29
|
+
@host = host
|
30
|
+
@port = port
|
31
|
+
@username = username
|
32
|
+
@password = password
|
33
|
+
self.host_patterns = host_patterns
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Boolean]
|
37
|
+
def direct
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param value [Array<String, Regexp>]
|
42
|
+
# @return [Array<String, Regexp>]
|
43
|
+
def host_patterns=(value)
|
44
|
+
raise ArgumentError, "host_patterns is not an array" unless value.is_a?(Array)
|
45
|
+
@host_patterns = value.frozen? ? value : value.dup.freeze
|
46
|
+
@remote_host_pattern_regex = Regexp.union(convert_regexps(value))
|
47
|
+
value
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param remote_host [String]
|
51
|
+
# @return [Boolean]
|
52
|
+
def match?(remote_host)
|
53
|
+
@remote_host_pattern_regex.match?(remote_host)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# @param value [Array<String, Regexp>]
|
59
|
+
# @return [Array<Regexp>]
|
60
|
+
def convert_regexps(value)
|
61
|
+
value.map do |v|
|
62
|
+
v.is_a?(Regexp) ? v : Regexp.new("\\A#{Regexp.escape(v)}\\z")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SocksHandler
|
4
|
+
module SocketSocksify
|
5
|
+
# @param remote_host [String]
|
6
|
+
# @param remote_port [Integer, String]
|
7
|
+
# @param local_host [String, nil]
|
8
|
+
# @param local_port [Integer, String]
|
9
|
+
# @param connect_timeout [Integer, Float, nil]
|
10
|
+
# @param resolv_timeout [Integer, Float, nil]
|
11
|
+
# @return [Socket]
|
12
|
+
def tcp(remote_host, remote_port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, &block)
|
13
|
+
rule = SocksHandler::TCP.find_rule(remote_host)
|
14
|
+
return super if rule.nil? || rule.direct
|
15
|
+
|
16
|
+
socket = super(rule.host, rule.port, local_host, local_port, connect_timeout: connect_timeout, resolv_timeout: resolv_timeout, &block)
|
17
|
+
begin
|
18
|
+
SocksHandler::TCP.establish_connection(socket, remote_host, remote_port, rule.username, rule.password)
|
19
|
+
rescue
|
20
|
+
socket.close
|
21
|
+
raise
|
22
|
+
end
|
23
|
+
|
24
|
+
socket
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|