socksify 1.7.0 → 1.8.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 +5 -5
- data/bin/socksify_ruby +1 -2
- data/doc/index.html +14 -0
- data/lib/socksify/debug.rb +25 -14
- data/lib/socksify/http.rb +45 -40
- data/lib/socksify/ruby3net_http_connectable.rb +98 -0
- data/lib/socksify/socksproxyable.rb +153 -0
- data/lib/socksify/tcpsocket.rb +82 -0
- data/lib/socksify/version.rb +6 -0
- data/lib/socksify.rb +77 -303
- metadata +15 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c9c9f42d889fe8506efd5c6d7bbd13484e6dfed208ac5d125cfeb661a7f03d1d
|
4
|
+
data.tar.gz: fdb78e9915cb13f17325173d721df354896fb085f82da79a1f7fb2a73b0f8076
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b15e3c4c7b72a03386d0a3f78472d659a44cddd5c9f846d57fc97e3c149a5cedde40958ec78de01a3ac4cb0f828f23f5f7b79adfc4c6d66501973e630f8bb2c
|
7
|
+
data.tar.gz: 2717aac706890dd74854a02dac6c714aebf3f54d26861144dfa32b6d684965e95d1e1aa4c8f078748fd92d28085a6dbd7495957ea65254bbdbba2bf92e9e8609
|
data/bin/socksify_ruby
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
if ARGV.size < 2
|
4
|
-
puts "Usage: #{$
|
4
|
+
puts "Usage: #{$PROGRAM_NAME} <SOCKS host> <SOCKS port> [script args ...]"
|
5
5
|
exit
|
6
6
|
end
|
7
7
|
|
@@ -15,4 +15,3 @@ else
|
|
15
15
|
require 'irb'
|
16
16
|
IRB.start(__FILE__)
|
17
17
|
end
|
18
|
-
|
data/doc/index.html
CHANGED
@@ -77,6 +77,12 @@ Socksify::proxy("127.0.0.1", 9050) {
|
|
77
77
|
</pre>
|
78
78
|
</p>
|
79
79
|
|
80
|
+
<p>
|
81
|
+
Please note: <b>socksify is not thread-safe</b> when used this way!
|
82
|
+
<code>socks_server</code> and <code>socks_port</code> are stored in class
|
83
|
+
<code>@@</code>-variables, and applied to all threads and fibers of application.
|
84
|
+
</p>
|
85
|
+
|
80
86
|
<h3>Use Net::HTTP explicitly via SOCKS</h3>
|
81
87
|
<p>
|
82
88
|
Require the additional library <code>socksify/http</code>
|
@@ -99,6 +105,14 @@ end
|
|
99
105
|
explicitly or use <code>Net::HTTP</code> directly.
|
100
106
|
</p>
|
101
107
|
|
108
|
+
<p>
|
109
|
+
<code>Net::HTTP.SOCKSProxy</code> also supports SOCKS authentication:
|
110
|
+
</p>
|
111
|
+
<pre>
|
112
|
+
Net::HTTP.SOCKSProxy('127.0.0.1', 9050, 'username', 'p4ssw0rd')
|
113
|
+
</pre>
|
114
|
+
|
115
|
+
|
102
116
|
<h3>Resolve addresses via SOCKS</h3>
|
103
117
|
<pre>Socksify::resolve("spaceboyz.net")
|
104
118
|
# => "87.106.131.203"</pre>
|
data/lib/socksify/debug.rb
CHANGED
@@ -1,32 +1,47 @@
|
|
1
|
+
# namespace
|
1
2
|
module Socksify
|
3
|
+
# rubocop:disable Style/Documentation
|
2
4
|
class Color
|
3
5
|
class Reset
|
4
|
-
def self
|
6
|
+
def self.to_s
|
5
7
|
"\e[0m\e[37m"
|
6
8
|
end
|
7
9
|
end
|
8
|
-
|
10
|
+
|
9
11
|
class Red < Color
|
10
|
-
def num
|
12
|
+
def num
|
13
|
+
31
|
14
|
+
end
|
11
15
|
end
|
16
|
+
|
12
17
|
class Green < Color
|
13
|
-
def num
|
18
|
+
def num
|
19
|
+
32
|
20
|
+
end
|
14
21
|
end
|
22
|
+
|
15
23
|
class Yellow < Color
|
16
|
-
def num
|
24
|
+
def num
|
25
|
+
33
|
26
|
+
end
|
17
27
|
end
|
18
|
-
|
19
|
-
|
28
|
+
# rubocop:enable Style/Documentation
|
29
|
+
|
30
|
+
def self.to_s
|
20
31
|
new.to_s
|
21
32
|
end
|
22
|
-
|
33
|
+
|
34
|
+
def num
|
35
|
+
0
|
36
|
+
end
|
37
|
+
|
23
38
|
def to_s
|
24
39
|
"\e[1m\e[#{num}m"
|
25
40
|
end
|
26
41
|
end
|
27
42
|
|
28
43
|
def self.debug=(enabled)
|
29
|
-
|
44
|
+
@debug = enabled
|
30
45
|
end
|
31
46
|
|
32
47
|
def self.debug_debug(str)
|
@@ -41,12 +56,8 @@ module Socksify
|
|
41
56
|
debug(Color::Red, str)
|
42
57
|
end
|
43
58
|
|
44
|
-
private
|
45
|
-
|
46
59
|
def self.debug(color, str)
|
47
|
-
if defined?
|
48
|
-
puts "#{color}#{now_s}#{Color::Reset} #{str}"
|
49
|
-
end
|
60
|
+
puts "#{color}#{now_s}#{Color::Reset} #{str}" if defined?(@debug) && @debug
|
50
61
|
end
|
51
62
|
|
52
63
|
def self.now_s
|
data/lib/socksify/http.rb
CHANGED
@@ -1,59 +1,64 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
-
=end
|
1
|
+
# Copyright (C) 2007 Stephan Maka <stephan@spaceboyz.net>
|
2
|
+
# Copyright (C) 2011 Musy Bite <musybite@gmail.com>
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
16
|
|
19
17
|
require 'socksify'
|
20
18
|
require 'net/http'
|
19
|
+
require_relative 'ruby3net_http_connectable'
|
21
20
|
|
22
21
|
module Net
|
22
|
+
# patched class
|
23
23
|
class HTTP
|
24
|
-
def self.
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
extend delta::ClassMethods
|
24
|
+
def self.socks_proxy(p_host, p_port, username: nil, password: nil)
|
25
|
+
proxyclass.module_eval do
|
26
|
+
include Ruby3NetHTTPConnectable if RUBY_VERSION.to_f > 3.0 # patch #connect method
|
27
|
+
include SOCKSProxyDelta::InstanceMethods
|
28
|
+
extend SOCKSProxyDelta::ClassMethods
|
29
|
+
|
31
30
|
@socks_server = p_host
|
32
31
|
@socks_port = p_port
|
33
|
-
|
32
|
+
@socks_username = username
|
33
|
+
@socks_password = password
|
34
|
+
end
|
35
|
+
|
34
36
|
proxyclass
|
35
37
|
end
|
36
38
|
|
39
|
+
def self.proxyclass
|
40
|
+
@proxyclass ||= Class.new(self).tap { |klass| klass.send(:include, SOCKSProxyDelta) }
|
41
|
+
end
|
42
|
+
|
43
|
+
class << self
|
44
|
+
alias SOCKSProxy socks_proxy # legacy support for non snake case method name
|
45
|
+
end
|
46
|
+
|
37
47
|
module SOCKSProxyDelta
|
48
|
+
# class methods
|
38
49
|
module ClassMethods
|
39
|
-
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
def socks_port
|
44
|
-
@socks_port
|
45
|
-
end
|
50
|
+
attr_reader :socks_server, :socks_port,
|
51
|
+
:socks_username, :socks_password
|
46
52
|
end
|
47
53
|
|
54
|
+
# instance methods - no long supports Ruby < 2
|
48
55
|
module InstanceMethods
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
TCPSocket::SOCKSConnectionPeerAddress.new(self.class.socks_server, self.class.socks_port, address())
|
56
|
-
end
|
56
|
+
def address
|
57
|
+
TCPSocket::SOCKSConnectionPeerAddress.new(
|
58
|
+
self.class.socks_server, self.class.socks_port,
|
59
|
+
@address,
|
60
|
+
self.class.socks_username, self.class.socks_password
|
61
|
+
)
|
57
62
|
end
|
58
63
|
end
|
59
64
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Ruby 3.0 private method Net::HTTP#connect
|
4
|
+
module Ruby3NetHTTPConnectable
|
5
|
+
# rubocop:disable all - CAN'T LINT RUBY SOURCE CODE!
|
6
|
+
def connect
|
7
|
+
if use_ssl? # 3.1 - MOVED FROM FURTHER DOWN IN METHOD
|
8
|
+
# reference early to load OpenSSL before connecting,
|
9
|
+
# as OpenSSL may take time to load.
|
10
|
+
@ssl_context = OpenSSL::SSL::SSLContext.new
|
11
|
+
end
|
12
|
+
|
13
|
+
if proxy? then
|
14
|
+
conn_addr = proxy_address
|
15
|
+
conn_port = proxy_port
|
16
|
+
else
|
17
|
+
conn_addr = conn_address
|
18
|
+
conn_port = port
|
19
|
+
end
|
20
|
+
|
21
|
+
D "opening connection to #{conn_addr}:#{conn_port}..."
|
22
|
+
######## RUBY < 3.1 ########
|
23
|
+
s = Timeout.timeout(@open_timeout, Net::OpenTimeout) {
|
24
|
+
begin
|
25
|
+
TCPSocket.open(conn_addr, conn_port, @local_host, @local_port)
|
26
|
+
rescue => e
|
27
|
+
raise e, "Failed to open TCP connection to " +
|
28
|
+
"#{conn_addr}:#{conn_port} (#{e.message})"
|
29
|
+
end
|
30
|
+
}
|
31
|
+
######## END RUBY < 3.1 ########
|
32
|
+
s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
33
|
+
D "opened"
|
34
|
+
if use_ssl?
|
35
|
+
if proxy?
|
36
|
+
plain_sock = Net::BufferedIO.new(s, read_timeout: @read_timeout, # 3.1 - FULLY QUALIFY CLASS
|
37
|
+
write_timeout: @write_timeout,
|
38
|
+
continue_timeout: @continue_timeout,
|
39
|
+
debug_output: @debug_output)
|
40
|
+
buf = "CONNECT #{conn_address}:#{@port} HTTP/#{Net::HTTP::HTTPVersion}\r\n" # 3.1 - FULLY QUALIFY CONSTANT
|
41
|
+
buf << "Host: #{@address}:#{@port}\r\n"
|
42
|
+
if proxy_user
|
43
|
+
credential = ["#{proxy_user}:#{proxy_pass}"].pack('m0')
|
44
|
+
buf << "Proxy-Authorization: Basic #{credential}\r\n"
|
45
|
+
end
|
46
|
+
buf << "\r\n"
|
47
|
+
plain_sock.write(buf)
|
48
|
+
Net::HTTPResponse.read_new(plain_sock).value # 3.1 - FULLY QUALIFY CLASS
|
49
|
+
# assuming nothing left in buffers after successful CONNECT response
|
50
|
+
end
|
51
|
+
|
52
|
+
ssl_parameters = Hash.new
|
53
|
+
iv_list = instance_variables
|
54
|
+
Net::HTTP::SSL_IVNAMES.each_with_index do |ivname, i| # 3.1 - FULLY QUALIFY CONSTANT
|
55
|
+
if iv_list.include?(ivname)
|
56
|
+
value = instance_variable_get(ivname)
|
57
|
+
unless value.nil?
|
58
|
+
ssl_parameters[Net::HTTP::SSL_ATTRIBUTES[i]] = value # 3.1 - FULLY QUALIFY CONSTANT
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
# @ssl_context = OpenSSL::SSL::SSLContext.new # 3.1 - MOVED TO TOP OF METHOD
|
63
|
+
@ssl_context.set_params(ssl_parameters)
|
64
|
+
@ssl_context.session_cache_mode =
|
65
|
+
OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT |
|
66
|
+
OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
|
67
|
+
@ssl_context.session_new_cb = proc {|sock, sess| @ssl_session = sess }
|
68
|
+
D "starting SSL for #{conn_addr}:#{conn_port}..."
|
69
|
+
s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
|
70
|
+
s.sync_close = true
|
71
|
+
# Server Name Indication (SNI) RFC 3546
|
72
|
+
s.hostname = @address if s.respond_to? :hostname=
|
73
|
+
if @ssl_session and
|
74
|
+
Process.clock_gettime(Process::CLOCK_REALTIME) < @ssl_session.time.to_f + @ssl_session.timeout
|
75
|
+
s.session = @ssl_session
|
76
|
+
end
|
77
|
+
ssl_socket_connect(s, @open_timeout)
|
78
|
+
if (@ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE) && @ssl_context.verify_hostname
|
79
|
+
s.post_connection_check(@address)
|
80
|
+
end
|
81
|
+
D "SSL established, protocol: #{s.ssl_version}, cipher: #{s.cipher[0]}"
|
82
|
+
end
|
83
|
+
@socket = Net::BufferedIO.new(s, read_timeout: @read_timeout, # 3.1 - FULLY QUALIFY CLASS
|
84
|
+
write_timeout: @write_timeout,
|
85
|
+
continue_timeout: @continue_timeout,
|
86
|
+
debug_output: @debug_output)
|
87
|
+
@last_communicated = nil # 3.1 - NEW
|
88
|
+
on_connect
|
89
|
+
rescue => exception
|
90
|
+
if s
|
91
|
+
D "Conn close because of connect error #{exception}"
|
92
|
+
s.close
|
93
|
+
end
|
94
|
+
raise
|
95
|
+
end
|
96
|
+
# rubocop:enable all
|
97
|
+
private :connect
|
98
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# decorator methods for socks proxying
|
4
|
+
module Socksproxyable
|
5
|
+
# class methods
|
6
|
+
module ClassMethods
|
7
|
+
SOCKS4_VERSIONS = %w[4 4a].freeze
|
8
|
+
|
9
|
+
attr_accessor :socks_server, :socks_port, :socks_username, :socks_password
|
10
|
+
|
11
|
+
def socks_version
|
12
|
+
@socks_version ||= '5'
|
13
|
+
end
|
14
|
+
|
15
|
+
def socks_ignores
|
16
|
+
@socks_ignores ||= %w[localhost]
|
17
|
+
end
|
18
|
+
|
19
|
+
def socks_ignores=(*hosts)
|
20
|
+
@socks_ignores = hosts
|
21
|
+
end
|
22
|
+
|
23
|
+
def socks_version_hex
|
24
|
+
SOCKS4_VERSIONS.include?(socks_version) ? "\004" : "\005"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# instance method #socks_authenticate
|
29
|
+
module InstanceMethodsAuthenticate
|
30
|
+
# rubocop:disable Metrics
|
31
|
+
def socks_authenticate(socks_username, socks_password)
|
32
|
+
if socks_username || socks_password
|
33
|
+
Socksify.debug_debug 'Sending username/password authentication'
|
34
|
+
write "\005\001\002"
|
35
|
+
else
|
36
|
+
Socksify.debug_debug 'Sending no authentication'
|
37
|
+
write "\005\001\000"
|
38
|
+
end
|
39
|
+
Socksify.debug_debug 'Waiting for authentication reply'
|
40
|
+
auth_reply = recv(2)
|
41
|
+
raise SOCKSError, "Server doesn't reply authentication" if auth_reply.empty?
|
42
|
+
|
43
|
+
if auth_reply[0..0] != "\004" && auth_reply[0..0] != "\005"
|
44
|
+
raise SOCKSError, "SOCKS version #{auth_reply[0..0]} not supported"
|
45
|
+
end
|
46
|
+
|
47
|
+
if socks_username || socks_password
|
48
|
+
if auth_reply[1..1] != "\002"
|
49
|
+
raise SOCKSError, "SOCKS authentication method #{auth_reply[1..1]} neither requested nor supported"
|
50
|
+
end
|
51
|
+
|
52
|
+
auth = "\001"
|
53
|
+
auth += socks_username.to_s.length.chr
|
54
|
+
auth += socks_username.to_s
|
55
|
+
auth += socks_password.to_s.length.chr
|
56
|
+
auth += socks_password.to_s
|
57
|
+
write auth
|
58
|
+
auth_reply = recv(2)
|
59
|
+
raise SOCKSError, 'SOCKS authentication failed' if auth_reply[1..1] != "\000"
|
60
|
+
elsif auth_reply[1..1] != "\000"
|
61
|
+
raise SOCKSError, "SOCKS authentication method #{auth_reply[1..1]} neither requested nor supported"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
# rubocop:enable Metrics
|
65
|
+
end
|
66
|
+
|
67
|
+
# instance methods #socks_connect & #socks_receive_reply
|
68
|
+
module InstanceMethodsConnect
|
69
|
+
# rubocop:disable Metrics
|
70
|
+
def socks_connect(host, port)
|
71
|
+
port = Socket.getservbyname(port) if port.is_a?(String)
|
72
|
+
req = String.new
|
73
|
+
Socksify.debug_debug 'Sending destination address'
|
74
|
+
req << TCPSocket.socks_version_hex
|
75
|
+
Socksify.debug_debug TCPSocket.socks_version_hex.unpack 'H*'
|
76
|
+
req << "\001"
|
77
|
+
req << "\000" if self.class.socks_version == '5'
|
78
|
+
req << [port].pack('n') if self.class.socks_version =~ /^4/
|
79
|
+
host = Resolv::DNS.new.getaddress(host).to_s if self.class.socks_version == '4'
|
80
|
+
Socksify.debug_debug host
|
81
|
+
if host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ # to IPv4 address
|
82
|
+
req << "\001" if self.class.socks_version == '5'
|
83
|
+
ip = (1..4).map { |i| Regexp.last_match(i).to_i }.pack('CCCC')
|
84
|
+
req << ip
|
85
|
+
elsif host =~ /^[:0-9a-f]+$/ # to IPv6 address
|
86
|
+
raise 'TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor'
|
87
|
+
# req << "\004" # UNREACHABLE
|
88
|
+
elsif self.class.socks_version == '5' # to hostname
|
89
|
+
# req << "\003" + [host.size].pack('C') + host
|
90
|
+
req << "\003#{[host.size].pack('C')}#{host}"
|
91
|
+
else
|
92
|
+
req << "\000\000\000\001" << "\007\000"
|
93
|
+
Socksify.debug_notice host
|
94
|
+
req << host << "\000"
|
95
|
+
end
|
96
|
+
req << [port].pack('n') if self.class.socks_version == '5'
|
97
|
+
write req
|
98
|
+
socks_receive_reply
|
99
|
+
Socksify.debug_notice "Connected to #{host}:#{port} over SOCKS"
|
100
|
+
end
|
101
|
+
# rubocop:enable Metrics
|
102
|
+
|
103
|
+
# returns [bind_addr: String, bind_port: Fixnum]
|
104
|
+
# rubocop:disable Metrics
|
105
|
+
def socks_receive_reply
|
106
|
+
Socksify.debug_debug 'Waiting for SOCKS reply'
|
107
|
+
if self.class.socks_version == '5'
|
108
|
+
connect_reply = recv(4)
|
109
|
+
raise SOCKSError, "Server doesn't reply" if connect_reply.empty?
|
110
|
+
|
111
|
+
Socksify.debug_debug connect_reply.unpack 'H*'
|
112
|
+
raise SOCKSError, "SOCKS version #{connect_reply[0..0]} is not 5" if connect_reply[0..0] != "\005"
|
113
|
+
raise SOCKSError.for_response_code(connect_reply.bytes.to_a[1]) if connect_reply[1..1] != "\000"
|
114
|
+
|
115
|
+
Socksify.debug_debug 'Waiting for bind_addr'
|
116
|
+
bind_addr_len = case connect_reply[3..3]
|
117
|
+
when "\001"
|
118
|
+
4
|
119
|
+
when "\003"
|
120
|
+
recv(1).bytes.first
|
121
|
+
when "\004"
|
122
|
+
16
|
123
|
+
else
|
124
|
+
raise SOCKSError.for_response_code(connect_reply.bytes.to_a[3])
|
125
|
+
end
|
126
|
+
bind_addr_s = recv(bind_addr_len)
|
127
|
+
bind_addr = case connect_reply[3..3]
|
128
|
+
when "\001"
|
129
|
+
bind_addr_s.bytes.to_a.join('.')
|
130
|
+
when "\003"
|
131
|
+
bind_addr_s
|
132
|
+
when "\004" # Untested!
|
133
|
+
i = 0
|
134
|
+
ip6 = ''
|
135
|
+
bind_addr_s.each_byte do |b|
|
136
|
+
ip6 += ':' if i > 0 && i.even?
|
137
|
+
i += 1
|
138
|
+
ip6 += b.to_s(16).rjust(2, '0')
|
139
|
+
end
|
140
|
+
end
|
141
|
+
bind_port = recv(bind_addr_len + 2)
|
142
|
+
[bind_addr, bind_port.unpack('n')]
|
143
|
+
else
|
144
|
+
connect_reply = recv(8)
|
145
|
+
unless connect_reply[0] == "\000" && connect_reply[1] == "\x5A"
|
146
|
+
Socksify.debug_debug connect_reply.unpack 'H'
|
147
|
+
raise SOCKSError, 'Failed while connecting througth socks'
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
# rubocop:enable Metrics
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require_relative 'socksproxyable'
|
2
|
+
|
3
|
+
# monkey patch
|
4
|
+
class TCPSocket
|
5
|
+
extend Socksproxyable::ClassMethods
|
6
|
+
include Socksproxyable::InstanceMethodsAuthenticate
|
7
|
+
include Socksproxyable::InstanceMethodsConnect
|
8
|
+
|
9
|
+
alias initialize_tcp initialize
|
10
|
+
|
11
|
+
attr_reader :socks_peer
|
12
|
+
|
13
|
+
# See http://tools.ietf.org/html/rfc1928
|
14
|
+
# rubocop:disable Metrics/ParameterLists
|
15
|
+
def initialize(host = nil, port = nil, local_host = nil, local_port = nil, **kwargs)
|
16
|
+
@socks_peer = host if host.is_a?(SOCKSConnectionPeerAddress)
|
17
|
+
host = socks_peer.peer_host if socks_peer
|
18
|
+
|
19
|
+
if socks_server && socks_port && !socks_ignores.include?(host)
|
20
|
+
make_socks_connection(host, port, **kwargs)
|
21
|
+
else
|
22
|
+
make_direct_connection(host, port, local_host, local_port, **kwargs)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
# rubocop:enable Metrics/ParameterLists
|
26
|
+
|
27
|
+
# string representation of the peer host address
|
28
|
+
class SOCKSConnectionPeerAddress < String
|
29
|
+
attr_reader :socks_server, :socks_port, :socks_username, :socks_password
|
30
|
+
|
31
|
+
def initialize(socks_server, socks_port, peer_host, socks_username = nil, socks_password = nil)
|
32
|
+
@socks_server = socks_server
|
33
|
+
@socks_port = socks_port
|
34
|
+
@socks_username = socks_username
|
35
|
+
@socks_password = socks_password
|
36
|
+
super(peer_host)
|
37
|
+
end
|
38
|
+
|
39
|
+
def inspect
|
40
|
+
"#{self} (via #{@socks_server}:#{@socks_port})"
|
41
|
+
end
|
42
|
+
|
43
|
+
def peer_host
|
44
|
+
to_s
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def socks_server
|
51
|
+
@socks_server ||= socks_peer ? socks_peer.socks_server : self.class.socks_server
|
52
|
+
end
|
53
|
+
|
54
|
+
def socks_port
|
55
|
+
@socks_port ||= socks_peer ? socks_peer.socks_port : self.class.socks_port
|
56
|
+
end
|
57
|
+
|
58
|
+
def socks_username
|
59
|
+
@socks_username ||= socks_peer ? socks_peer.socks_username : self.class.socks_username
|
60
|
+
end
|
61
|
+
|
62
|
+
def socks_password
|
63
|
+
@socks_password ||= socks_peer ? socks_peer.socks_password : self.class.socks_password
|
64
|
+
end
|
65
|
+
|
66
|
+
def socks_ignores
|
67
|
+
@socks_ignores ||= socks_peer ? [] : self.class.socks_ignores
|
68
|
+
end
|
69
|
+
|
70
|
+
def make_socks_connection(host, port, **kwargs)
|
71
|
+
Socksify.debug_notice "Connecting to SOCKS server #{socks_server}:#{socks_port}"
|
72
|
+
initialize_tcp socks_server, socks_port, **kwargs
|
73
|
+
socks_authenticate(socks_username, socks_password) unless @socks_version =~ /^4/
|
74
|
+
socks_connect(host, port) if host
|
75
|
+
end
|
76
|
+
|
77
|
+
def make_direct_connection(host, port, local_host, local_port, **kwargs)
|
78
|
+
Socksify.debug_notice "Connecting directly to #{host}:#{port}"
|
79
|
+
initialize_tcp host, port, local_host, local_port, **kwargs
|
80
|
+
Socksify.debug_debug "Connected to #{host}:#{port}"
|
81
|
+
end
|
82
|
+
end
|
data/lib/socksify.rb
CHANGED
@@ -1,360 +1,134 @@
|
|
1
|
-
#encoding: us-ascii
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
=end
|
1
|
+
# encoding: us-ascii
|
2
|
+
|
3
|
+
# Copyright (C) 2007 Stephan Maka <stephan@spaceboyz.net>
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
17
|
|
19
18
|
require 'socket'
|
20
19
|
require 'resolv'
|
21
20
|
require 'socksify/debug'
|
21
|
+
require 'socksify/tcpsocket'
|
22
22
|
|
23
|
+
# error class
|
23
24
|
class SOCKSError < RuntimeError
|
24
25
|
def initialize(msg)
|
25
|
-
Socksify
|
26
|
+
Socksify.debug_error("#{self.class}: #{msg}")
|
26
27
|
super
|
27
28
|
end
|
28
29
|
|
30
|
+
# rubocop:disable Style/Documentation
|
29
31
|
class ServerFailure < SOCKSError
|
30
32
|
def initialize
|
31
|
-
super(
|
33
|
+
super('general SOCKS server failure')
|
32
34
|
end
|
33
35
|
end
|
36
|
+
|
34
37
|
class NotAllowed < SOCKSError
|
35
38
|
def initialize
|
36
|
-
super(
|
39
|
+
super('connection not allowed by ruleset')
|
37
40
|
end
|
38
41
|
end
|
42
|
+
|
39
43
|
class NetworkUnreachable < SOCKSError
|
40
44
|
def initialize
|
41
|
-
super(
|
45
|
+
super('Network unreachable')
|
42
46
|
end
|
43
47
|
end
|
48
|
+
|
44
49
|
class HostUnreachable < SOCKSError
|
45
50
|
def initialize
|
46
|
-
super(
|
51
|
+
super('Host unreachable')
|
47
52
|
end
|
48
53
|
end
|
54
|
+
|
49
55
|
class ConnectionRefused < SOCKSError
|
50
56
|
def initialize
|
51
|
-
super(
|
57
|
+
super('Connection refused')
|
52
58
|
end
|
53
59
|
end
|
60
|
+
|
54
61
|
class TTLExpired < SOCKSError
|
55
62
|
def initialize
|
56
|
-
super(
|
63
|
+
super('TTL expired')
|
57
64
|
end
|
58
65
|
end
|
66
|
+
|
59
67
|
class CommandNotSupported < SOCKSError
|
60
68
|
def initialize
|
61
|
-
super(
|
69
|
+
super('Command not supported')
|
62
70
|
end
|
63
71
|
end
|
72
|
+
|
64
73
|
class AddressTypeNotSupported < SOCKSError
|
65
74
|
def initialize
|
66
|
-
super(
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def self.for_response_code(code)
|
71
|
-
case code
|
72
|
-
when 1
|
73
|
-
ServerFailure
|
74
|
-
when 2
|
75
|
-
NotAllowed
|
76
|
-
when 3
|
77
|
-
NetworkUnreachable
|
78
|
-
when 4
|
79
|
-
HostUnreachable
|
80
|
-
when 5
|
81
|
-
ConnectionRefused
|
82
|
-
when 6
|
83
|
-
TTLExpired
|
84
|
-
when 7
|
85
|
-
CommandNotSupported
|
86
|
-
when 8
|
87
|
-
AddressTypeNotSupported
|
88
|
-
else
|
89
|
-
self
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
class TCPSocket
|
95
|
-
@@socks_version ||= "5"
|
96
|
-
|
97
|
-
def self.socks_version
|
98
|
-
(@@socks_version == "4a" or @@socks_version == "4") ? "\004" : "\005"
|
99
|
-
end
|
100
|
-
def self.socks_version=(version)
|
101
|
-
@@socks_version = version.to_s
|
102
|
-
end
|
103
|
-
def self.socks_server
|
104
|
-
@@socks_server ||= nil
|
105
|
-
end
|
106
|
-
def self.socks_server=(host)
|
107
|
-
@@socks_server = host
|
108
|
-
end
|
109
|
-
def self.socks_port
|
110
|
-
@@socks_port ||= nil
|
111
|
-
end
|
112
|
-
def self.socks_port=(port)
|
113
|
-
@@socks_port = port
|
114
|
-
end
|
115
|
-
def self.socks_username
|
116
|
-
@@socks_username ||= nil
|
117
|
-
end
|
118
|
-
def self.socks_username=(username)
|
119
|
-
@@socks_username = username
|
120
|
-
end
|
121
|
-
def self.socks_password
|
122
|
-
@@socks_password ||= nil
|
123
|
-
end
|
124
|
-
def self.socks_password=(password)
|
125
|
-
@@socks_password = password
|
126
|
-
end
|
127
|
-
def self.socks_ignores
|
128
|
-
@@socks_ignores ||= %w(localhost)
|
129
|
-
end
|
130
|
-
def self.socks_ignores=(ignores)
|
131
|
-
@@socks_ignores = ignores
|
132
|
-
end
|
133
|
-
|
134
|
-
class SOCKSConnectionPeerAddress < String
|
135
|
-
attr_reader :socks_server, :socks_port
|
136
|
-
|
137
|
-
def initialize(socks_server, socks_port, peer_host)
|
138
|
-
@socks_server, @socks_port = socks_server, socks_port
|
139
|
-
super peer_host
|
75
|
+
super('Address type not supported')
|
140
76
|
end
|
141
|
-
|
142
|
-
def inspect
|
143
|
-
"#{to_s} (via #{@socks_server}:#{@socks_port})"
|
144
|
-
end
|
145
|
-
|
146
|
-
def peer_host
|
147
|
-
to_s
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
alias :initialize_tcp :initialize
|
152
|
-
|
153
|
-
# See http://tools.ietf.org/html/rfc1928
|
154
|
-
def initialize(host=nil, port=0, local_host=nil, local_port=nil)
|
155
|
-
if host.is_a?(SOCKSConnectionPeerAddress)
|
156
|
-
socks_peer = host
|
157
|
-
socks_server = socks_peer.socks_server
|
158
|
-
socks_port = socks_peer.socks_port
|
159
|
-
socks_ignores = []
|
160
|
-
host = socks_peer.peer_host
|
161
|
-
else
|
162
|
-
socks_server = self.class.socks_server
|
163
|
-
socks_port = self.class.socks_port
|
164
|
-
socks_ignores = self.class.socks_ignores
|
165
|
-
end
|
166
|
-
|
167
|
-
if socks_server and socks_port and not socks_ignores.include?(host)
|
168
|
-
Socksify::debug_notice "Connecting to SOCKS server #{socks_server}:#{socks_port}"
|
169
|
-
initialize_tcp socks_server, socks_port
|
170
|
-
|
171
|
-
socks_authenticate unless @@socks_version =~ /^4/
|
172
|
-
|
173
|
-
if host
|
174
|
-
socks_connect(host, port)
|
175
|
-
end
|
176
|
-
else
|
177
|
-
Socksify::debug_notice "Connecting directly to #{host}:#{port}"
|
178
|
-
initialize_tcp host, port, local_host, local_port
|
179
|
-
Socksify::debug_debug "Connected to #{host}:#{port}"
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
# Authentication
|
184
|
-
def socks_authenticate
|
185
|
-
if self.class.socks_username || self.class.socks_password
|
186
|
-
Socksify::debug_debug "Sending username/password authentication"
|
187
|
-
write "\005\001\002"
|
188
|
-
else
|
189
|
-
Socksify::debug_debug "Sending no authentication"
|
190
|
-
write "\005\001\000"
|
191
|
-
end
|
192
|
-
Socksify::debug_debug "Waiting for authentication reply"
|
193
|
-
auth_reply = recv(2)
|
194
|
-
if auth_reply.empty?
|
195
|
-
raise SOCKSError.new("Server doesn't reply authentication")
|
196
|
-
end
|
197
|
-
if auth_reply[0..0] != "\004" and auth_reply[0..0] != "\005"
|
198
|
-
raise SOCKSError.new("SOCKS version #{auth_reply[0..0]} not supported")
|
199
|
-
end
|
200
|
-
if self.class.socks_username || self.class.socks_password
|
201
|
-
if auth_reply[1..1] != "\002"
|
202
|
-
raise SOCKSError.new("SOCKS authentication method #{auth_reply[1..1]} neither requested nor supported")
|
203
|
-
end
|
204
|
-
auth = "\001"
|
205
|
-
auth += self.class.socks_username.to_s.length.chr
|
206
|
-
auth += self.class.socks_username.to_s
|
207
|
-
auth += self.class.socks_password.to_s.length.chr
|
208
|
-
auth += self.class.socks_password.to_s
|
209
|
-
write auth
|
210
|
-
auth_reply = recv(2)
|
211
|
-
if auth_reply[1..1] != "\000"
|
212
|
-
raise SOCKSError.new("SOCKS authentication failed")
|
213
|
-
end
|
214
|
-
else
|
215
|
-
if auth_reply[1..1] != "\000"
|
216
|
-
raise SOCKSError.new("SOCKS authentication method #{auth_reply[1..1]} neither requested nor supported")
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
# Connect
|
222
|
-
def socks_connect(host, port)
|
223
|
-
port = Socket.getservbyname(port) if port.is_a?(String)
|
224
|
-
Socksify::debug_debug "Sending destination address"
|
225
|
-
write TCPSocket.socks_version
|
226
|
-
Socksify::debug_debug TCPSocket.socks_version.unpack "H*"
|
227
|
-
write "\001"
|
228
|
-
write "\000" if @@socks_version == "5"
|
229
|
-
write [port].pack('n') if @@socks_version =~ /^4/
|
230
|
-
|
231
|
-
if @@socks_version == "4"
|
232
|
-
host = Resolv::DNS.new.getaddress(host).to_s
|
233
|
-
end
|
234
|
-
Socksify::debug_debug host
|
235
|
-
if host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ # to IPv4 address
|
236
|
-
write "\001" if @@socks_version == "5"
|
237
|
-
_ip = [$1.to_i,
|
238
|
-
$2.to_i,
|
239
|
-
$3.to_i,
|
240
|
-
$4.to_i
|
241
|
-
].pack('CCCC')
|
242
|
-
write _ip
|
243
|
-
elsif host =~ /^[:0-9a-f]+$/ # to IPv6 address
|
244
|
-
raise "TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor"
|
245
|
-
write "\004"
|
246
|
-
else # to hostname
|
247
|
-
if @@socks_version == "5"
|
248
|
-
write "\003" + [host.size].pack('C') + host
|
249
|
-
else
|
250
|
-
write "\000\000\000\001"
|
251
|
-
write "\007\000"
|
252
|
-
Socksify::debug_notice host
|
253
|
-
write host
|
254
|
-
write "\000"
|
255
|
-
end
|
256
|
-
end
|
257
|
-
write [port].pack('n') if @@socks_version == "5"
|
258
|
-
|
259
|
-
socks_receive_reply
|
260
|
-
Socksify::debug_notice "Connected to #{host}:#{port} over SOCKS"
|
261
77
|
end
|
78
|
+
# rubocop:enable Style/Documentation
|
262
79
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
Socksify::debug_debug connect_reply.unpack "H*"
|
272
|
-
if connect_reply[0..0] != "\005"
|
273
|
-
raise SOCKSError.new("SOCKS version #{connect_reply[0..0]} is not 5")
|
274
|
-
end
|
275
|
-
if connect_reply[1..1] != "\000"
|
276
|
-
raise SOCKSError.for_response_code(connect_reply.bytes.to_a[1])
|
277
|
-
end
|
278
|
-
Socksify::debug_debug "Waiting for bind_addr"
|
279
|
-
bind_addr_len = case connect_reply[3..3]
|
280
|
-
when "\001"
|
281
|
-
4
|
282
|
-
when "\003"
|
283
|
-
recv(1).bytes.first
|
284
|
-
when "\004"
|
285
|
-
16
|
286
|
-
else
|
287
|
-
raise SOCKSError.for_response_code(connect_reply.bytes.to_a[3])
|
288
|
-
end
|
289
|
-
bind_addr_s = recv(bind_addr_len)
|
290
|
-
bind_addr = case connect_reply[3..3]
|
291
|
-
when "\001"
|
292
|
-
bind_addr_s.bytes.to_a.join('.')
|
293
|
-
when "\003"
|
294
|
-
bind_addr_s
|
295
|
-
when "\004" # Untested!
|
296
|
-
i = 0
|
297
|
-
ip6 = ""
|
298
|
-
bind_addr_s.each_byte do |b|
|
299
|
-
if i > 0 and i % 2 == 0
|
300
|
-
ip6 += ":"
|
301
|
-
end
|
302
|
-
i += 1
|
80
|
+
RESPONSE_CODE_CLASSES = { 1 => ServerFailure,
|
81
|
+
2 => NotAllowed,
|
82
|
+
3 => NetworkUnreachable,
|
83
|
+
4 => HostUnreachable,
|
84
|
+
5 => ConnectionRefused,
|
85
|
+
6 => TTLExpired,
|
86
|
+
7 => CommandNotSupported,
|
87
|
+
8 => AddressTypeNotSupported }.freeze
|
303
88
|
|
304
|
-
|
305
|
-
|
306
|
-
end
|
307
|
-
bind_port = recv(bind_addr_len + 2)
|
308
|
-
[bind_addr, bind_port.unpack('n')]
|
309
|
-
else
|
310
|
-
connect_reply = recv(8)
|
311
|
-
unless connect_reply[0] == "\000" and connect_reply[1] == "\x5A"
|
312
|
-
Socksify::debug_debug connect_reply.unpack 'H'
|
313
|
-
raise SOCKSError.new("Failed while connecting througth socks")
|
314
|
-
end
|
315
|
-
end
|
89
|
+
def self.for_response_code(code)
|
90
|
+
(resp = RESPONSE_CODE_CLASSES[code]) ? resp : self
|
316
91
|
end
|
317
92
|
end
|
318
93
|
|
94
|
+
# namespace
|
319
95
|
module Socksify
|
320
96
|
def self.resolve(host)
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
$4.to_i
|
331
|
-
].pack('CCCC')
|
332
|
-
elsif host =~ /^[:0-9a-f]+$/ # to IPv6 address
|
333
|
-
raise "TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor"
|
334
|
-
s.write "\004"
|
335
|
-
else # to hostname
|
336
|
-
s.write "\xF0\000\003" + [host.size].pack('C') + host
|
337
|
-
end
|
338
|
-
s.write [0].pack('n') # Port
|
339
|
-
|
340
|
-
addr, _port = s.socks_receive_reply
|
341
|
-
Socksify::debug_notice "Resolved #{host} as #{addr} over SOCKS"
|
342
|
-
addr
|
343
|
-
ensure
|
344
|
-
s.close
|
345
|
-
end
|
97
|
+
socket = TCPSocket.new # no args?
|
98
|
+
Socksify.debug_debug "Sending hostname to resolve: #{host}"
|
99
|
+
req = request(host)
|
100
|
+
socket.write req
|
101
|
+
addr, _port = socket.socks_receive_reply
|
102
|
+
Socksify.debug_notice "Resolved #{host} as #{addr} over SOCKS"
|
103
|
+
addr
|
104
|
+
ensure
|
105
|
+
socket.close
|
346
106
|
end
|
347
107
|
|
348
108
|
def self.proxy(server, port)
|
349
|
-
default_server = TCPSocket
|
350
|
-
default_port = TCPSocket
|
109
|
+
default_server = TCPSocket.socks_server
|
110
|
+
default_port = TCPSocket.socks_port
|
351
111
|
begin
|
352
|
-
TCPSocket
|
353
|
-
TCPSocket
|
112
|
+
TCPSocket.socks_server = server
|
113
|
+
TCPSocket.socks_port = port
|
354
114
|
yield
|
355
|
-
ensure
|
356
|
-
TCPSocket
|
357
|
-
TCPSocket
|
115
|
+
ensure # failback
|
116
|
+
TCPSocket.socks_server = default_server
|
117
|
+
TCPSocket.socks_port = default_port
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.request(host)
|
122
|
+
req = String.new << "\005"
|
123
|
+
case host
|
124
|
+
when /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ # to IPv4 address
|
125
|
+
req << "\xF1\000\001#{(1..4).map { |i| Regexp.last_match(i).to_i }.pack('CCCC')}"
|
126
|
+
when /^[:0-9a-f]+$/ # to IPv6 address
|
127
|
+
raise 'TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor)'
|
128
|
+
# # req << "\004" # UNREACHABLE
|
129
|
+
else # to hostname
|
130
|
+
req << "\xF0\000\003#{[host.size].pack('C')}#{host}"
|
358
131
|
end
|
132
|
+
req << [0].pack('n') # Port
|
359
133
|
end
|
360
134
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: socksify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephan Maka
|
@@ -10,19 +10,17 @@ authors:
|
|
10
10
|
- Musy Bite
|
11
11
|
- Yuichi Tateno
|
12
12
|
- David Dollar
|
13
|
-
autorequire:
|
14
13
|
bindir: bin
|
15
14
|
cert_chain: []
|
16
|
-
date:
|
15
|
+
date: 2025-07-17 00:00:00.000000000 Z
|
17
16
|
dependencies: []
|
18
|
-
description:
|
19
17
|
email: stephan@spaceboyz.net
|
20
18
|
executables:
|
21
19
|
- socksify_ruby
|
22
20
|
extensions: []
|
23
21
|
extra_rdoc_files:
|
24
|
-
- doc/index.html
|
25
22
|
- doc/index.css
|
23
|
+
- doc/index.html
|
26
24
|
- COPYING
|
27
25
|
files:
|
28
26
|
- COPYING
|
@@ -32,10 +30,16 @@ files:
|
|
32
30
|
- lib/socksify.rb
|
33
31
|
- lib/socksify/debug.rb
|
34
32
|
- lib/socksify/http.rb
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
33
|
+
- lib/socksify/ruby3net_http_connectable.rb
|
34
|
+
- lib/socksify/socksproxyable.rb
|
35
|
+
- lib/socksify/tcpsocket.rb
|
36
|
+
- lib/socksify/version.rb
|
37
|
+
homepage: https://github.com/astro/socksify-ruby
|
38
|
+
licenses:
|
39
|
+
- Ruby
|
40
|
+
- GPL-3.0-only
|
41
|
+
metadata:
|
42
|
+
funding_uri: https://github.com/sponsors/astro
|
39
43
|
rdoc_options: []
|
40
44
|
require_paths:
|
41
45
|
- lib
|
@@ -43,16 +47,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
43
47
|
requirements:
|
44
48
|
- - ">="
|
45
49
|
- !ruby/object:Gem::Version
|
46
|
-
version: '0'
|
50
|
+
version: '2.0'
|
47
51
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
52
|
requirements:
|
49
53
|
- - ">="
|
50
54
|
- !ruby/object:Gem::Version
|
51
55
|
version: '0'
|
52
56
|
requirements: []
|
53
|
-
|
54
|
-
rubygems_version: 2.4.5
|
55
|
-
signing_key:
|
57
|
+
rubygems_version: 3.6.6
|
56
58
|
specification_version: 4
|
57
59
|
summary: Redirect all TCPSockets through a SOCKS5 proxy
|
58
60
|
test_files: []
|