mirror of
https://github.com/sshuttle/sshuttle.git
synced 2024-11-22 07:53:43 +01:00
dns: Added --ns-hosts to tunnel only some requests
By default, the --dns flag configures the firewall to only intercept queries made to the nameservers defined in resolvconf. This flag enables the user to explicitly specify the nameservers which queries will be redirected. This can be useful when the local nameserver forwards queries to some domains to a nameserver on the remote site of the tunnel.
This commit is contained in:
parent
3cf5002b62
commit
d2ee34d71c
@ -277,15 +277,18 @@ class MultiListener:
|
|||||||
class FirewallClient:
|
class FirewallClient:
|
||||||
|
|
||||||
def __init__(self, port_v6, port_v4, subnets_include, subnets_exclude,
|
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.auto_nets = []
|
||||||
self.subnets_include = subnets_include
|
self.subnets_include = subnets_include
|
||||||
self.subnets_exclude = subnets_exclude
|
self.subnets_exclude = subnets_exclude
|
||||||
|
self.ns_hosts = ns_hosts
|
||||||
argvbase = ([sys.argv[1], sys.argv[0], sys.argv[1]] +
|
argvbase = ([sys.argv[1], sys.argv[0], sys.argv[1]] +
|
||||||
['-v'] * (helpers.verbose or 0) +
|
['-v'] * (helpers.verbose or 0) +
|
||||||
['--firewall', str(port_v6), str(port_v4),
|
['--firewall', str(port_v6), str(port_v4),
|
||||||
str(dnsport_v6), str(dnsport_v4),
|
str(dnsport_v6), str(dnsport_v4),
|
||||||
method, str(int(udp))])
|
method, str(int(udp))])
|
||||||
|
if dnsport_v4 or dnsport_v6:
|
||||||
|
argvbase += ['--ns-hosts', ns_hosts]
|
||||||
if ssyslog._p:
|
if ssyslog._p:
|
||||||
argvbase += ['--syslog']
|
argvbase += ['--syslog']
|
||||||
argv_tries = [
|
argv_tries = [
|
||||||
@ -599,7 +602,7 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
|||||||
|
|
||||||
|
|
||||||
def main(listenip_v6, listenip_v4,
|
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,
|
method, seed_hosts, auto_nets,
|
||||||
subnets_include, subnets_exclude, syslog, daemon, pidfile):
|
subnets_include, subnets_exclude, syslog, daemon, pidfile):
|
||||||
|
|
||||||
@ -695,7 +698,9 @@ def main(listenip_v6, listenip_v4,
|
|||||||
udp_listener.print_listening("UDP redirector")
|
udp_listener.print_listening("UDP redirector")
|
||||||
|
|
||||||
bound = False
|
bound = False
|
||||||
|
if dns or ns_hosts:
|
||||||
if dns:
|
if dns:
|
||||||
|
ns_hosts += resolvconf_nameservers()
|
||||||
# search for spare port for DNS
|
# search for spare port for DNS
|
||||||
debug2('Binding DNS:')
|
debug2('Binding DNS:')
|
||||||
ports = xrange(12300, 9000, -1)
|
ports = xrange(12300, 9000, -1)
|
||||||
@ -735,9 +740,11 @@ def main(listenip_v6, listenip_v4,
|
|||||||
dnsport_v6 = 0
|
dnsport_v6 = 0
|
||||||
dnsport_v4 = 0
|
dnsport_v4 = 0
|
||||||
dns_listener = None
|
dns_listener = None
|
||||||
|
ns_hosts = []
|
||||||
|
|
||||||
fw = FirewallClient(redirectport_v6, redirectport_v4, subnets_include,
|
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":
|
if fw.method == "tproxy":
|
||||||
tcp_listener.setsockopt(socket.SOL_IP, IP_TRANSPARENT, 1)
|
tcp_listener.setsockopt(socket.SOL_IP, IP_TRANSPARENT, 1)
|
||||||
|
@ -83,7 +83,7 @@ def _ipt_ttl(family, *args):
|
|||||||
# multiple copies shouldn't have overlapping subnets, or only the most-
|
# multiple copies shouldn't have overlapping subnets, or only the most-
|
||||||
# recently-started one will win (because we use "-I OUTPUT 1" instead of
|
# recently-started one will win (because we use "-I OUTPUT 1" instead of
|
||||||
# "-A OUTPUT").
|
# "-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
|
# only ipv4 supported with NAT
|
||||||
if family != socket.AF_INET:
|
if family != socket.AF_INET:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
@ -134,7 +134,6 @@ def do_iptables_nat(port, dnsport, family, subnets, udp):
|
|||||||
'--to-ports', str(port))
|
'--to-ports', str(port))
|
||||||
|
|
||||||
if dnsport:
|
if dnsport:
|
||||||
nslist = resolvconf_nameservers()
|
|
||||||
for f, ip in filter(lambda i: i[0] == family, nslist):
|
for f, ip in filter(lambda i: i[0] == family, nslist):
|
||||||
ipt_ttl('-A', chain, '-j', 'REDIRECT',
|
ipt_ttl('-A', chain, '-j', 'REDIRECT',
|
||||||
'--dest', '%s/32' % ip,
|
'--dest', '%s/32' % ip,
|
||||||
@ -143,7 +142,7 @@ def do_iptables_nat(port, dnsport, family, subnets, udp):
|
|||||||
'--to-ports', str(dnsport))
|
'--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]:
|
if family not in [socket.AF_INET, socket.AF_INET6]:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Address family "%s" unsupported by tproxy method'
|
'Address family "%s" unsupported by tproxy method'
|
||||||
@ -194,7 +193,6 @@ def do_iptables_tproxy(port, dnsport, family, subnets, udp):
|
|||||||
'-m', 'udp', '-p', 'udp')
|
'-m', 'udp', '-p', 'udp')
|
||||||
|
|
||||||
if dnsport:
|
if dnsport:
|
||||||
nslist = resolvconf_nameservers()
|
|
||||||
for f, ip in filter(lambda i: i[0] == family, nslist):
|
for f, ip in filter(lambda i: i[0] == family, nslist):
|
||||||
ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
|
ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
|
||||||
'--dest', '%s/32' % ip,
|
'--dest', '%s/32' % ip,
|
||||||
@ -442,7 +440,6 @@ def do_ipfw(port, dnsport, family, subnets, udp):
|
|||||||
IPPROTO_DIVERT)
|
IPPROTO_DIVERT)
|
||||||
divertsock.bind(('0.0.0.0', port)) # IP field is ignored
|
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):
|
for f, ip in filter(lambda i: i[0] == family, nslist):
|
||||||
# relabel and then catch outgoing DNS requests
|
# relabel and then catch outgoing DNS requests
|
||||||
ipfw('add', sport, 'divert', sport,
|
ipfw('add', sport, 'divert', sport,
|
||||||
@ -483,7 +480,7 @@ def pfctl(args, stdin = None):
|
|||||||
|
|
||||||
_pf_context = {'started_by_sshuttle': False, 'Xtoken':''}
|
_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
|
global _pf_started_by_sshuttle
|
||||||
tables = []
|
tables = []
|
||||||
translating_rules = []
|
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 <forward_subnets> keep state')
|
filtering_rules.append('pass out route-to lo0 inet proto tcp to <forward_subnets> keep state')
|
||||||
|
|
||||||
if dnsport:
|
if dnsport:
|
||||||
nslist = resolvconf_nameservers()
|
|
||||||
tables.append('table <dns_servers> {%s}' % ','.join([ns[1] for ns in nslist]))
|
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)
|
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')
|
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):
|
|||||||
# exit. In case that fails, it's not the end of the world; future runs will
|
# 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
|
# supercede it in the transproxy list, at least, so the leftover rules
|
||||||
# are hopefully harmless.
|
# 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 >= 0)
|
||||||
assert(port_v6 <= 65535)
|
assert(port_v6 <= 65535)
|
||||||
assert(port_v4 >= 0)
|
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)
|
subnets_v6 = filter(lambda i: i[0] == socket.AF_INET6, subnets)
|
||||||
if port_v6:
|
if port_v6:
|
||||||
do_wait = do_it(
|
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:
|
elif len(subnets_v6) > 0:
|
||||||
debug1("IPv6 subnets defined but IPv6 disabled\n")
|
debug1("IPv6 subnets defined but IPv6 disabled\n")
|
||||||
|
|
||||||
subnets_v4 = filter(lambda i: i[0] == socket.AF_INET, subnets)
|
subnets_v4 = filter(lambda i: i[0] == socket.AF_INET, subnets)
|
||||||
if port_v4:
|
if port_v4:
|
||||||
do_wait = do_it(
|
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:
|
elif len(subnets_v4) > 0:
|
||||||
debug1('IPv4 subnets defined but IPv4 disabled\n')
|
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:
|
except:
|
||||||
pass
|
pass
|
||||||
if port_v6:
|
if port_v6:
|
||||||
do_it(port_v6, 0, socket.AF_INET6, [], udp)
|
do_it(port_v6, 0, [], socket.AF_INET6, [], udp)
|
||||||
if port_v4:
|
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)
|
restore_etc_hosts(port_v6 or port_v4)
|
||||||
|
@ -48,10 +48,7 @@ def resolvconf_nameservers():
|
|||||||
for line in open('/etc/resolv.conf'):
|
for line in open('/etc/resolv.conf'):
|
||||||
words = line.lower().split()
|
words = line.lower().split()
|
||||||
if len(words) >= 2 and words[0] == 'nameserver':
|
if len(words) >= 2 and words[0] == 'nameserver':
|
||||||
if ':' in words[1]:
|
l.append(family_ip_tuple(words[1]))
|
||||||
l.append((socket.AF_INET6, words[1]))
|
|
||||||
else:
|
|
||||||
l.append((socket.AF_INET, words[1]))
|
|
||||||
return l
|
return l
|
||||||
|
|
||||||
|
|
||||||
@ -82,6 +79,13 @@ def islocal(ip, family):
|
|||||||
return True # it's a local IP, or there would have been an error
|
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):
|
def family_to_string(family):
|
||||||
if family == socket.AF_INET6:
|
if family == socket.AF_INET6:
|
||||||
return "AF_INET6"
|
return "AF_INET6"
|
||||||
|
13
src/main.py
13
src/main.py
@ -7,7 +7,7 @@ import client
|
|||||||
import server
|
import server
|
||||||
import firewall
|
import firewall
|
||||||
import hostwatch
|
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
|
# 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))
|
(ip, port) = (ip or '::', int(port or 0))
|
||||||
return (ip, port)
|
return (ip, port)
|
||||||
|
|
||||||
|
def parse_list(list):
|
||||||
|
return re.split(r'[\s,]+', list.strip()) if list else []
|
||||||
|
|
||||||
|
|
||||||
optspec = """
|
optspec = """
|
||||||
sshuttle [-l [ip:]port] [-r [username@]sshserver[:port]] <subnets...>
|
sshuttle [-l [ip:]port] [-r [username@]sshserver[:port]] <subnets...>
|
||||||
@ -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
|
H,auto-hosts scan for remote hostnames and update local /etc/hosts
|
||||||
N,auto-nets automatically determine subnets to route
|
N,auto-nets automatically determine subnets to route
|
||||||
dns capture local DNS requests and forward to the remote DNS server
|
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
|
method= auto, nat, tproxy, pf or ipfw
|
||||||
python= path to python interpreter on the remote server
|
python= path to python interpreter on the remote server
|
||||||
r,remote= ssh hostname (and optional username) of remote sshuttle server
|
r,remote= ssh hostname (and optional username) of remote sshuttle server
|
||||||
@ -153,8 +157,10 @@ try:
|
|||||||
elif opt.firewall:
|
elif opt.firewall:
|
||||||
if len(extra) != 6:
|
if len(extra) != 6:
|
||||||
o.fatal('exactly six arguments expected')
|
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]),
|
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))
|
extra[4], int(extra[5]), opt.syslog))
|
||||||
elif opt.hostwatch:
|
elif opt.hostwatch:
|
||||||
sys.exit(hostwatch.hw_main(extra))
|
sys.exit(hostwatch.hw_main(extra))
|
||||||
@ -171,6 +177,8 @@ try:
|
|||||||
remotename = opt.remote
|
remotename = opt.remote
|
||||||
if remotename == '' or remotename == '-':
|
if remotename == '' or remotename == '-':
|
||||||
remotename = None
|
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:
|
if opt.seed_hosts and not opt.auto_hosts:
|
||||||
o.fatal('--seed-hosts only works if you also use -H')
|
o.fatal('--seed-hosts only works if you also use -H')
|
||||||
if opt.seed_hosts:
|
if opt.seed_hosts:
|
||||||
@ -208,6 +216,7 @@ try:
|
|||||||
opt.python,
|
opt.python,
|
||||||
opt.latency_control,
|
opt.latency_control,
|
||||||
opt.dns,
|
opt.dns,
|
||||||
|
opt.ns_hosts,
|
||||||
method,
|
method,
|
||||||
sh,
|
sh,
|
||||||
opt.auto_nets,
|
opt.auto_nets,
|
||||||
|
Loading…
Reference in New Issue
Block a user