slyphon-transocks_em 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+