diff --git a/client.py b/client.py index 0954a09..2416fc1 100644 --- a/client.py +++ b/client.py @@ -12,6 +12,7 @@ def got_signal(signum, frame): _pidname = None +IP_TRANSPARENT = 19 def check_daemon(pidfile): global _pidname _pidname = os.path.abspath(pidfile) @@ -232,8 +233,11 @@ def onaccept_tcp(listener, method, mux, handlers): return else: raise - dstip = original_dst(sock) - debug1('Accept: %s:%r -> %s:%r.\n' % (srcip[0],srcip[1], + if method == "tproxy": + dstip = sock.getsockname(); + else: + dstip = original_dst(sock) + debug1('Accept TCP: %s:%r -> %s:%r.\n' % (srcip[0],srcip[1], dstip[0],dstip[1])) if dstip[1] == listener.getsockname()[1] and islocal(dstip[0], sock.family): debug1("-- ignored: that's my address!\n") @@ -466,6 +470,9 @@ def main(listenip_v4, fw = FirewallClient(redirectport_v4, subnets_include, subnets_exclude, dnsport_v4, method) + if fw.method == "tproxy": + tcp_listener.setsockopt(socket.SOL_IP, IP_TRANSPARENT, 1) + try: return _main(tcp_listener, fw, ssh_cmd, remotename, python, latency_control, dns_listener, diff --git a/firewall.py b/firewall.py index 2136f21..1829072 100644 --- a/firewall.py +++ b/firewall.py @@ -118,6 +118,67 @@ def do_iptables_nat(port, dnsport, family, subnets): '--to-ports', str(dnsport)) +def do_iptables_tproxy(port, dnsport, family, subnets): + if family not in [socket.AF_INET]: + raise Exception('Address family "%s" unsupported by tproxy method'%family_to_string(family)) + + table = "mangle" + def ipt(*args): + return _ipt(family, table, *args) + def ipt_ttl(*args): + return _ipt_ttl(family, table, *args) + + mark_chain = 'sshuttle-m-%s' % port + tproxy_chain = 'sshuttle-t-%s' % port + divert_chain = 'sshuttle-d-%s' % port + + # basic cleanup/setup of chains + if ipt_chain_exists(family, table, mark_chain): + ipt('-D', 'OUTPUT', '-j', mark_chain) + ipt('-F', mark_chain) + ipt('-X', mark_chain) + + if ipt_chain_exists(family, table, tproxy_chain): + ipt('-D', 'PREROUTING', '-j', tproxy_chain) + ipt('-F', tproxy_chain) + ipt('-X', tproxy_chain) + + if ipt_chain_exists(family, table, divert_chain): + ipt('-F', divert_chain) + ipt('-X', divert_chain) + + if subnets or dnsport: + ipt('-N', mark_chain) + ipt('-F', mark_chain) + ipt('-N', divert_chain) + ipt('-F', divert_chain) + ipt('-N', tproxy_chain) + ipt('-F', tproxy_chain) + ipt('-I', 'OUTPUT', '1', '-j', mark_chain) + ipt('-I', 'PREROUTING', '1', '-j', tproxy_chain) + ipt('-A', divert_chain, '-j', 'MARK', '--set-mark', '1') + ipt('-A', divert_chain, '-j', 'ACCEPT') + ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain, + '-m', 'tcp', '-p', 'tcp') + if subnets: + for f,swidth,sexclude,snet in sorted(subnets, key=lambda s: s[1], reverse=True): + if sexclude: + ipt('-A', mark_chain, '-j', 'RETURN', + '--dest', '%s/%s' % (snet,swidth), + '-m', 'tcp', '-p', 'tcp') + ipt('-A', tproxy_chain, '-j', 'RETURN', + '--dest', '%s/%s' % (snet,swidth), + '-m', 'tcp', '-p', 'tcp') + else: + ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1', + '--dest', '%s/%s' % (snet,swidth), + '-m', 'tcp', '-p', 'tcp') + ipt('-A', tproxy_chain, '-j', 'TPROXY', '--tproxy-mark', '0x1/0x1', + '--dest', '%s/%s' % (snet,swidth), + '-m', 'tcp', '-p', 'tcp', + '--on-port', str(port)) + + def ipfw_rule_exists(n): argv = ['ipfw', 'list'] p = ssubprocess.Popen(argv, stdout = ssubprocess.PIPE) @@ -408,6 +469,8 @@ def main(port_v4, dnsport_v4, method, syslog): if method == "nat": do_it = do_iptables_nat + elif method == "tproxy": + do_it = do_iptables_tproxy elif method == "ipfw": do_it = do_ipfw else: diff --git a/main.py b/main.py index 10fe35d..1cf12ad 100644 --- a/main.py +++ b/main.py @@ -60,7 +60,7 @@ l,listen= transproxy to this ip address and port number [127.0.0.1:0] H,auto-hosts scan for remote hostnames and update local /etc/hosts N,auto-nets automatically determine subnets to route dns capture local DNS requests and forward to the remote DNS server -method= auto, nat, or ipfw +method= auto, nat, tproxy, or ipfw python= path to python interpreter on the remote server r,remote= ssh hostname (and optional username) of remote sshuttle server x,exclude= exclude this subnet (can be used more than once) @@ -120,7 +120,7 @@ try: sh = None if not opt.method: method = "auto" - elif opt.method in [ "auto", "nat", "ipfw" ]: + elif opt.method in [ "auto", "nat", "tproxy", "ipfw" ]: method = opt.method else: o.fatal("method %s not supported"%opt.method)