Skip to content

Commit 0fb7148

Browse files
committed
Merge pull request apenwarr#23 from vieira/ns-hosts
dns: Added --ns-hosts to tunnel only some requests
2 parents 3cf5002 + 28be71e commit 0fb7148

File tree

4 files changed

+37
-22
lines changed

4 files changed

+37
-22
lines changed

src/client.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -277,15 +277,18 @@ def print_listening(self, what):
277277
class FirewallClient:
278278

279279
def __init__(self, port_v6, port_v4, subnets_include, subnets_exclude,
280-
dnsport_v6, dnsport_v4, method, udp):
280+
dnsport_v6, dnsport_v4, ns_hosts, method, udp):
281281
self.auto_nets = []
282282
self.subnets_include = subnets_include
283283
self.subnets_exclude = subnets_exclude
284+
self.ns_hosts = ns_hosts
284285
argvbase = ([sys.argv[1], sys.argv[0], sys.argv[1]] +
285286
['-v'] * (helpers.verbose or 0) +
286287
['--firewall', str(port_v6), str(port_v4),
287288
str(dnsport_v6), str(dnsport_v4),
288289
method, str(int(udp))])
290+
if dnsport_v4 or dnsport_v6:
291+
argvbase += ['--ns-hosts', ns_hosts]
289292
if ssyslog._p:
290293
argvbase += ['--syslog']
291294
argv_tries = [
@@ -599,7 +602,7 @@ def onhostlist(hostlist):
599602

600603

601604
def main(listenip_v6, listenip_v4,
602-
ssh_cmd, remotename, python, latency_control, dns,
605+
ssh_cmd, remotename, python, latency_control, dns, ns_hosts,
603606
method, seed_hosts, auto_nets,
604607
subnets_include, subnets_exclude, syslog, daemon, pidfile):
605608

@@ -695,7 +698,9 @@ def main(listenip_v6, listenip_v4,
695698
udp_listener.print_listening("UDP redirector")
696699

697700
bound = False
698-
if dns:
701+
if dns or ns_hosts:
702+
if dns:
703+
ns_hosts += resolvconf_nameservers()
699704
# search for spare port for DNS
700705
debug2('Binding DNS:')
701706
ports = xrange(12300, 9000, -1)
@@ -735,9 +740,11 @@ def main(listenip_v6, listenip_v4,
735740
dnsport_v6 = 0
736741
dnsport_v4 = 0
737742
dns_listener = None
743+
ns_hosts = []
738744

739745
fw = FirewallClient(redirectport_v6, redirectport_v4, subnets_include,
740-
subnets_exclude, dnsport_v6, dnsport_v4, method, udp)
746+
subnets_exclude, dnsport_v6, dnsport_v4, ns_hosts,
747+
method, udp)
741748

742749
if fw.method == "tproxy":
743750
tcp_listener.setsockopt(socket.SOL_IP, IP_TRANSPARENT, 1)

src/firewall.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def _ipt_ttl(family, *args):
8383
# multiple copies shouldn't have overlapping subnets, or only the most-
8484
# recently-started one will win (because we use "-I OUTPUT 1" instead of
8585
# "-A OUTPUT").
86-
def do_iptables_nat(port, dnsport, family, subnets, udp):
86+
def do_iptables_nat(port, dnsport, nslist, family, subnets, udp):
8787
# only ipv4 supported with NAT
8888
if family != socket.AF_INET:
8989
raise Exception(
@@ -134,7 +134,6 @@ def ipt_ttl(*args):
134134
'--to-ports', str(port))
135135

136136
if dnsport:
137-
nslist = resolvconf_nameservers()
138137
for f, ip in filter(lambda i: i[0] == family, nslist):
139138
ipt_ttl('-A', chain, '-j', 'REDIRECT',
140139
'--dest', '%s/32' % ip,
@@ -143,7 +142,7 @@ def ipt_ttl(*args):
143142
'--to-ports', str(dnsport))
144143

145144

146-
def do_iptables_tproxy(port, dnsport, family, subnets, udp):
145+
def do_iptables_tproxy(port, dnsport, nslist, family, subnets, udp):
147146
if family not in [socket.AF_INET, socket.AF_INET6]:
148147
raise Exception(
149148
'Address family "%s" unsupported by tproxy method'
@@ -194,7 +193,6 @@ def ipt_ttl(*args):
194193
'-m', 'udp', '-p', 'udp')
195194

196195
if dnsport:
197-
nslist = resolvconf_nameservers()
198196
for f, ip in filter(lambda i: i[0] == family, nslist):
199197
ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
200198
'--dest', '%s/32' % ip,
@@ -442,7 +440,6 @@ def do_ipfw(port, dnsport, family, subnets, udp):
442440
IPPROTO_DIVERT)
443441
divertsock.bind(('0.0.0.0', port)) # IP field is ignored
444442

445-
nslist = resolvconf_nameservers()
446443
for f, ip in filter(lambda i: i[0] == family, nslist):
447444
# relabel and then catch outgoing DNS requests
448445
ipfw('add', sport, 'divert', sport,
@@ -483,7 +480,7 @@ def pfctl(args, stdin = None):
483480

484481
_pf_context = {'started_by_sshuttle': False, 'Xtoken':''}
485482

486-
def do_pf(port, dnsport, family, subnets, udp):
483+
def do_pf(port, dnsport, nslist, family, subnets, udp):
487484
global _pf_started_by_sshuttle
488485
tables = []
489486
translating_rules = []
@@ -502,7 +499,6 @@ def do_pf(port, dnsport, family, subnets, udp):
502499
filtering_rules.append('pass out route-to lo0 inet proto tcp to <forward_subnets> keep state')
503500

504501
if dnsport:
505-
nslist = resolvconf_nameservers()
506502
tables.append('table <dns_servers> {%s}' % ','.join([ns[1] for ns in nslist]))
507503
translating_rules.append('rdr pass on lo0 proto udp to <dns_servers> port 53 -> 127.0.0.1 port %r' % dnsport)
508504
filtering_rules.append('pass out route-to lo0 inet proto udp to <dns_servers> port 53 keep state')
@@ -690,7 +686,7 @@ def pf_add_anchor_rule(type, name):
690686
# exit. In case that fails, it's not the end of the world; future runs will
691687
# supercede it in the transproxy list, at least, so the leftover rules
692688
# are hopefully harmless.
693-
def main(port_v6, port_v4, dnsport_v6, dnsport_v4, method, udp, syslog):
689+
def main(port_v6, port_v4, dnsport_v6, dnsport_v4, nslist, method, udp, syslog):
694690
assert(port_v6 >= 0)
695691
assert(port_v6 <= 65535)
696692
assert(port_v4 >= 0)
@@ -777,14 +773,14 @@ def main(port_v6, port_v4, dnsport_v6, dnsport_v4, method, udp, syslog):
777773
subnets_v6 = filter(lambda i: i[0] == socket.AF_INET6, subnets)
778774
if port_v6:
779775
do_wait = do_it(
780-
port_v6, dnsport_v6, socket.AF_INET6, subnets_v6, udp)
776+
port_v6, dnsport_v6, nslist, socket.AF_INET6, subnets_v6, udp)
781777
elif len(subnets_v6) > 0:
782778
debug1("IPv6 subnets defined but IPv6 disabled\n")
783779

784780
subnets_v4 = filter(lambda i: i[0] == socket.AF_INET, subnets)
785781
if port_v4:
786782
do_wait = do_it(
787-
port_v4, dnsport_v4, socket.AF_INET, subnets_v4, udp)
783+
port_v4, dnsport_v4, nslist, socket.AF_INET, subnets_v4, udp)
788784
elif len(subnets_v4) > 0:
789785
debug1('IPv4 subnets defined but IPv4 disabled\n')
790786

@@ -826,7 +822,7 @@ def main(port_v6, port_v4, dnsport_v6, dnsport_v4, method, udp, syslog):
826822
except:
827823
pass
828824
if port_v6:
829-
do_it(port_v6, 0, socket.AF_INET6, [], udp)
825+
do_it(port_v6, 0, [], socket.AF_INET6, [], udp)
830826
if port_v4:
831-
do_it(port_v4, 0, socket.AF_INET, [], udp)
827+
do_it(port_v4, 0, [], socket.AF_INET, [], udp)
832828
restore_etc_hosts(port_v6 or port_v4)

src/helpers.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,7 @@ def resolvconf_nameservers():
4848
for line in open('/etc/resolv.conf'):
4949
words = line.lower().split()
5050
if len(words) >= 2 and words[0] == 'nameserver':
51-
if ':' in words[1]:
52-
l.append((socket.AF_INET6, words[1]))
53-
else:
54-
l.append((socket.AF_INET, words[1]))
51+
l.append(family_ip_tuple(words[1]))
5552
return l
5653

5754

@@ -82,6 +79,13 @@ def islocal(ip, family):
8279
return True # it's a local IP, or there would have been an error
8380

8481

82+
def family_ip_tuple(ip):
83+
if ':' in ip:
84+
return (socket.AF_INET6, ip)
85+
else:
86+
return (socket.AF_INET, ip)
87+
88+
8589
def family_to_string(family):
8690
if family == socket.AF_INET6:
8791
return "AF_INET6"

src/main.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import server
88
import firewall
99
import hostwatch
10-
from helpers import log, Fatal
10+
from helpers import family_ip_tuple, log, Fatal
1111

1212

1313
# 1.2.3.4/5 or just 1.2.3.4
@@ -105,6 +105,9 @@ def parse_ipport6(s):
105105
(ip, port) = (ip or '::', int(port or 0))
106106
return (ip, port)
107107

108+
def parse_list(list):
109+
return re.split(r'[\s,]+', list.strip()) if list else []
110+
108111

109112
optspec = """
110113
sshuttle [-l [ip:]port] [-r [username@]sshserver[:port]] <subnets...>
@@ -116,6 +119,7 @@ def parse_ipport6(s):
116119
H,auto-hosts scan for remote hostnames and update local /etc/hosts
117120
N,auto-nets automatically determine subnets to route
118121
dns capture local DNS requests and forward to the remote DNS server
122+
ns-hosts= capture and forward remote DNS requests to the following servers
119123
method= auto, nat, tproxy, pf or ipfw
120124
python= path to python interpreter on the remote server
121125
r,remote= ssh hostname (and optional username) of remote sshuttle server
@@ -153,8 +157,10 @@ def parse_ipport6(s):
153157
elif opt.firewall:
154158
if len(extra) != 6:
155159
o.fatal('exactly six arguments expected')
160+
port, dnsport = int(extra[0]), int(extra[1])
161+
nslist = [family_ip_tuple(ns) for ns in parse_list(opt.ns_hosts)]
156162
sys.exit(firewall.main(int(extra[0]), int(extra[1]),
157-
int(extra[2]), int(extra[3]),
163+
int(extra[2]), int(extra[3]), nslist,
158164
extra[4], int(extra[5]), opt.syslog))
159165
elif opt.hostwatch:
160166
sys.exit(hostwatch.hw_main(extra))
@@ -171,6 +177,7 @@ def parse_ipport6(s):
171177
remotename = opt.remote
172178
if remotename == '' or remotename == '-':
173179
remotename = None
180+
nslist = [family_ip_tuple(ns) for ns in parse_list(opt.ns_hosts)]
174181
if opt.seed_hosts and not opt.auto_hosts:
175182
o.fatal('--seed-hosts only works if you also use -H')
176183
if opt.seed_hosts:
@@ -208,6 +215,7 @@ def parse_ipport6(s):
208215
opt.python,
209216
opt.latency_control,
210217
opt.dns,
218+
opt.ns_hosts,
211219
method,
212220
sh,
213221
opt.auto_nets,

0 commit comments

Comments
 (0)