Skip to content

Commit

Permalink
Merge pull request #23 from vieira/ns-hosts
Browse files Browse the repository at this point in the history
dns: Added --ns-hosts to tunnel only some requests
  • Loading branch information
brianmay committed Oct 29, 2015
2 parents 3cf5002 + 28be71e commit 0fb7148
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 22 deletions.
15 changes: 11 additions & 4 deletions src/client.py
Expand Up @@ -277,15 +277,18 @@ def print_listening(self, what):
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 = [
Expand Down Expand Up @@ -599,7 +602,7 @@ def onhostlist(hostlist):


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):

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
20 changes: 8 additions & 12 deletions src/firewall.py
Expand Up @@ -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(
Expand Down Expand Up @@ -134,7 +134,6 @@ def ipt_ttl(*args):
'--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,
Expand All @@ -143,7 +142,7 @@ def ipt_ttl(*args):
'--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'
Expand Down Expand Up @@ -194,7 +193,6 @@ def ipt_ttl(*args):
'-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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 = []
Expand All @@ -502,7 +499,6 @@ def do_pf(port, dnsport, family, subnets, udp):
filtering_rules.append('pass out route-to lo0 inet proto tcp to <forward_subnets> keep state')

if dnsport:
nslist = resolvconf_nameservers()
tables.append('table <dns_servers> {%s}' % ','.join([ns[1] for ns in nslist]))
translating_rules.append('rdr pass on lo0 proto udp to <dns_servers> port 53 -> 127.0.0.1 port %r' % dnsport)
filtering_rules.append('pass out route-to lo0 inet proto udp to <dns_servers> port 53 keep state')
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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')

Expand Down Expand Up @@ -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)
12 changes: 8 additions & 4 deletions src/helpers.py
Expand Up @@ -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


Expand Down Expand Up @@ -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"
Expand Down
12 changes: 10 additions & 2 deletions src/main.py
Expand Up @@ -7,7 +7,7 @@
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
Expand Down Expand Up @@ -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]] <subnets...>
Expand All @@ -116,6 +119,7 @@ def parse_ipport6(s):
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
Expand Down Expand Up @@ -153,8 +157,10 @@ def parse_ipport6(s):
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))
Expand All @@ -171,6 +177,7 @@ def parse_ipport6(s):
remotename = opt.remote
if remotename == '' or remotename == '-':
remotename = None
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:
Expand Down Expand Up @@ -208,6 +215,7 @@ def parse_ipport6(s):
opt.python,
opt.latency_control,
opt.dns,
opt.ns_hosts,
method,
sh,
opt.auto_nets,
Expand Down

0 comments on commit 0fb7148

Please sign in to comment.