mirror of
https://github.com/sshuttle/sshuttle.git
synced 2025-05-01 22:54:47 +02:00
Merge pull request #646 from skuhl/nat-ipv6
Add IPv6 support to nat (iptables) method.
This commit is contained in:
commit
a3cbf0885f
@ -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, tmark):
|
user, ttl, tmark):
|
||||||
# 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, '0x01')
|
False,
|
||||||
assert str(excinfo.value) \
|
None,
|
||||||
== 'Address family "AF_INET6" unsupported by nat method_name'
|
63, '0x01')
|
||||||
|
|
||||||
|
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