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 ADDED
@@ -0,0 +1,4 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
data/.rspec ADDED
File without changes
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -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
@@ -0,0 +1,5 @@
1
+ module EventMachine
2
+ module Socksify
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ require 'eventmachine'
2
+
3
+ require 'em-socksify/socksify'
data/spec/helper.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'em-socksify'
@@ -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