diff --git a/src/client.py b/src/client.py index 0fe0e07..48be2fa 100644 --- a/src/client.py +++ b/src/client.py @@ -277,15 +277,18 @@ class MultiListener: class FirewallClient: def __init__(self, port_v6, port_v4, subnets_include, subnets_exclude, - dnsport_v6, dnsport_v4, method, udp): + dnsport_v6, dnsport_v4, ns_hosts, method, udp): self.auto_nets = [] self.subnets_include = subnets_include self.subnets_exclude = subnets_exclude + self.ns_hosts = ns_hosts argvbase = ([sys.argv[1], sys.argv[0], sys.argv[1]] + ['-v'] * (helpers.verbose or 0) + ['--firewall', str(port_v6), str(port_v4), str(dnsport_v6), str(dnsport_v4), method, str(int(udp))]) + if dnsport_v4 or dnsport_v6: + argvbase += ['--ns-hosts', ns_hosts] if ssyslog._p: argvbase += ['--syslog'] argv_tries = [ @@ -599,7 +602,7 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename, def main(listenip_v6, listenip_v4, - ssh_cmd, remotename, python, latency_control, dns, + ssh_cmd, remotename, python, latency_control, dns, ns_hosts, method, seed_hosts, auto_nets, subnets_include, subnets_exclude, syslog, daemon, pidfile): @@ -695,7 +698,9 @@ def main(listenip_v6, listenip_v4, udp_listener.print_listening("UDP redirector") bound = False - if dns: + if dns or ns_hosts: + if dns: + ns_hosts += resolvconf_nameservers() # search for spare port for DNS debug2('Binding DNS:') ports = xrange(12300, 9000, -1) @@ -735,9 +740,11 @@ def main(listenip_v6, listenip_v4, dnsport_v6 = 0 dnsport_v4 = 0 dns_listener = None + ns_hosts = [] fw = FirewallClient(redirectport_v6, redirectport_v4, subnets_include, - subnets_exclude, dnsport_v6, dnsport_v4, method, udp) + subnets_exclude, dnsport_v6, dnsport_v4, ns_hosts, + method, udp) if fw.method == "tproxy": tcp_listener.setsockopt(socket.SOL_IP, IP_TRANSPARENT, 1) diff --git a/src/firewall.py b/src/firewall.py index c8f2ec5..07ce437 100644 --- a/src/firewall.py +++ b/src/firewall.py @@ -83,7 +83,7 @@ def _ipt_ttl(family, *args): # multiple copies shouldn't have overlapping subnets, or only the most- # recently-started one will win (because we use "-I OUTPUT 1" instead of # "-A OUTPUT"). -def do_iptables_nat(port, dnsport, family, subnets, udp): +def do_iptables_nat(port, dnsport, nslist, family, subnets, udp): # only ipv4 supported with NAT if family != socket.AF_INET: raise Exception( @@ -134,7 +134,6 @@ def do_iptables_nat(port, dnsport, family, subnets, udp): '--to-ports', str(port)) if dnsport: - nslist = resolvconf_nameservers() for f, ip in filter(lambda i: i[0] == family, nslist): ipt_ttl('-A', chain, '-j', 'REDIRECT', '--dest', '%s/32' % ip, @@ -143,7 +142,7 @@ def do_iptables_nat(port, dnsport, family, subnets, udp): '--to-ports', str(dnsport)) -def do_iptables_tproxy(port, dnsport, family, subnets, udp): +def do_iptables_tproxy(port, dnsport, nslist, family, subnets, udp): if family not in [socket.AF_INET, socket.AF_INET6]: raise Exception( 'Address family "%s" unsupported by tproxy method' @@ -194,7 +193,6 @@ def do_iptables_tproxy(port, dnsport, family, subnets, udp): '-m', 'udp', '-p', 'udp') if dnsport: - nslist = resolvconf_nameservers() for f, ip in filter(lambda i: i[0] == family, nslist): ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1', '--dest', '%s/32' % ip, @@ -442,7 +440,6 @@ def do_ipfw(port, dnsport, family, subnets, udp): IPPROTO_DIVERT) divertsock.bind(('0.0.0.0', port)) # IP field is ignored - nslist = resolvconf_nameservers() for f, ip in filter(lambda i: i[0] == family, nslist): # relabel and then catch outgoing DNS requests ipfw('add', sport, 'divert', sport, @@ -483,7 +480,7 @@ def pfctl(args, stdin = None): _pf_context = {'started_by_sshuttle': False, 'Xtoken':''} -def do_pf(port, dnsport, family, subnets, udp): +def do_pf(port, dnsport, nslist, family, subnets, udp): global _pf_started_by_sshuttle tables = [] translating_rules = [] @@ -502,7 +499,6 @@ def do_pf(port, dnsport, family, subnets, udp): filtering_rules.append('pass out route-to lo0 inet proto tcp to keep state') if dnsport: - nslist = resolvconf_nameservers() tables.append('table {%s}' % ','.join([ns[1] for ns in nslist])) translating_rules.append('rdr pass on lo0 proto udp to port 53 -> 127.0.0.1 port %r' % dnsport) filtering_rules.append('pass out route-to lo0 inet proto udp to port 53 keep state') @@ -690,7 +686,7 @@ def pf_add_anchor_rule(type, name): # exit. In case that fails, it's not the end of the world; future runs will # supercede it in the transproxy list, at least, so the leftover rules # are hopefully harmless. -def main(port_v6, port_v4, dnsport_v6, dnsport_v4, method, udp, syslog): +def main(port_v6, port_v4, dnsport_v6, dnsport_v4, nslist, method, udp, syslog): assert(port_v6 >= 0) assert(port_v6 <= 65535) assert(port_v4 >= 0) @@ -777,14 +773,14 @@ def main(port_v6, port_v4, dnsport_v6, dnsport_v4, method, udp, syslog): subnets_v6 = filter(lambda i: i[0] == socket.AF_INET6, subnets) if port_v6: do_wait = do_it( - port_v6, dnsport_v6, socket.AF_INET6, subnets_v6, udp) + port_v6, dnsport_v6, nslist, socket.AF_INET6, subnets_v6, udp) elif len(subnets_v6) > 0: debug1("IPv6 subnets defined but IPv6 disabled\n") subnets_v4 = filter(lambda i: i[0] == socket.AF_INET, subnets) if port_v4: do_wait = do_it( - port_v4, dnsport_v4, socket.AF_INET, subnets_v4, udp) + port_v4, dnsport_v4, nslist, socket.AF_INET, subnets_v4, udp) elif len(subnets_v4) > 0: debug1('IPv4 subnets defined but IPv4 disabled\n') @@ -826,7 +822,7 @@ def main(port_v6, port_v4, dnsport_v6, dnsport_v4, method, udp, syslog): except: pass if port_v6: - do_it(port_v6, 0, socket.AF_INET6, [], udp) + do_it(port_v6, 0, [], socket.AF_INET6, [], udp) if port_v4: - do_it(port_v4, 0, socket.AF_INET, [], udp) + do_it(port_v4, 0, [], socket.AF_INET, [], udp) restore_etc_hosts(port_v6 or port_v4) diff --git a/src/helpers.py b/src/helpers.py index abb9f80..6d8c9a3 100644 --- a/src/helpers.py +++ b/src/helpers.py @@ -48,10 +48,7 @@ def resolvconf_nameservers(): for line in open('/etc/resolv.conf'): words = line.lower().split() if len(words) >= 2 and words[0] == 'nameserver': - if ':' in words[1]: - l.append((socket.AF_INET6, words[1])) - else: - l.append((socket.AF_INET, words[1])) + l.append(family_ip_tuple(words[1])) return l @@ -82,6 +79,13 @@ def islocal(ip, family): return True # it's a local IP, or there would have been an error +def family_ip_tuple(ip): + if ':' in ip: + return (socket.AF_INET6, ip) + else: + return (socket.AF_INET, ip) + + def family_to_string(family): if family == socket.AF_INET6: return "AF_INET6" diff --git a/src/main.py b/src/main.py index fe8275c..8d506e1 100644 --- a/src/main.py +++ b/src/main.py @@ -7,7 +7,7 @@ import client import server import firewall import hostwatch -from helpers import log, Fatal +from helpers import family_ip_tuple, log, Fatal # 1.2.3.4/5 or just 1.2.3.4 @@ -105,6 +105,9 @@ def parse_ipport6(s): (ip, port) = (ip or '::', int(port or 0)) return (ip, port) +def parse_list(list): + return re.split(r'[\s,]+', list.strip()) if list else [] + optspec = """ sshuttle [-l [ip:]port] [-r [username@]sshserver[:port]] @@ -116,6 +119,7 @@ l,listen= transproxy to this ip address and port number 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 +ns-hosts= capture and forward remote DNS requests to the following servers method= auto, nat, tproxy, pf or ipfw python= path to python interpreter on the remote server r,remote= ssh hostname (and optional username) of remote sshuttle server @@ -153,8 +157,10 @@ try: elif opt.firewall: if len(extra) != 6: o.fatal('exactly six arguments expected') + port, dnsport = int(extra[0]), int(extra[1]) + nslist = [family_ip_tuple(ns) for ns in parse_list(opt.ns_hosts)] sys.exit(firewall.main(int(extra[0]), int(extra[1]), - int(extra[2]), int(extra[3]), + int(extra[2]), int(extra[3]), nslist, extra[4], int(extra[5]), opt.syslog)) elif opt.hostwatch: sys.exit(hostwatch.hw_main(extra)) @@ -171,6 +177,8 @@ try: remotename = opt.remote if remotename == '' or remotename == '-': remotename = None + #nslist = re.split(r'[\s,]+', opt.dnshosts.strip()) if opt.dnshosts else [] + nslist = [family_ip_tuple(ns) for ns in parse_list(opt.ns_hosts)] if opt.seed_hosts and not opt.auto_hosts: o.fatal('--seed-hosts only works if you also use -H') if opt.seed_hosts: @@ -208,6 +216,7 @@ try: opt.python, opt.latency_control, opt.dns, + opt.ns_hosts, method, sh, opt.auto_nets,