Commit 6d86e44f authored by Scott Kuhl's avatar Scott Kuhl

IPv6 support in nft method.

This works for me but needs testing by others. Remember to specify a
::0/0 subnet or similar to route IPv6 through sshuttle.

I'm adding this to nft before nat since it is not sshuttle's default
method on Linux. Documentation updates may be required too.

This patch uses the ipaddress module, but that appears to be included
since Python 3.3.
parent ebf87d8f
...@@ -3,24 +3,34 @@ import importlib ...@@ -3,24 +3,34 @@ import importlib
import socket import socket
import struct import struct
import errno import errno
import ipaddress
from sshuttle.helpers import Fatal, debug3 from sshuttle.helpers import Fatal, debug3
def original_dst(sock): def original_dst(sock):
ip = "0.0.0.0"
port = -1
try: try:
family = sock.family
SO_ORIGINAL_DST = 80 SO_ORIGINAL_DST = 80
if family == socket.AF_INET:
SOCKADDR_MIN = 16 SOCKADDR_MIN = 16
sockaddr_in = sock.getsockopt(socket.SOL_IP, sockaddr_in = sock.getsockopt(socket.SOL_IP,
SO_ORIGINAL_DST, SOCKADDR_MIN) SO_ORIGINAL_DST, SOCKADDR_MIN)
(proto, port, a, b, c, d) = struct.unpack('!HHBBBB', sockaddr_in[:8]) port, raw_ip = struct.unpack_from('!2xH4s', sockaddr_in[:8])
# FIXME: decoding is IPv4 only. ip = str(ipaddress.IPv4Address(raw_ip))
assert(socket.htons(proto) == socket.AF_INET) elif family == socket.AF_INET6:
ip = '%d.%d.%d.%d' % (a, b, c, d) sockaddr_in = sock.getsockopt(41, SO_ORIGINAL_DST, 64)
return (ip, port) port, raw_ip = struct.unpack_from("!2xH4x16s", sockaddr_in)
ip = str(ipaddress.IPv6Address(raw_ip))
else:
raise Fatal("fw: Unknown family type.")
except socket.error as e: except socket.error as e:
if e.args[0] == errno.ENOPROTOOPT: if e.args[0] == errno.ENOPROTOOPT:
return sock.getsockname() return sock.getsockname()
raise raise
return (ip, port)
class Features(object): class Features(object):
......
...@@ -16,7 +16,10 @@ class Method(BaseMethod): ...@@ -16,7 +16,10 @@ class Method(BaseMethod):
if udp: if udp:
raise Exception("UDP not supported by nft") raise Exception("UDP not supported by nft")
table = 'sshuttle-%s' % port if family == socket.AF_INET:
table = 'sshuttle-ipv4-%s' % port
if family == socket.AF_INET6:
table = 'sshuttle-ipv6-%s' % port
def _nft(action, *args): def _nft(action, *args):
return nft(family, table, action, *args) return nft(family, table, action, *args)
...@@ -37,7 +40,10 @@ class Method(BaseMethod): ...@@ -37,7 +40,10 @@ class Method(BaseMethod):
# This TTL hack allows the client and server to run on the # This TTL hack allows the client and server to run on the
# same host. The connections the sshuttle server makes will # same host. The connections the sshuttle server makes will
# have TTL set to 63. # have TTL set to 63.
if family == socket.AF_INET:
_nft('add rule', chain, 'ip ttl == 63 return') _nft('add rule', chain, 'ip ttl == 63 return')
elif family == socket.AF_INET6:
_nft('add rule', chain, 'ip6 hoplimit == 63 return')
# Redirect DNS traffic as requested. This includes routing traffic # Redirect DNS traffic as requested. This includes routing traffic
# to localhost DNS servers through sshuttle. # to localhost DNS servers through sshuttle.
...@@ -57,7 +63,11 @@ class Method(BaseMethod): ...@@ -57,7 +63,11 @@ class Method(BaseMethod):
# create new subnet entries. # create new subnet entries.
for _, swidth, sexclude, snet, fport, lport \ for _, swidth, sexclude, snet, fport, lport \
in sorted(subnets, key=subnet_weight, reverse=True): in sorted(subnets, key=subnet_weight, reverse=True):
if family == socket.AF_INET:
tcp_ports = ('ip', 'protocol', 'tcp') tcp_ports = ('ip', 'protocol', 'tcp')
elif family == socket.AF_INET6:
tcp_ports = ('ip6', 'nexthdr', 'tcp')
if fport and fport != lport: if fport and fport != lport:
tcp_ports = \ tcp_ports = \
tcp_ports + \ tcp_ports + \
...@@ -66,21 +76,38 @@ class Method(BaseMethod): ...@@ -66,21 +76,38 @@ class Method(BaseMethod):
tcp_ports = tcp_ports + ('tcp', 'dport', '%d' % (fport)) tcp_ports = tcp_ports + ('tcp', 'dport', '%d' % (fport))
if sexclude: if sexclude:
if family == socket.AF_INET:
_nft('add rule', chain, *(tcp_ports + ( _nft('add rule', chain, *(tcp_ports + (
'ip daddr %s/%s' % (snet, swidth), 'return'))) 'ip daddr %s/%s' % (snet, swidth), 'return')))
elif family == socket.AF_INET6:
_nft('add rule', chain, *(tcp_ports + (
'ip6 daddr %s/%s' % (snet, swidth), 'return')))
else: else:
if family == socket.AF_INET:
_nft('add rule', chain, *(tcp_ports + ( _nft('add rule', chain, *(tcp_ports + (
'ip daddr %s/%s' % (snet, swidth), 'ip daddr %s/%s' % (snet, swidth),
('redirect to :' + str(port))))) ('redirect to :' + str(port)))))
elif family == socket.AF_INET6:
_nft('add rule', chain, *(tcp_ports + (
'ip6 daddr %s/%s' % (snet, swidth),
('redirect to :' + str(port)))))
def restore_firewall(self, port, family, udp, user): def restore_firewall(self, port, family, udp, user):
if udp: if udp:
raise Exception("UDP not supported by nft method_name") raise Exception("UDP not supported by nft method_name")
table = 'sshuttle-%s' % port if family == socket.AF_INET:
table = 'sshuttle-ipv4-%s' % port
if family == socket.AF_INET6:
table = 'sshuttle-ipv6-%s' % port
def _nft(action, *args): def _nft(action, *args):
return nft(family, table, action, *args) return nft(family, table, action, *args)
# basic cleanup/setup of chains # basic cleanup/setup of chains
nonfatal(_nft, 'delete table', '') nonfatal(_nft, 'delete table', '')
def get_supported_features(self):
result = super(Method, self).get_supported_features()
result.ipv6 = True
return result
...@@ -18,12 +18,22 @@ def test_get_supported_features(): ...@@ -18,12 +18,22 @@ def test_get_supported_features():
def test_get_tcp_dstip(): def test_get_tcp_dstip():
sock = Mock() sock = Mock()
sock.family = AF_INET
sock.getsockopt.return_value = struct.pack( sock.getsockopt.return_value = struct.pack(
'!HHBBBB', socket.ntohs(AF_INET), 1024, 127, 0, 0, 1) '!HHBBBB', socket.ntohs(AF_INET), 1024, 127, 0, 0, 1)
method = get_method('nat') method = get_method('nat')
assert method.get_tcp_dstip(sock) == ('127.0.0.1', 1024) assert method.get_tcp_dstip(sock) == ('127.0.0.1', 1024)
assert sock.mock_calls == [call.getsockopt(0, 80, 16)] assert sock.mock_calls == [call.getsockopt(0, 80, 16)]
sock = Mock()
sock.family = AF_INET6
sock.getsockopt.return_value = struct.pack(
'!HH4xBBBBBBBBBBBBBBBB', socket.ntohs(AF_INET6),
1024, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)
method = get_method('nft')
assert method.get_tcp_dstip(sock) == ('::1', 1024)
assert sock.mock_calls == [call.getsockopt(41, 80, 64)]
def test_recv_udp(): def test_recv_udp():
sock = Mock() sock = Mock()
......
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