Add IPv6 support to nat (iptables) method.

Adding IPv6 support to the nat method is straightforward after the
previous work to add IPv6 support for nft.
This commit is contained in:
Scott Kuhl 2021-05-28 17:55:17 -04:00
parent bc54ffe398
commit c026a92cad
3 changed files with 80 additions and 23 deletions

View File

@ -15,10 +15,12 @@ Supports:
* IPv4 TCP * IPv4 TCP
* IPv4 DNS * IPv4 DNS
* IPv6 TCP
* IPv6 DNS
Requires: Requires:
* iptables DNAT, REDIRECT, and ttl modules. * iptables DNAT, REDIRECT, and ttl modules. ip6tables for IPv6.
Linux with nft method Linux with nft method
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~

View File

@ -14,14 +14,12 @@ class Method(BaseMethod):
# "-A OUTPUT"). # "-A OUTPUT").
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
user, ttl): user, ttl):
# only ipv4 supported with NAT if family != socket.AF_INET and family != socket.AF_INET6:
if family != socket.AF_INET:
raise Exception( raise Exception(
'Address family "%s" unsupported by nat method_name' 'Address family "%s" unsupported by nat method_name'
% family_to_string(family)) % family_to_string(family))
if udp: if udp:
raise Exception("UDP not supported by nat method_name") raise Exception("UDP not supported by nat method_name")
table = "nat" table = "nat"
def _ipt(*args): def _ipt(*args):
@ -53,13 +51,18 @@ 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.
_ipt_ttl('-A', chain, '-j', 'RETURN', '-m', 'ttl', '--ttl', '%s' % ttl) if family == socket.AF_INET:
_ipt_ttl('-A', chain, '-j', 'RETURN', '-m', 'ttl', '--ttl',
'%s' % ttl)
else: # ipv6, ttl is renamed to 'hop limit'
_ipt_ttl('-A', chain, '-j', 'RETURN', '-m', 'hl', '--hl-eq',
'%s' % ttl)
# 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.
for _, ip in [i for i in nslist if i[0] == family]: for _, ip in [i for i in nslist if i[0] == family]:
_ipt('-A', chain, '-j', 'REDIRECT', _ipt('-A', chain, '-j', 'REDIRECT',
'--dest', '%s/32' % ip, '--dest', '%s' % ip,
'-p', 'udp', '-p', 'udp',
'--dport', '53', '--dport', '53',
'--to-ports', str(dnsport)) '--to-ports', str(dnsport))
@ -87,7 +90,7 @@ class Method(BaseMethod):
def restore_firewall(self, port, family, udp, user): def restore_firewall(self, port, family, udp, user):
# only ipv4 supported with NAT # only ipv4 supported with NAT
if family != socket.AF_INET: if family != socket.AF_INET and family != socket.AF_INET6:
raise Exception( raise Exception(
'Address family "%s" unsupported by nat method_name' 'Address family "%s" unsupported by nat method_name'
% family_to_string(family)) % family_to_string(family))
@ -123,6 +126,7 @@ class Method(BaseMethod):
def get_supported_features(self): def get_supported_features(self):
result = super(Method, self).get_supported_features() result = super(Method, self).get_supported_features()
result.user = True result.user = True
result.ipv6 = True
return result return result
def is_supported(self): def is_supported(self):

View File

@ -11,7 +11,7 @@ from sshuttle.methods import get_method
def test_get_supported_features(): def test_get_supported_features():
method = get_method('nat') method = get_method('nat')
features = method.get_supported_features() features = method.get_supported_features()
assert not features.ipv6 assert features.ipv6
assert not features.udp assert not features.udp
assert features.dns assert features.dns
@ -92,18 +92,51 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
method = get_method('nat') method = get_method('nat')
assert method.name == 'nat' assert method.name == 'nat'
with pytest.raises(Exception) as excinfo: assert mock_ipt_chain_exists.mock_calls == []
method.setup_firewall( assert mock_ipt_ttl.mock_calls == []
1024, 1026, assert mock_ipt.mock_calls == []
[(AF_INET6, u'2404:6800:4004:80c::33')], method.setup_firewall(
AF_INET6, 1024, 1026,
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 0, 0), [(AF_INET6, u'2404:6800:4004:80c::33')],
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)], AF_INET6,
True, [(AF_INET6, 64, False, u'2404:6800:4004:80c::', 0, 0),
None, (AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
63) False,
assert str(excinfo.value) \ None,
== 'Address family "AF_INET6" unsupported by nat method_name' 63)
assert mock_ipt_chain_exists.mock_calls == [
call(AF_INET6, 'nat', 'sshuttle-1024')
]
assert mock_ipt_ttl.mock_calls == [
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'RETURN',
'-m', 'hl', '--hl-eq', '63')
]
assert mock_ipt.mock_calls == [
call(AF_INET6, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1024'),
call(AF_INET6, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1024'),
call(AF_INET6, 'nat', '-F', 'sshuttle-1024'),
call(AF_INET6, 'nat', '-X', 'sshuttle-1024'),
call(AF_INET6, 'nat', '-N', 'sshuttle-1024'),
call(AF_INET6, 'nat', '-F', 'sshuttle-1024'),
call(AF_INET6, 'nat', '-I', 'OUTPUT', '1', '-j', 'sshuttle-1024'),
call(AF_INET6, 'nat', '-I', 'PREROUTING', '1', '-j', 'sshuttle-1024'),
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'REDIRECT',
'--dest', u'2404:6800:4004:80c::33', '-p', 'udp',
'--dport', '53', '--to-ports', '1026'),
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'RETURN',
'-m', 'addrtype', '--dst-type', 'LOCAL'),
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'RETURN',
'--dest', u'2404:6800:4004:80c::101f/128', '-p', 'tcp',
'--dport', '80:80'),
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'REDIRECT',
'--dest', u'2404:6800:4004:80c::/64', '-p', 'tcp',
'--to-ports', '1024')
]
mock_ipt_chain_exists.reset_mock()
mock_ipt_ttl.reset_mock()
mock_ipt.reset_mock()
assert mock_ipt_chain_exists.mock_calls == [] assert mock_ipt_chain_exists.mock_calls == []
assert mock_ipt_ttl.mock_calls == [] assert mock_ipt_ttl.mock_calls == []
assert mock_ipt.mock_calls == [] assert mock_ipt.mock_calls == []
@ -149,7 +182,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
call(AF_INET, 'nat', '-I', 'OUTPUT', '1', '-j', 'sshuttle-1025'), call(AF_INET, 'nat', '-I', 'OUTPUT', '1', '-j', 'sshuttle-1025'),
call(AF_INET, 'nat', '-I', 'PREROUTING', '1', '-j', 'sshuttle-1025'), call(AF_INET, 'nat', '-I', 'PREROUTING', '1', '-j', 'sshuttle-1025'),
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT', call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
'--dest', u'1.2.3.33/32', '-p', 'udp', '--dest', u'1.2.3.33', '-p', 'udp',
'--dport', '53', '--to-ports', '1027'), '--dport', '53', '--to-ports', '1027'),
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN', call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
'-m', 'addrtype', '--dst-type', 'LOCAL'), '-m', 'addrtype', '--dst-type', 'LOCAL'),
@ -169,11 +202,29 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
] ]
assert mock_ipt_ttl.mock_calls == [] assert mock_ipt_ttl.mock_calls == []
assert mock_ipt.mock_calls == [ assert mock_ipt.mock_calls == [
call(AF_INET, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'), call(AF_INET, 'nat', '-D', 'OUTPUT', '-j',
call(AF_INET, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'), 'sshuttle-1025'),
call(AF_INET, 'nat', '-D', 'PREROUTING', '-j',
'sshuttle-1025'),
call(AF_INET, 'nat', '-F', 'sshuttle-1025'), call(AF_INET, 'nat', '-F', 'sshuttle-1025'),
call(AF_INET, 'nat', '-X', 'sshuttle-1025') call(AF_INET, 'nat', '-X', 'sshuttle-1025')
] ]
mock_ipt_chain_exists.reset_mock() mock_ipt_chain_exists.reset_mock()
mock_ipt_ttl.reset_mock() mock_ipt_ttl.reset_mock()
mock_ipt.reset_mock() mock_ipt.reset_mock()
method.restore_firewall(1025, AF_INET6, False, None)
assert mock_ipt_chain_exists.mock_calls == [
call(AF_INET6, 'nat', 'sshuttle-1025')
]
assert mock_ipt_ttl.mock_calls == []
assert mock_ipt.mock_calls == [
call(AF_INET6, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
call(AF_INET6, 'nat', '-D', 'PREROUTING', '-j',
'sshuttle-1025'),
call(AF_INET6, 'nat', '-F', 'sshuttle-1025'),
call(AF_INET6, 'nat', '-X', 'sshuttle-1025')
]
mock_ipt_chain_exists.reset_mock()
mock_ipt_ttl.reset_mock()
mock_ipt.reset_mock()