slyphon-transocks_em 0.0.1

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/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__)).uniq!
4
+
5
+ require 'transocks_em'
6
+
7
+ TransocksEM::Command.main
8
+
@@ -0,0 +1,114 @@
1
+ require 'socket'
2
+ require 'eventmachine'
3
+ require 'logging'
4
+ require 'optparse'
5
+
6
+ include Logging.globally
7
+
8
+ module TransocksEM
9
+ OPSYS = `uname -s`.chomp
10
+
11
+ def self.config
12
+ unless defined?(@@config)
13
+ @@config = {}
14
+ end
15
+ @@config
16
+ end
17
+
18
+ def self.debug?
19
+ false|config[:debug]
20
+ end
21
+
22
+ class EM::Connection
23
+ def orig_sockaddr
24
+ addr = get_sock_opt(Socket::SOL_IP, 80) # Socket::SO_ORIGINAL_DST
25
+ _, port, host = addr.unpack("nnN")
26
+
27
+ [host, port]
28
+ end
29
+
30
+ def bsd_orig_sockaddr
31
+ # get_sock_opt(4, 3)
32
+ end
33
+ end
34
+
35
+ class TransocksClient < EM::P::Socks4
36
+ attr_accessor :closed
37
+
38
+ def initialize(proxied, host, port)
39
+ @proxied = proxied
40
+ super(host, port)
41
+ end
42
+
43
+ def receive_data(data)
44
+ @proxied.send_data(data)
45
+ proxy_incoming_to @proxied unless @proxied.closed
46
+ end
47
+
48
+ def proxy_target_unbound
49
+ close_connection
50
+ end
51
+
52
+ def unbind
53
+ self.closed = true
54
+ @proxied.close_connection_after_writing
55
+ end
56
+ end
57
+
58
+ class TransocksTCPServer < EM::Connection
59
+ attr_accessor :closed
60
+
61
+ def initialize(ipfw_natd_style = false)
62
+ logger.debug { "started server, ipfw_natd_style: #{ipfw_natd_style}" }
63
+ @ipfw_natd_style = ipfw_natd_style
64
+ end
65
+
66
+ def connection_completed
67
+ logger.debug { "connection_completed" }
68
+ end
69
+
70
+ def post_init
71
+ return if @ipfw_natd_style
72
+
73
+ orig_host, orig_port = orig_sockaddr
74
+ orig_host = [orig_host].pack("N").unpack("CCCC")*'.'
75
+
76
+ proxy_to orig_host, orig_port
77
+ end
78
+
79
+ def receive_data(data)
80
+ @buf ||= ''
81
+ @buf << data
82
+ if @buf.gsub!(/\[DEST (\d+\.\d+\.\d+\.\d+) (\d+)\] *\n/m, '')
83
+ orig_host, orig_port = $1, $2.to_i
84
+ proxy_to orig_host, orig_port
85
+ @proxied.send_data @buf
86
+ end
87
+ end
88
+
89
+ def proxy_target_unbound
90
+ close_connection
91
+ end
92
+
93
+ def unbind
94
+ self.closed = true
95
+ @proxied.close_connection_after_writing if @proxied
96
+ end
97
+
98
+ private
99
+ def config
100
+ TransocksEM.config
101
+ end
102
+
103
+ def proxy_to(orig_host, orig_port)
104
+ logger.debug { "connecting to #{orig_host}:#{orig_port}" }
105
+
106
+ @proxied = EM.connect(config[:connect_host], config[:connect_port], TransocksClient, self, orig_host, orig_port)
107
+ proxy_incoming_to @proxied unless @proxied.closed
108
+ end
109
+ end
110
+ end
111
+
112
+ require 'transocks_em/command'
113
+ require 'transocks_em/ipfw_tweaker'
114
+
@@ -0,0 +1,100 @@
1
+ module TransocksEM
2
+ class Command
3
+ USAGE = <<-EOS
4
+ Usage: transsocks_em.rb [opts] <proxy_to_host> <proxy_to_port> <listen_port>
5
+
6
+ --natd puts daemon in ipfw/natd bsd mode rather than linux iptables SO_ORIGINAL_DST mode,
7
+ aka: get the original dest addr/port from the TCP stream rather than
8
+ from getsockopt. Current issue with natd mode is that the original addr
9
+ wont be sent until the connection sends some initial data (this makes it
10
+ incompatible with certain protocols, SSH for example). This is the
11
+ default for Darwin (checked using 'uname -s').
12
+
13
+ If --debug is enabled, natd debugging output goes to /tmp/natd.log
14
+
15
+
16
+ EOS
17
+
18
+ def self.main
19
+ new.main
20
+ end
21
+
22
+ def initialize
23
+ @ipfw_tweaker = IPFWTweaker.new
24
+ @set_ipfw_state = false
25
+ @divert_ports = []
26
+ end
27
+
28
+ def config
29
+ TransocksEM.config
30
+ end
31
+
32
+ def optparser
33
+ @optparser ||= OptionParser.new do |o|
34
+ o.banner = USAGE
35
+ o.on('--natd', 'see description above') { config[:natd] = true }
36
+ o.on('-P', '--ports a,b,c', Array, 'the ports to divert to socks (mandatory)') do |a|
37
+ @divert_ports = a.map { |n| Integer(n) }
38
+ end
39
+ o.on('-D', '--debug', 'set debug logging') { config[:debug] = true }
40
+ o.on('-h', '--help', "you're reading it") { help! }
41
+ end
42
+ end
43
+
44
+ def help!
45
+ $stderr.puts optparser
46
+ exit 1
47
+ end
48
+
49
+ def main
50
+ optparser.parse!(ARGV)
51
+
52
+ help! if @divert_ports.empty? or (ARGV.size < 3)
53
+
54
+ host, port, listen = ARGV[0,3]
55
+
56
+ config.merge!({
57
+ :connect_host => host,
58
+ :connect_port => port.to_i,
59
+ :listen_port => listen.to_i,
60
+ })
61
+
62
+ Logging.backtrace(true)
63
+
64
+ Logging.logger.root.tap do |root|
65
+ root.level = TransocksEM.debug? ? :debug : :info
66
+ root.add_appenders(Logging.appenders.stderr)
67
+ end
68
+
69
+ logger.debug { "using config: #{config.inspect}" }
70
+
71
+ if (ARGV[3] == 'natd') or (TransocksEM::OPSYS =~ /^(?:Darwin|FreeBSD)$/)
72
+ config[:natd] = true
73
+ logger.info { "set natd mode" }
74
+ @set_ipfw_state = true
75
+ @ipfw_tweaker.divert_to_socks(*@divert_ports)
76
+ end
77
+
78
+ %w[INT TERM].each do |sig|
79
+ Kernel.trap(sig) do
80
+ logger.info { "trapped signal #{sig}, shutting down" }
81
+ EM.next_tick { EM.stop_event_loop }
82
+ end
83
+ end
84
+
85
+ EM.run do
86
+ logger.info { "started event loop" }
87
+
88
+ EM.error_handler do |e|
89
+ logger.error { e }
90
+ end
91
+
92
+ EM.start_server '127.0.0.1', config[:listen_port], TransocksTCPServer, config[:natd]
93
+ end
94
+
95
+ ensure
96
+ @ipfw_tweaker.clear_state! if @set_ipfw_state
97
+ end
98
+ end
99
+ end
100
+
@@ -0,0 +1,156 @@
1
+ require 'tempfile'
2
+
3
+ module TransocksEM
4
+ class IPFWTweaker
5
+ DEFAULT_START_RULE_NUM = 1_000
6
+ OFFSET = 10
7
+ DIVERT_PORT = 4000
8
+ IPFW_SET_NUM = 7
9
+
10
+ IPFW_BIN = '/sbin/ipfw' # Darwin & FreeBSD
11
+
12
+ attr_reader :start_rule_num, :offset, :ipfw_set_num, :added_rule_nums, :divert_port
13
+
14
+ def initialize(opts={})
15
+ @start_rule_num = opts.fetch(:start_rule_num, DEFAULT_START_RULE_NUM)
16
+ @cur_rule_num = @start_rule_num
17
+
18
+ @offset = opts.fetch(:offset, OFFSET)
19
+ @divert_port = opts.fetch(:divert_port, DIVERT_PORT)
20
+ @ipfw_set_num = opts.fetch(:ipfw_set_num, IPFW_SET_NUM)
21
+ @added_rule_nums = []
22
+ @natd_pid = nil
23
+ @natd_config_tmpfile = nil
24
+ end
25
+
26
+ def transocks_port
27
+ TransocksEM.config[:listen_port]
28
+ end
29
+
30
+ # Diverts the given ports to SOCKS via ipfw and natd. after this has been
31
+ # called once, you must reset the rules before calling it again, or an
32
+ # error will occur
33
+ def divert_to_socks(*ports)
34
+ clear_state!
35
+ add_ipfw_diversion_rules!(ports)
36
+ setup_natd!(ports)
37
+ end
38
+
39
+ def clear_state!
40
+ clear_ipfw_rules!
41
+ kill_natd!
42
+ end
43
+
44
+ def add_ipfw_diversion_rules!(ports)
45
+ ipfw *%W[set disable #{ipfw_set_num}]
46
+ ipfw *%W[add #{cur_rule_num} set #{ipfw_set_num} divert #{divert_port} tcp from 127.0.0.1 #{transocks_port} to me in]
47
+ ipfw *%W[add #{cur_rule_num} set #{ipfw_set_num} divert #{divert_port} tcp from me to any #{ports.join(',')} out]
48
+ ipfw *%W[set enable #{ipfw_set_num}]
49
+
50
+ logger.debug do
51
+ rules = `sudo ipfw list`
52
+ "ipfw rules: #{rules}"
53
+ end
54
+ end
55
+
56
+ def setup_natd!(ports)
57
+ kill_natd!
58
+
59
+ @natd_config_tmpfile = tmp = Tempfile.new('natdrulez')
60
+
61
+ tmp.puts(<<-EOS)
62
+ port #{divert_port}
63
+ log yes
64
+ interface lo0
65
+ proxy_only yes
66
+ EOS
67
+
68
+ ports.each do |port|
69
+ tmp.puts %Q[proxy_rule type encode_tcp_stream port #{port} server 127.0.0.1:#{transocks_port}]
70
+ end
71
+
72
+ tmp.fsync
73
+
74
+ tmp.rewind
75
+ config = tmp.read
76
+
77
+ logger.debug { "natd config: \n#{config}" }
78
+
79
+ cmd = %W[sudo #{natd_bin} -f #{tmp.path}]
80
+
81
+ opts = {
82
+ :chdir => '/',
83
+ :in => '/dev/null',
84
+ :out => [:child, :err],
85
+ }
86
+
87
+ opts[:err] = TransocksEM.debug? ? '/tmp/natd.log' : '/dev/null'
88
+
89
+ cmd << opts
90
+
91
+ logger.debug { "spawning natd: #{cmd.inspect}" }
92
+
93
+ @natd_pid = Process.spawn(*cmd)
94
+
95
+ logger.debug { "launched natd pid: #{@natd_pid}" }
96
+ end
97
+
98
+ def clear_ipfw_rules!
99
+ sh(*%W[sudo ipfw delete set #{ipfw_set_num}])
100
+ rescue RuntimeError
101
+ end
102
+
103
+ def kill_natd!
104
+ if @natd_config_tmpfile
105
+ @natd_config_tmpfile.close
106
+ @natd_config_tmpfile = nil
107
+ end
108
+
109
+ if @natd_pid
110
+ sh(*%W[sudo kill -9 #{@natd_pid}])
111
+ Process.waitpid2(@natd_pid)
112
+ @natd_pid = nil
113
+ end
114
+ end
115
+
116
+ protected
117
+ def ipfw(*args)
118
+ cmd = ['sudo', IPFW_BIN]
119
+
120
+ cmd << '-q' unless TransocksEM.debug?
121
+
122
+ cmd += args
123
+
124
+ sh(*cmd)
125
+ end
126
+
127
+ def sh(*cmd)
128
+ logger.debug { "running command: #{cmd.join(' ')}" }
129
+
130
+ system(*cmd).tap do |bool|
131
+ raise "command: #{cmd.join(' ')} failed with status: #{$?.inspect}" unless bool
132
+ end
133
+ end
134
+
135
+ def natd_bin
136
+ @natd_bin ||= (
137
+ case OPSYS
138
+ when 'Darwin'
139
+ '/usr/sbin/natd'
140
+ when 'FreeBSD'
141
+ '/sbin/natd'
142
+ else
143
+ raise "don't know about natd on opsys: #{OPSYS}"
144
+ end
145
+ )
146
+ end
147
+
148
+ def cur_rule_num
149
+ orig = @cur_rule_num
150
+ @added_rule_nums << orig
151
+ @cur_rule_num += @offset
152
+ orig
153
+ end
154
+ end
155
+ end
156
+
@@ -0,0 +1,4 @@
1
+ module TransocksEM
2
+ VERSION = '0.0.1'
3
+ end
4
+
@@ -0,0 +1,30 @@
1
+ #!/bin/sh
2
+
3
+ # transparently proxy all traffic of processes launched with specified group to transocks daemon
4
+
5
+ PROXY_GROUP=prx
6
+ TRANSOCKS_PORT=1212
7
+
8
+ if [ "$1" = "--clear" ]; then
9
+ sudo iptables -t nat --flush
10
+ echo cleared all nat rules
11
+ exit 0
12
+ fi
13
+
14
+ sudo iptables -t nat -X SOCKSIFY
15
+ sudo iptables -t nat -N SOCKSIFY
16
+
17
+ # Only proxy traffic for programs run with group $PROXY_GROUP
18
+ sudo iptables -t nat -A SOCKSIFY -m owner ! --gid-owner $PROXY_GROUP -j RETURN
19
+
20
+ # Exceptions for local traffic
21
+ sudo iptables -t nat -A SOCKSIFY -o lo -j RETURN
22
+ sudo iptables -t nat -A SOCKSIFY --dst 127.0.0.1 -j RETURN
23
+ # Add extra local nets to ignore here as necessary
24
+ sudo iptables -t nat -A SOCKSIFY --dst 192.168.0.0/16 -j RETURN
25
+
26
+ # Send to transocks
27
+ sudo iptables -t nat -A SOCKSIFY -p tcp -j REDIRECT --to-port $TRANSOCKS_PORT
28
+
29
+ # Socksify traffic leaving this host:
30
+ sudo iptables -t nat -A OUTPUT -p tcp --syn -j SOCKSIFY
@@ -0,0 +1,20 @@
1
+ #!/bin/sh
2
+
3
+ TRANSOCKS_PORT=1212
4
+
5
+ # transparently proxy ports 80, 443, and 1935 (hulu) to transocks daemon
6
+
7
+ if [ "$1" = "--clear" ]; then
8
+ sudo ipfw -q flush
9
+ echo cleared all ipfw rules
10
+ exit 0
11
+ fi
12
+
13
+ sudo ipfw add divert 4000 tcp from 127.0.0.1 $TRANSOCKS_PORT to me in
14
+ sudo ipfw add divert 4000 tcp from me to any 80,443,1935 out
15
+
16
+ sudo killall -9 natd
17
+ sudo natd -port 4000 -interface lo0 -proxy_only \
18
+ -proxy_rule type encode_tcp_stream port 80 server 127.0.0.1:$TRANSOCKS_PORT \
19
+ -proxy_rule type encode_tcp_stream port 443 server 127.0.0.1:$TRANSOCKS_PORT \
20
+ -proxy_rule type encode_tcp_stream port 1935 server 127.0.0.1:$TRANSOCKS_PORT \
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require 'transocks_em/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "slyphon-transocks_em"
7
+ s.version = TransocksEM::VERSION
8
+ s.authors = ["coderrr", "Jonathan D. Simms"]
9
+ s.email = ["coderrr.contact@gmail.com", "simms@hp.com"]
10
+ s.summary = %q{transparently tunnel connections over SOCKS5 using eventmachine and ipfw/iptables}
11
+ s.description = s.summary
12
+
13
+ s.required_ruby_version = '>= 1.9.2'
14
+
15
+ s.add_runtime_dependency('logging', '~> 1.5.1')
16
+ s.add_runtime_dependency('eventmachine', '~> 1.0.0.beta.3')
17
+
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+ end
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+
4
+ class UOTClient < EM::Connection
5
+ def receive_data(data)
6
+ port, host = Socket.unpack_sockaddr_in(get_peername)
7
+
8
+ dst_port = data.slice!(-2..-1).unpack("S").first
9
+ dst_host = data.slice!(-4..-1).unpack("C4")*'.'
10
+
11
+ $tunnel.mapping[[dst_host, dst_port]] = [host, port]
12
+ $tunnel.send_object [dst_host, dst_port, data]
13
+
14
+ print '>'; $stdout.flush
15
+ end
16
+ end
17
+
18
+ class UOTTunnel < EM::Connection
19
+ attr_accessor :mapping
20
+
21
+ include EM::P::ObjectProtocol
22
+
23
+ def initialize
24
+ super
25
+ @mapping = {}
26
+ end
27
+
28
+ def receive_object(data)
29
+ host, port, data = data
30
+
31
+ dst_host, dst_port = @mapping[[host, port]]
32
+ if ! dst_host
33
+ puts "unexpected packet received for #{host}:#{port}"
34
+ return
35
+ end
36
+
37
+ $udp_connection.send_datagram data, dst_host, dst_port
38
+ print '<'; $stdout.flush
39
+ end
40
+
41
+ def unbind
42
+ p :control_conn_dropped
43
+ reconnect
44
+ end
45
+
46
+ def reconnect
47
+ @mapping.clear
48
+ super $host, $port
49
+ end
50
+ end
51
+
52
+ $host, $port, listen_port = ARGV[0], ARGV[1].to_i, ARGV[2]
53
+
54
+ EM.run do
55
+ EM.error_handler { puts $!, $@ }
56
+ $tunnel = EM.connect $host, $port, UOTTunnel
57
+ $udp_connection = EM.open_datagram_socket '127.1', listen_port, UOTClient
58
+ end
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require 'socket'
4
+
5
+ class UOTServer < EM::Connection
6
+ include EM::P::ObjectProtocol
7
+
8
+ def post_init
9
+ $server = self
10
+ end
11
+
12
+ def receive_object(data)
13
+ host, port, data = data
14
+ $outgoing_connection.send_datagram data, host, port
15
+ end
16
+ end
17
+
18
+ class UDPConnection < EM::Connection
19
+ def receive_data(data)
20
+ port, host = Socket.unpack_sockaddr_in(get_peername)
21
+ $server.send_object [host, port, data] if $server
22
+ end
23
+ end
24
+
25
+ listen_port = ARGV.first
26
+
27
+ EM.run do
28
+ EM.error_handler { puts $!, $@ }
29
+ EM.start_server '0', listen_port, UOTServer
30
+ $outgoing_connection = EM.open_datagram_socket '0', 0, UDPConnection
31
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: slyphon-transocks_em
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - coderrr
14
+ - Jonathan D. Simms
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-08-17 00:00:00 Z
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: logging
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 1
30
+ segments:
31
+ - 1
32
+ - 5
33
+ - 1
34
+ version: 1.5.1
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: eventmachine
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 62196357
46
+ segments:
47
+ - 1
48
+ - 0
49
+ - 0
50
+ - beta
51
+ - 3
52
+ version: 1.0.0.beta.3
53
+ type: :runtime
54
+ version_requirements: *id002
55
+ description: transparently tunnel connections over SOCKS5 using eventmachine and ipfw/iptables
56
+ email:
57
+ - coderrr.contact@gmail.com
58
+ - simms@hp.com
59
+ executables:
60
+ - transocks-server
61
+ extensions: []
62
+
63
+ extra_rdoc_files: []
64
+
65
+ files:
66
+ - Gemfile
67
+ - bin/transocks-server
68
+ - lib/transocks_em.rb
69
+ - lib/transocks_em/command.rb
70
+ - lib/transocks_em/ipfw_tweaker.rb
71
+ - lib/transocks_em/version.rb
72
+ - setup_linux_routing
73
+ - setup_osx_routing
74
+ - transocks_em.gemspec
75
+ - uot_client.rb
76
+ - uot_server.rb
77
+ homepage:
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options: []
82
+
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ hash: 55
91
+ segments:
92
+ - 1
93
+ - 9
94
+ - 2
95
+ version: 1.9.2
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ hash: 3
102
+ segments:
103
+ - 0
104
+ version: "0"
105
+ requirements: []
106
+
107
+ rubyforge_project:
108
+ rubygems_version: 1.8.6
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: transparently tunnel connections over SOCKS5 using eventmachine and ipfw/iptables
112
+ test_files: []
113
+