em-socksify 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.
- data/.gitignore +4 -0
- data/.rspec +0 -0
- data/Gemfile +3 -0
- data/README.md +47 -0
- data/Rakefile +2 -0
- data/em-socksify.gemspec +24 -0
- data/lib/em-socksify/socksify.rb +122 -0
- data/lib/em-socksify/version.rb +5 -0
- data/lib/em-socksify.rb +3 -0
- data/spec/helper.rb +4 -0
- data/spec/socksify_spec.rb +33 -0
- metadata +97 -0
data/.gitignore
ADDED
data/.rspec
ADDED
File without changes
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# EM-Socksify: Transparent SOCKS support for any EventMachine protocol
|
2
|
+
|
3
|
+
Dealing with SOCKS proxies is pain. EM-Socksify provides a simple shim to setup & negotiate a SOCKS5 connection for any EventMachine protocol. To add SOCKS support, all you have to do is include the module and provide your destination address.
|
4
|
+
|
5
|
+
## Example: Routing HTTP request via SOCKS5 proxy
|
6
|
+
|
7
|
+
class Handler < EM::Connection
|
8
|
+
include EM::Socksify
|
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
|
20
|
+
|
21
|
+
EM.run do
|
22
|
+
EventMachine.connect SOCKS_HOST, SOCKS_PORT, Handler
|
23
|
+
end
|
24
|
+
|
25
|
+
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
|
+
|
27
|
+
After you call socksify, the module temporarily intercepts your receive_data callbacks, negotiates the SOCKS connection (version, authentication, etc), and then once all is done, returns the control back to your code. Simple as that.
|
28
|
+
|
29
|
+
For SOCKS proxies which require authentication, use:
|
30
|
+
|
31
|
+
socksify(destination_host, destination_port, username, password)
|
32
|
+
|
33
|
+
|
34
|
+
## Wishlist
|
35
|
+
|
36
|
+
- IPV6 support
|
37
|
+
- SOCKS4 support
|
38
|
+
|
39
|
+
## Resources
|
40
|
+
|
41
|
+
- [SOCKS on Wikipedia](http://en.wikipedia.org/wiki/SOCKS)
|
42
|
+
- [Socksify-Ruby](https://github.com/astro/socksify-ruby) for regular Ruby TCPSocket
|
43
|
+
|
44
|
+
# License
|
45
|
+
|
46
|
+
(The MIT License)
|
47
|
+
Copyright © 2011 Ilya Grigorik
|
data/Rakefile
ADDED
data/em-socksify.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "em-socksify/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "em-socksify"
|
7
|
+
s.version = EventMachine::Socksify::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Ilya Grigorik"]
|
10
|
+
s.email = ["ilya@igvita.com"]
|
11
|
+
s.homepage = "http://github.com/igrigorik/em-socksify"
|
12
|
+
s.summary = "EventMachine SOCKSify shim: adds SOCKS support to any protocol"
|
13
|
+
s.description = s.summary
|
14
|
+
|
15
|
+
s.rubyforge_project = "em-socksify"
|
16
|
+
|
17
|
+
s.add_dependency "eventmachine"
|
18
|
+
s.add_development_dependency "rspec"
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module Socksify
|
3
|
+
|
4
|
+
def socksify(host, port, username = nil, password = nil, &blk)
|
5
|
+
@host = host
|
6
|
+
@port = port
|
7
|
+
@username = username
|
8
|
+
@password = password
|
9
|
+
@callback = blk
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def receive_data(data); proxy_receive_data(data); end
|
13
|
+
end
|
14
|
+
|
15
|
+
send_socks_handshake
|
16
|
+
end
|
17
|
+
|
18
|
+
def proxy_receive_data(data)
|
19
|
+
@data ||= ''
|
20
|
+
@data << data
|
21
|
+
parse_socks_response
|
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
|
28
|
+
|
29
|
+
methods = socks_methods
|
30
|
+
send_data [5, methods.size].pack('CC') + methods.pack('C*')
|
31
|
+
end
|
32
|
+
|
33
|
+
def send_socks_connect_request
|
34
|
+
begin
|
35
|
+
# TO-DO: Implement address types for IPv6 and Domain
|
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
|
42
|
+
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
|
+
end
|
122
|
+
end
|
data/lib/em-socksify.rb
ADDED
data/spec/helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe EventMachine do
|
4
|
+
|
5
|
+
it "should negotiate a socks connection" do
|
6
|
+
|
7
|
+
class Handler < EM::Connection
|
8
|
+
include EM::Socksify
|
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
|
+
@received ||= ''
|
18
|
+
@received << data
|
19
|
+
end
|
20
|
+
|
21
|
+
def unbind
|
22
|
+
@received.size.should > 0
|
23
|
+
@received[0,4].should == 'HTTP'
|
24
|
+
EM.stop
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
EM.run do
|
29
|
+
EventMachine.connect '127.0.0.1', 8080, Handler
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-socksify
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Ilya Grigorik
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-01-23 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: eventmachine
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :runtime
|
31
|
+
version_requirements: *id001
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: rspec
|
34
|
+
prerelease: false
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
segments:
|
40
|
+
- 0
|
41
|
+
version: "0"
|
42
|
+
type: :development
|
43
|
+
version_requirements: *id002
|
44
|
+
description: "EventMachine SOCKSify shim: adds SOCKS support to any protocol"
|
45
|
+
email:
|
46
|
+
- ilya@igvita.com
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files: []
|
52
|
+
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- .rspec
|
56
|
+
- Gemfile
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- em-socksify.gemspec
|
60
|
+
- lib/em-socksify.rb
|
61
|
+
- lib/em-socksify/socksify.rb
|
62
|
+
- lib/em-socksify/version.rb
|
63
|
+
- spec/helper.rb
|
64
|
+
- spec/socksify_spec.rb
|
65
|
+
has_rdoc: true
|
66
|
+
homepage: http://github.com/igrigorik/em-socksify
|
67
|
+
licenses: []
|
68
|
+
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
version: "0"
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
requirements: []
|
89
|
+
|
90
|
+
rubyforge_project: em-socksify
|
91
|
+
rubygems_version: 1.3.6
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: "EventMachine SOCKSify shim: adds SOCKS support to any protocol"
|
95
|
+
test_files:
|
96
|
+
- spec/helper.rb
|
97
|
+
- spec/socksify_spec.rb
|