em-socksify 0.1.0 → 0.2.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.
- data/README.md +21 -18
- data/Rakefile +5 -0
- data/em-socksify.gemspec +1 -1
- data/lib/em-socksify.rb +3 -1
- data/lib/em-socksify/errors.rb +38 -0
- data/lib/em-socksify/socks5.rb +115 -0
- data/lib/em-socksify/socksify.rb +30 -109
- data/lib/em-socksify/version.rb +5 -3
- data/spec/socksify_spec.rb +2 -2
- metadata +47 -61
data/README.md
CHANGED
@@ -4,23 +4,25 @@ Dealing with SOCKS proxies is pain. EM-Socksify provides a simple shim to setup
|
|
4
4
|
|
5
5
|
## Example: Routing HTTP request via SOCKS5 proxy
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def connection_completed
|
11
|
-
socksify('google.ca', 80) do
|
12
|
-
send_data "GET / HTTP/1.1\r\nConnection:close\r\nHost: google.ca\r\n\r\n"
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def receive_data(data)
|
17
|
-
p data
|
18
|
-
end
|
19
|
-
end
|
7
|
+
```ruby
|
8
|
+
class Handler < EM::Connection
|
9
|
+
include EM::Socksify
|
20
10
|
|
21
|
-
|
22
|
-
|
11
|
+
def connection_completed
|
12
|
+
socksify('google.ca', 80) do
|
13
|
+
send_data "GET / HTTP/1.1\r\nConnection:close\r\nHost: google.ca\r\n\r\n"
|
23
14
|
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def receive_data(data)
|
18
|
+
p data
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
EM.run do
|
23
|
+
EventMachine.connect SOCKS_HOST, SOCKS_PORT, Handler
|
24
|
+
end
|
25
|
+
```
|
24
26
|
|
25
27
|
What's happening here? First, we open a raw TCP connection to the SOCKS proxy (after all, all data will flow through it). Then, we provide a Handler connection class, which includes "EM::Socksify". Once the TCP connection is established, EventMachine calls the **connection_completed** method in our handler. Here, we call socksify with the actual destination host & port (address that we actually want to get to), and the module does the rest.
|
26
28
|
|
@@ -28,8 +30,9 @@ After you call socksify, the module temporarily intercepts your receive_data cal
|
|
28
30
|
|
29
31
|
For SOCKS proxies which require authentication, use:
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
+
```ruby
|
34
|
+
socksify(destination_host, destination_port, username, password, version)
|
35
|
+
```
|
33
36
|
|
34
37
|
## Wishlist
|
35
38
|
|
@@ -44,4 +47,4 @@ For SOCKS proxies which require authentication, use:
|
|
44
47
|
# License
|
45
48
|
|
46
49
|
(The MIT License)
|
47
|
-
Copyright © 2011 Ilya Grigorik
|
50
|
+
Copyright © 2011 Ilya Grigorik
|
data/Rakefile
CHANGED
data/em-socksify.gemspec
CHANGED
data/lib/em-socksify.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module Socksify
|
3
|
+
|
4
|
+
class SOCKSError < Exception
|
5
|
+
def self.define (message)
|
6
|
+
Class.new(self) do
|
7
|
+
def initialize
|
8
|
+
super(message)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
ServerFailure = define('general SOCKS server failure')
|
14
|
+
NotAllowed = define('connection not allowed by ruleset')
|
15
|
+
NetworkUnreachable = define('Network unreachable')
|
16
|
+
HostUnreachable = define('Host unreachable')
|
17
|
+
ConnectionRefused = define('Connection refused')
|
18
|
+
TTLExpired = define('TTL expired')
|
19
|
+
CommandNotSupported = define('Command not supported')
|
20
|
+
AddressTypeNotSupported = define('Address type not supported')
|
21
|
+
|
22
|
+
def self.for_response_code(code)
|
23
|
+
case code.is_a?(String) ? code.ord : code
|
24
|
+
when 1 then ServerFailure
|
25
|
+
when 2 then NotAllowed
|
26
|
+
when 3 then NetworkUnreachable
|
27
|
+
when 4 then HostUnreachable
|
28
|
+
when 5 then ConnectionRefused
|
29
|
+
when 6 then TTLExpired
|
30
|
+
when 7 then CommandNotSupported
|
31
|
+
when 8 then AddressTypeNotSupported
|
32
|
+
else self
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module Socksify
|
3
|
+
|
4
|
+
module SOCKS5
|
5
|
+
def socks_send_handshake
|
6
|
+
# Method Negotiation as described on
|
7
|
+
# http://www.faqs.org/rfcs/rfc1928.html Section 3
|
8
|
+
@socks_state = :method_negotiation
|
9
|
+
|
10
|
+
socks_methods.tap do |methods|
|
11
|
+
send_data [5, methods.size].pack('CC') + methods.pack('C*')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def socks_send_connect_request
|
16
|
+
@socks_state = :connecting
|
17
|
+
|
18
|
+
send_data [5, 1, 0].pack('CCC')
|
19
|
+
|
20
|
+
if matches = @socks_target_host.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/)
|
21
|
+
send_data "\xF1\x00\x01" + matches.to_a[1 .. -1].map { |s| s.to_i }.pack('CCCC')
|
22
|
+
|
23
|
+
elsif @socks_target_host =~ /^[:0-9a-f]+$/
|
24
|
+
raise SOCKSError, 'TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor)'
|
25
|
+
|
26
|
+
else
|
27
|
+
send_data [3, @socks_target_host.size, @socks_target_host].pack('CCA*')
|
28
|
+
end
|
29
|
+
|
30
|
+
send_data [@socks_target_port].pack('n')
|
31
|
+
end
|
32
|
+
|
33
|
+
def socks_send_authentication
|
34
|
+
@socks_state = :authenticating
|
35
|
+
|
36
|
+
send_data [5,
|
37
|
+
@socks_username.length, @socks_username,
|
38
|
+
@socks_password.length, @socks_password
|
39
|
+
].pack('CCA*CA*')
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# parses socks 5 server responses as specified
|
45
|
+
# on http://www.faqs.org/rfcs/rfc1928.html
|
46
|
+
def socks_parse_response
|
47
|
+
case @socks_state
|
48
|
+
when :method_negotiation
|
49
|
+
return unless @socks_data.size >= 2
|
50
|
+
|
51
|
+
_, method = @socks_data.slice!(0, 2).unpack('CC')
|
52
|
+
|
53
|
+
if socks_methods.include?(method)
|
54
|
+
case method
|
55
|
+
when 0 then socks_send_connect_request
|
56
|
+
when 2 then socks_send_authentication
|
57
|
+
end
|
58
|
+
else
|
59
|
+
raise SOCKSError, 'proxy did not accept method'
|
60
|
+
end
|
61
|
+
|
62
|
+
when :authenticating
|
63
|
+
return unless @socks_data.size >= 2
|
64
|
+
|
65
|
+
socks_version, status_code = @socks_data.slice!(0, 2).unpack('CC')
|
66
|
+
|
67
|
+
raise SOCKSError, "SOCKS version 5 not supported" unless socks_version == 5
|
68
|
+
raise SOCKSError, 'access denied by proxy' unless status_code == 0
|
69
|
+
|
70
|
+
send_socks_connect_request
|
71
|
+
|
72
|
+
when :connecting
|
73
|
+
return unless @socks_data.size >= 2
|
74
|
+
|
75
|
+
socks_version, status_code = @socks_data.slice(0, 2).unpack('CC')
|
76
|
+
|
77
|
+
raise SOCKSError, "SOCKS version #{socks_version} is not 5" unless socks_version == 5
|
78
|
+
raise SOCKSError.for_response_code(status_code) unless status_code == 0
|
79
|
+
|
80
|
+
min_size = @socks_data[3].ord == 3 ? 5 : 4
|
81
|
+
|
82
|
+
return unless @socks_data.size >= min_size
|
83
|
+
|
84
|
+
size = case @socks_data[3].ord
|
85
|
+
when 1 then 4
|
86
|
+
when 3 then @socks_data[4].ord
|
87
|
+
when 4 then 16
|
88
|
+
else raise SOCKSError.for_response_code(@socks_data[3])
|
89
|
+
end
|
90
|
+
|
91
|
+
return unless @socks_data.size >= (min_size + size)
|
92
|
+
|
93
|
+
bind_addr = @socks_data.slice(min_size, size)
|
94
|
+
|
95
|
+
ip = case @socks_data[3].ord
|
96
|
+
when 1 then bind_addr.bytes.to_a.join('.')
|
97
|
+
when 3 then bind_addr
|
98
|
+
when 4 then # TODO: ???
|
99
|
+
end
|
100
|
+
|
101
|
+
socks_unhook(ip)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def socks_methods
|
106
|
+
methods = []
|
107
|
+
methods << 2 if !@socks_username.nil? # 2 => Username/Password Authentication
|
108
|
+
methods << 0 # 0 => No Authentication Required
|
109
|
+
|
110
|
+
methods
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
data/lib/em-socksify/socksify.rb
CHANGED
@@ -1,122 +1,43 @@
|
|
1
1
|
module EventMachine
|
2
|
+
|
2
3
|
module Socksify
|
4
|
+
def socksify(host, port, username = nil, password = nil, version = 5, &blk)
|
5
|
+
@socks_target_host = host
|
6
|
+
@socks_target_port = port
|
7
|
+
@socks_username = username
|
8
|
+
@socks_password = password
|
9
|
+
@socks_version = version
|
10
|
+
@socks_callback = blk
|
11
|
+
@socks_data = ''
|
12
|
+
|
13
|
+
socks_hook
|
14
|
+
socks_send_handshake
|
15
|
+
end
|
3
16
|
|
4
|
-
def
|
5
|
-
@
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
17
|
+
def socks_hook
|
18
|
+
if @socks_version == 5
|
19
|
+
extend SOCKS5
|
20
|
+
else
|
21
|
+
raise ArgumentError, 'SOCKS version unsupported'
|
22
|
+
end
|
10
23
|
|
11
24
|
class << self
|
12
|
-
|
25
|
+
alias receive_data socks_receive_data
|
13
26
|
end
|
14
|
-
|
15
|
-
send_socks_handshake
|
16
27
|
end
|
17
28
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
def send_socks_handshake
|
25
|
-
# Method Negotiation as described on
|
26
|
-
# http://www.faqs.org/rfcs/rfc1928.html Section 3
|
27
|
-
@socks_state = :method_negotiation
|
29
|
+
def socks_unhook(ip = nil)
|
30
|
+
class << self
|
31
|
+
remove_method :receive_data
|
32
|
+
end
|
28
33
|
|
29
|
-
|
30
|
-
send_data [5, methods.size].pack('CC') + methods.pack('C*')
|
34
|
+
@socks_callback.call(ip)
|
31
35
|
end
|
32
36
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
ip_address = Socket.gethostbyname(@host).last
|
37
|
-
send_data [5, 1, 0, 1, ip_address, @port].flatten.pack('CCCCA4n')
|
38
|
-
|
39
|
-
rescue
|
40
|
-
fail("could not resolve host")
|
41
|
-
end
|
37
|
+
def socks_receive_data(data)
|
38
|
+
@socks_data << data
|
39
|
+
socks_parse_response
|
42
40
|
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
# parses socks 5 server responses as specified
|
47
|
-
# on http://www.faqs.org/rfcs/rfc1928.html
|
48
|
-
def parse_socks_response
|
49
|
-
if @socks_state == :method_negotiation
|
50
|
-
return if not @data.size >= 2
|
51
|
-
|
52
|
-
_, method = @data.slice!(0,2).unpack('CC')
|
53
|
-
|
54
|
-
if socks_methods.include?(method)
|
55
|
-
if method == 0
|
56
|
-
@socks_state = :connecting
|
57
|
-
send_socks_connect_request
|
58
|
-
|
59
|
-
elsif method == 2
|
60
|
-
@socks_state = :authenticating
|
61
|
-
send_data [5, @username.length, @username, @password.length, @password].pack('CCA*CA*')
|
62
|
-
end
|
63
|
-
|
64
|
-
else
|
65
|
-
fail("proxy did not accept method")
|
66
|
-
end
|
67
|
-
|
68
|
-
elsif @socks_state == :authenticating
|
69
|
-
return if not @data.size >= 2
|
70
|
-
|
71
|
-
_, status_code = @data.slice!(0, 2).unpack('CC')
|
72
|
-
|
73
|
-
if status_code == 0 # success
|
74
|
-
@socks_state = :connecting
|
75
|
-
send_socks_connect_request
|
76
|
-
|
77
|
-
else # error
|
78
|
-
fail "access denied by proxy"
|
79
|
-
end
|
80
|
-
|
81
|
-
elsif @socks_state == :connecting
|
82
|
-
return if not @data.size >= 10
|
83
|
-
|
84
|
-
_, response_code, _, address_type, _, _ = @data.slice(0, 10).unpack('CCCCNn')
|
85
|
-
|
86
|
-
if response_code == 0 # success
|
87
|
-
@socks_state = :connected
|
88
|
-
|
89
|
-
class << self
|
90
|
-
remove_method :receive_data
|
91
|
-
end
|
92
|
-
|
93
|
-
@callback.call
|
94
|
-
|
95
|
-
else # error
|
96
|
-
error_messages = {
|
97
|
-
1 => "general socks server failure",
|
98
|
-
2 => "connection not allowed by ruleset",
|
99
|
-
3 => "network unreachable",
|
100
|
-
4 => "host unreachable",
|
101
|
-
5 => "connection refused",
|
102
|
-
6 => "TTL expired",
|
103
|
-
7 => "command not supported",
|
104
|
-
8 => "address type not supported"
|
105
|
-
}
|
106
|
-
|
107
|
-
error_message = error_messages[response_code] || "unknown error (code: #{response_code})"
|
108
|
-
fail "socks5 connect error: #{error_message}"
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def socks_methods
|
114
|
-
methods = []
|
115
|
-
methods << 2 if !@username.nil? # 2 => Username/Password Authentication
|
116
|
-
methods << 0 # 0 => No Authentication Required
|
117
|
-
|
118
|
-
methods
|
119
|
-
end
|
120
|
-
|
121
41
|
end
|
122
|
-
|
42
|
+
|
43
|
+
end
|
data/lib/em-socksify/version.rb
CHANGED
data/spec/socksify_spec.rb
CHANGED
@@ -8,7 +8,7 @@ describe EventMachine do
|
|
8
8
|
include EM::Socksify
|
9
9
|
|
10
10
|
def connection_completed
|
11
|
-
socksify('google.ca', 80) do
|
11
|
+
socksify('google.ca', 80) do |ip|
|
12
12
|
send_data "GET / HTTP/1.1\r\nConnection:close\r\nHost: google.ca\r\n\r\n"
|
13
13
|
end
|
14
14
|
end
|
@@ -30,4 +30,4 @@ describe EventMachine do
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
end
|
33
|
+
end
|
metadata
CHANGED
@@ -1,56 +1,45 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: em-socksify
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 1
|
8
|
-
- 0
|
9
|
-
version: 0.1.0
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
10
6
|
platform: ruby
|
11
|
-
authors:
|
7
|
+
authors:
|
12
8
|
- Ilya Grigorik
|
13
9
|
autorequire:
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
dependencies:
|
20
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-03-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
21
15
|
name: eventmachine
|
22
|
-
|
23
|
-
|
24
|
-
requirements:
|
25
|
-
- -
|
26
|
-
- !ruby/object:Gem::Version
|
27
|
-
|
28
|
-
- 0
|
29
|
-
version: "0"
|
16
|
+
requirement: &2156395280 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.0.beta.4
|
30
22
|
type: :runtime
|
31
|
-
version_requirements: *id001
|
32
|
-
- !ruby/object:Gem::Dependency
|
33
|
-
name: rspec
|
34
23
|
prerelease: false
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
24
|
+
version_requirements: *2156395280
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &2156394860 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
42
33
|
type: :development
|
43
|
-
|
44
|
-
|
45
|
-
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2156394860
|
36
|
+
description: ! 'EventMachine SOCKSify shim: adds SOCKS support to any protocol'
|
37
|
+
email:
|
46
38
|
- ilya@igvita.com
|
47
39
|
executables: []
|
48
|
-
|
49
40
|
extensions: []
|
50
|
-
|
51
41
|
extra_rdoc_files: []
|
52
|
-
|
53
|
-
files:
|
42
|
+
files:
|
54
43
|
- .gitignore
|
55
44
|
- .rspec
|
56
45
|
- Gemfile
|
@@ -58,40 +47,37 @@ files:
|
|
58
47
|
- Rakefile
|
59
48
|
- em-socksify.gemspec
|
60
49
|
- lib/em-socksify.rb
|
50
|
+
- lib/em-socksify/errors.rb
|
51
|
+
- lib/em-socksify/socks5.rb
|
61
52
|
- lib/em-socksify/socksify.rb
|
62
53
|
- lib/em-socksify/version.rb
|
63
54
|
- spec/helper.rb
|
64
55
|
- spec/socksify_spec.rb
|
65
|
-
has_rdoc: true
|
66
56
|
homepage: http://github.com/igrigorik/em-socksify
|
67
57
|
licenses: []
|
68
|
-
|
69
58
|
post_install_message:
|
70
59
|
rdoc_options: []
|
71
|
-
|
72
|
-
require_paths:
|
60
|
+
require_paths:
|
73
61
|
- lib
|
74
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
requirements:
|
83
|
-
- -
|
84
|
-
- !ruby/object:Gem::Version
|
85
|
-
|
86
|
-
- 0
|
87
|
-
version: "0"
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
88
74
|
requirements: []
|
89
|
-
|
90
75
|
rubyforge_project: em-socksify
|
91
|
-
rubygems_version: 1.
|
76
|
+
rubygems_version: 1.8.10
|
92
77
|
signing_key:
|
93
78
|
specification_version: 3
|
94
|
-
summary:
|
95
|
-
test_files:
|
79
|
+
summary: ! 'EventMachine SOCKSify shim: adds SOCKS support to any protocol'
|
80
|
+
test_files:
|
96
81
|
- spec/helper.rb
|
97
82
|
- spec/socksify_spec.rb
|
83
|
+
has_rdoc:
|