mirror of
https://github.com/sshuttle/sshuttle.git
synced 2025-02-15 01:49:24 +01:00
Merge pull request #23 from vieira/ns-hosts
dns: Added --ns-hosts to tunnel only some requests
This commit is contained in:
commit
0fb714893a
@ -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"
|
||||||
|
12
src/main.py
12
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,7 @@ try:
|
|||||||
remotename = opt.remote
|
remotename = opt.remote
|
||||||
if remotename == '' or remotename == '-':
|
if remotename == '' or remotename == '-':
|
||||||
remotename = None
|
remotename = None
|
||||||
|
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 +215,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