Commit 20254bab authored by Brian May's avatar Brian May

TProxy IPv6 support.

parent f41c6b62
...@@ -99,41 +99,55 @@ def original_dst(sock): ...@@ -99,41 +99,55 @@ def original_dst(sock):
class MultiListener: class MultiListener:
def __init__(self, type=socket.SOCK_STREAM, proto=0): def __init__(self, type=socket.SOCK_STREAM, proto=0):
self.v6 = socket.socket(socket.AF_INET6, type, proto)
self.v4 = socket.socket(socket.AF_INET, type, proto) self.v4 = socket.socket(socket.AF_INET, type, proto)
def setsockopt(self, level, optname, value): def setsockopt(self, level, optname, value):
if self.v6:
self.v6.setsockopt(level, optname, value)
if self.v4: if self.v4:
self.v4.setsockopt(level, optname, value) self.v4.setsockopt(level, optname, value)
def add_handler(self, handlers, callback, method, mux): def add_handler(self, handlers, callback, method, mux):
if self.v6:
handlers.append(Handler([self.v6], lambda: callback(self.v6, method, mux, handlers)))
if self.v4: if self.v4:
handlers.append(Handler([self.v4], lambda: callback(self.v4, method, mux, handlers))) handlers.append(Handler([self.v4], lambda: callback(self.v4, method, mux, handlers)))
def listen(self, backlog): def listen(self, backlog):
if self.v6:
self.v6.listen(backlog)
if self.v4: if self.v4:
self.v4.listen(backlog) self.v4.listen(backlog)
def bind(self, address_v4): def bind(self, address_v6, address_v4):
if address_v6 and self.v6:
self.v6.bind(address_v6)
else:
self.v6 = None
if address_v4 and self.v4: if address_v4 and self.v4:
self.v4.bind(address_v4) self.v4.bind(address_v4)
else: else:
self.v4 = None self.v4 = None
def print_listening(self, what): def print_listening(self, what):
if self.v6:
listenip = self.v6.getsockname()
debug1('%s listening on %r.\n' % (what, listenip))
if self.v4: if self.v4:
listenip = self.v4.getsockname() listenip = self.v4.getsockname()
debug1('%s listening on %r.\n' % (what, listenip)) debug1('%s listening on %r.\n' % (what, listenip))
class FirewallClient: class FirewallClient:
def __init__(self, port_v4, subnets_include, subnets_exclude, dnsport_v4, method): def __init__(self, port_v6, port_v4, subnets_include, subnets_exclude, dnsport_v6, dnsport_v4, method):
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
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_v4), ['--firewall', str(port_v6), str(port_v4),
str(dnsport_v4), str(dnsport_v6), str(dnsport_v4),
method]) method])
if ssyslog._p: if ssyslog._p:
argvbase += ['--syslog'] argvbase += ['--syslog']
...@@ -376,7 +390,7 @@ def _main(tcp_listener, fw, ssh_cmd, remotename, python, latency_control, ...@@ -376,7 +390,7 @@ def _main(tcp_listener, fw, ssh_cmd, remotename, python, latency_control,
mux.callback() mux.callback()
def main(listenip_v4, def main(listenip_v6, listenip_v4,
ssh_cmd, remotename, python, latency_control, dns, ssh_cmd, remotename, python, latency_control, dns,
method, seed_hosts, auto_nets, method, seed_hosts, auto_nets,
subnets_include, subnets_exclude, syslog, daemon, pidfile): subnets_include, subnets_exclude, syslog, daemon, pidfile):
...@@ -390,7 +404,7 @@ def main(listenip_v4, ...@@ -390,7 +404,7 @@ def main(listenip_v4,
return 5 return 5
debug1('Starting sshuttle proxy.\n') debug1('Starting sshuttle proxy.\n')
if listenip_v4 and listenip_v4[1]: if listenip_v6 and listenip_v6[1] and listenip_v4 and listenip_v4[1]:
# if both ports given, no need to search for a spare port # if both ports given, no need to search for a spare port
ports = [ 0, ] ports = [ 0, ]
else: else:
...@@ -399,6 +413,7 @@ def main(listenip_v4, ...@@ -399,6 +413,7 @@ def main(listenip_v4,
# search for free ports and try to bind # search for free ports and try to bind
last_e = None last_e = None
redirectport_v6 = 0
redirectport_v4 = 0 redirectport_v4 = 0
bound = False bound = False
debug2('Binding redirector:') debug2('Binding redirector:')
...@@ -407,6 +422,16 @@ def main(listenip_v4, ...@@ -407,6 +422,16 @@ def main(listenip_v4,
tcp_listener = MultiListener() tcp_listener = MultiListener()
tcp_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) tcp_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if listenip_v6 and listenip_v6[1]:
lv6 = listenip_v6
redirectport_v6 = lv6[1]
elif listenip_v6:
lv6 = (listenip_v6[0],port)
redirectport_v6 = port
else:
lv6 = None
redirectport_v6 = 0
if listenip_v4 and listenip_v4[1]: if listenip_v4 and listenip_v4[1]:
lv4 = listenip_v4 lv4 = listenip_v4
redirectport_v4 = lv4[1] redirectport_v4 = lv4[1]
...@@ -418,7 +443,7 @@ def main(listenip_v4, ...@@ -418,7 +443,7 @@ def main(listenip_v4,
redirectport_v4 = 0 redirectport_v4 = 0
try: try:
tcp_listener.bind(lv4) tcp_listener.bind(lv6, lv4)
bound = True bound = True
break break
except socket.error, e: except socket.error, e:
...@@ -443,6 +468,13 @@ def main(listenip_v4, ...@@ -443,6 +468,13 @@ def main(listenip_v4,
dns_listener = MultiListener(socket.SOCK_DGRAM) dns_listener = MultiListener(socket.SOCK_DGRAM)
dns_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) dns_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if listenip_v6:
lv6 = (listenip_v6[0],port)
dnsport_v6 = port
else:
lv6 = None
dnsport_v6 = 0
if listenip_v4: if listenip_v4:
lv4 = (listenip_v4[0],port) lv4 = (listenip_v4[0],port)
dnsport_v4 = port dnsport_v4 = port
...@@ -451,7 +483,7 @@ def main(listenip_v4, ...@@ -451,7 +483,7 @@ def main(listenip_v4,
dnsport_v4 = 0 dnsport_v4 = 0
try: try:
dns_listener.bind(lv4) dns_listener.bind(lv6, lv4)
bound = True bound = True
break break
except socket.error, e: except socket.error, e:
...@@ -465,10 +497,11 @@ def main(listenip_v4, ...@@ -465,10 +497,11 @@ def main(listenip_v4,
assert(last_e) assert(last_e)
raise last_e raise last_e
else: else:
dnsport_v6 = 0
dnsport_v4 = 0 dnsport_v4 = 0
dns_listener = None dns_listener = None
fw = FirewallClient(redirectport_v4, subnets_include, subnets_exclude, dnsport_v4, method) fw = FirewallClient(redirectport_v6, redirectport_v4, subnets_include, subnets_exclude, dnsport_v6, dnsport_v4, method)
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)
......
...@@ -15,7 +15,9 @@ def nonfatal(func, *args): ...@@ -15,7 +15,9 @@ def nonfatal(func, *args):
def ipt_chain_exists(family, table, name): def ipt_chain_exists(family, table, name):
if family == socket.AF_INET: if family == socket.AF_INET6:
cmd = 'ip6tables'
elif family == socket.AF_INET:
cmd = 'iptables' cmd = 'iptables'
else: else:
raise Exception('Unsupported family "%s"'%family_to_string(family)) raise Exception('Unsupported family "%s"'%family_to_string(family))
...@@ -30,7 +32,9 @@ def ipt_chain_exists(family, table, name): ...@@ -30,7 +32,9 @@ def ipt_chain_exists(family, table, name):
def _ipt(family, table, *args): def _ipt(family, table, *args):
if family == socket.AF_INET: if family == socket.AF_INET6:
argv = ['ip6tables', '-t', table] + list(args)
elif family == socket.AF_INET:
argv = ['iptables', '-t', table] + list(args) argv = ['iptables', '-t', table] + list(args)
else: else:
raise Exception('Unsupported family "%s"'%family_to_string(family)) raise Exception('Unsupported family "%s"'%family_to_string(family))
...@@ -110,7 +114,7 @@ def do_iptables_nat(port, dnsport, family, subnets): ...@@ -110,7 +114,7 @@ def do_iptables_nat(port, dnsport, family, subnets):
if dnsport: if dnsport:
nslist = resolvconf_nameservers() nslist = resolvconf_nameservers()
for ip in 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,
'-p', 'udp', '-p', 'udp',
...@@ -119,7 +123,7 @@ def do_iptables_nat(port, dnsport, family, subnets): ...@@ -119,7 +123,7 @@ def do_iptables_nat(port, dnsport, family, subnets):
def do_iptables_tproxy(port, dnsport, family, subnets): def do_iptables_tproxy(port, dnsport, family, subnets):
if family not in [socket.AF_INET]: if family not in [socket.AF_INET, socket.AF_INET6]:
raise Exception('Address family "%s" unsupported by tproxy method'%family_to_string(family)) raise Exception('Address family "%s" unsupported by tproxy method'%family_to_string(family))
table = "mangle" table = "mangle"
...@@ -367,7 +371,7 @@ def do_ipfw(port, dnsport, family, subnets): ...@@ -367,7 +371,7 @@ def do_ipfw(port, dnsport, family, subnets):
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() nslist = resolvconf_nameservers()
for ip in 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,
'log', 'udp', 'log', 'udp',
...@@ -450,9 +454,13 @@ def restore_etc_hosts(port): ...@@ -450,9 +454,13 @@ def restore_etc_hosts(port):
# 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_v4, dnsport_v4, method, syslog): def main(port_v6, port_v4, dnsport_v6, dnsport_v4, method, syslog):
assert(port_v4 > 0) assert(port_v6 >= 0)
assert(port_v6 <= 65535)
assert(port_v4 >= 0)
assert(port_v4 <= 65535) assert(port_v4 <= 65535)
assert(dnsport_v6 >= 0)
assert(dnsport_v6 <= 65535)
assert(dnsport_v4 >= 0) assert(dnsport_v4 >= 0)
assert(dnsport_v4 <= 65535) assert(dnsport_v4 <= 65535)
...@@ -519,6 +527,12 @@ def main(port_v4, dnsport_v4, method, syslog): ...@@ -519,6 +527,12 @@ def main(port_v4, dnsport_v4, method, syslog):
if line: if line:
debug1('firewall manager: starting transproxy.\n') debug1('firewall manager: starting transproxy.\n')
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)
elif len(subnets_v6) > 0:
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(port_v4, dnsport_v4, socket.AF_INET, subnets_v4) do_wait = do_it(port_v4, dnsport_v4, socket.AF_INET, subnets_v4)
...@@ -543,7 +557,7 @@ def main(port_v4, dnsport_v4, method, syslog): ...@@ -543,7 +557,7 @@ def main(port_v4, dnsport_v4, method, syslog):
if line.startswith('HOST '): if line.startswith('HOST '):
(name,ip) = line[5:].strip().split(',', 1) (name,ip) = line[5:].strip().split(',', 1)
hostmap[name] = ip hostmap[name] = ip
rewrite_etc_hosts(port_v4) rewrite_etc_hosts(port_v6 or port_v4)
elif line: elif line:
raise Fatal('expected EOF, got %r' % line) raise Fatal('expected EOF, got %r' % line)
else: else:
...@@ -555,4 +569,4 @@ def main(port_v4, dnsport_v4, method, syslog): ...@@ -555,4 +569,4 @@ def main(port_v4, dnsport_v4, method, syslog):
pass pass
if port_v4: if port_v4:
do_it(port_v4, 0, socket.AF_INET, []) do_it(port_v4, 0, socket.AF_INET, [])
restore_etc_hosts(port_v4) restore_etc_hosts(port_v6 or port_v4)
...@@ -42,7 +42,10 @@ def resolvconf_nameservers(): ...@@ -42,7 +42,10 @@ 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':
l.append(words[1]) if ':' in words[1]:
l.append((socket.AF_INET6,words[1]))
else:
l.append((socket.AF_INET,words[1]))
return l return l
...@@ -55,7 +58,7 @@ def resolvconf_random_nameserver(): ...@@ -55,7 +58,7 @@ def resolvconf_random_nameserver():
random.shuffle(l) random.shuffle(l)
return l[0] return l[0]
else: else:
return '127.0.0.1' return (socket.AF_INET,'127.0.0.1')
def islocal(ip,family): def islocal(ip,family):
......
...@@ -22,12 +22,31 @@ def parse_subnet4(s): ...@@ -22,12 +22,31 @@ def parse_subnet4(s):
return(socket.AF_INET, '%d.%d.%d.%d' % (a,b,c,d), width) return(socket.AF_INET, '%d.%d.%d.%d' % (a,b,c,d), width)
# 1:2::3/64 or just 1:2::3
def parse_subnet6(s):
m = re.match(r'(?:([a-fA-F\d:]+))?(?:/(\d+))?$', s)
if not m:
raise Fatal('%r is not a valid IP subnet format' % s)
(net,width) = m.groups()
if width == None:
width = 128
else:
width = int(width)
if width > 128:
raise Fatal('*/%d is greater than the maximum of 128' % width)
return(socket.AF_INET6, net, width)
# list of: # list of:
# 1.2.3.4/5 or just 1.2.3.4 # 1.2.3.4/5 or just 1.2.3.4
# 1:2::3/64 or just 1:2::3
def parse_subnets(subnets_str): def parse_subnets(subnets_str):
subnets = [] subnets = []
for s in subnets_str: for s in subnets_str:
subnet = parse_subnet4(s) if ':' in s:
subnet = parse_subnet6(s)
else:
subnet = parse_subnet4(s)
subnets.append(subnet) subnets.append(subnet)
return subnets return subnets
...@@ -50,13 +69,24 @@ def parse_ipport4(s): ...@@ -50,13 +69,24 @@ def parse_ipport4(s):
return ('%d.%d.%d.%d' % (a,b,c,d), port) return ('%d.%d.%d.%d' % (a,b,c,d), port)
# [1:2::3]:456 or [1:2::3] or 456
def parse_ipport6(s):
s = str(s)
m = re.match(r'(?:\[([^]]*)])?(?::)?(?:(\d+))?$', s)
if not m:
raise Fatal('%s is not a valid IP:port format' % s)
(ip,port) = m.groups()
(ip,port) = (ip or '::', int(port or 0))
return (ip, port)
optspec = """ optspec = """
sshuttle [-l [ip:]port] [-r [username@]sshserver[:port]] <subnets...> sshuttle [-l [ip:]port] [-r [username@]sshserver[:port]] <subnets...>
sshuttle --server sshuttle --server
sshuttle --firewall <port> <subnets...> sshuttle --firewall <port> <subnets...>
sshuttle --hostwatch sshuttle --hostwatch
-- --
l,listen= transproxy to this ip address and port number [127.0.0.1:0] 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
...@@ -93,10 +123,11 @@ try: ...@@ -93,10 +123,11 @@ try:
server.latency_control = opt.latency_control server.latency_control = opt.latency_control
sys.exit(server.main()) sys.exit(server.main())
elif opt.firewall: elif opt.firewall:
if len(extra) != 3: if len(extra) != 5:
o.fatal('exactly three arguments expected') o.fatal('exactly five arguments expected')
sys.exit(firewall.main(int(extra[0]), int(extra[1]), sys.exit(firewall.main(int(extra[0]), int(extra[1]),
extra[2], opt.syslog)) int(extra[2]), int(extra[3]),
extra[4], opt.syslog))
elif opt.hostwatch: elif opt.hostwatch:
sys.exit(hostwatch.hw_main(extra)) sys.exit(hostwatch.hw_main(extra))
else: else:
...@@ -124,8 +155,22 @@ try: ...@@ -124,8 +155,22 @@ try:
method = opt.method method = opt.method
else: else:
o.fatal("method %s not supported"%opt.method) o.fatal("method %s not supported"%opt.method)
ipport_v4 = parse_ipport4(opt.listen or '0.0.0.0:0') if not opt.listen:
sys.exit(client.main(ipport_v4, if opt.method == "tproxy":
ipport_v6 = parse_ipport6('[::1]:0')
else:
ipport_v6 = None
ipport_v4 = parse_ipport4('127.0.0.1:0')
else:
ipport_v6 = None
ipport_v4 = None
list = opt.listen.split(",")
for ip in list:
if '[' in ip and ']' in ip and opt.method == "tproxy":
ipport_v6 = parse_ipport6(ip)
else:
ipport_v4 = parse_ipport4(ip)
sys.exit(client.main(ipport_v6, ipport_v4,
opt.ssh_cmd, opt.ssh_cmd,
remotename, remotename,
opt.python, opt.python,
......
...@@ -108,6 +108,7 @@ class Hostwatch: ...@@ -108,6 +108,7 @@ class Hostwatch:
class DnsProxy(Handler): class DnsProxy(Handler):
def __init__(self, mux, chan, request): def __init__(self, mux, chan, request):
# FIXME! IPv4 specific
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
Handler.__init__(self, [sock]) Handler.__init__(self, [sock])
self.timeout = time.time()+30 self.timeout = time.time()+30
...@@ -117,6 +118,7 @@ class DnsProxy(Handler): ...@@ -117,6 +118,7 @@ class DnsProxy(Handler):
self.peer = None self.peer = None
self.request = request self.request = request
self.sock = sock self.sock = sock
# FIXME! IPv4 specific
self.sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42) self.sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
self.try_send() self.try_send()
...@@ -124,7 +126,8 @@ class DnsProxy(Handler): ...@@ -124,7 +126,8 @@ class DnsProxy(Handler):
if self.tries >= 3: if self.tries >= 3:
return return
self.tries += 1 self.tries += 1
self.peer = resolvconf_random_nameserver() # FIXME! Support IPv6 nameservers
self.peer = resolvconf_random_nameserver()[1]
self.sock.connect((self.peer, 53)) self.sock.connect((self.peer, 53))
debug2('DNS: sending to %r\n' % self.peer) debug2('DNS: sending to %r\n' % self.peer)
try: try:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment