mirror of
https://github.com/sshuttle/sshuttle.git
synced 2025-04-24 11:19:00 +02:00
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:
parent
bc54ffe398
commit
c026a92cad
@ -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
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user