Skip to content

Commit a3cbf08

Browse files
authored
Merge pull request sshuttle#646 from skuhl/nat-ipv6
Add IPv6 support to nat (iptables) method.
2 parents 58c264f + 3f20109 commit a3cbf08

File tree

3 files changed

+80
-23
lines changed

3 files changed

+80
-23
lines changed

docs/requirements.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ Supports:
1515

1616
* IPv4 TCP
1717
* IPv4 DNS
18+
* IPv6 TCP
19+
* IPv6 DNS
1820

1921
Requires:
2022

21-
* iptables DNAT, REDIRECT, and ttl modules.
23+
* iptables DNAT, REDIRECT, and ttl modules. ip6tables for IPv6.
2224

2325
Linux with nft method
2426
~~~~~~~~~~~~~~~~~~~~~

sshuttle/methods/nat.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,12 @@ class Method(BaseMethod):
1414
# "-A OUTPUT").
1515
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
1616
user, ttl, tmark):
17-
# only ipv4 supported with NAT
18-
if family != socket.AF_INET:
17+
if family != socket.AF_INET and family != socket.AF_INET6:
1918
raise Exception(
2019
'Address family "%s" unsupported by nat method_name'
2120
% family_to_string(family))
2221
if udp:
2322
raise Exception("UDP not supported by nat method_name")
24-
2523
table = "nat"
2624

2725
def _ipt(*args):
@@ -53,13 +51,18 @@ def _ipm(*args):
5351
# This TTL hack allows the client and server to run on the
5452
# same host. The connections the sshuttle server makes will
5553
# have TTL set to 63.
56-
_ipt_ttl('-A', chain, '-j', 'RETURN', '-m', 'ttl', '--ttl', '%s' % ttl)
54+
if family == socket.AF_INET:
55+
_ipt_ttl('-A', chain, '-j', 'RETURN', '-m', 'ttl', '--ttl',
56+
'%s' % ttl)
57+
else: # ipv6, ttl is renamed to 'hop limit'
58+
_ipt_ttl('-A', chain, '-j', 'RETURN', '-m', 'hl', '--hl-eq',
59+
'%s' % ttl)
5760

5861
# Redirect DNS traffic as requested. This includes routing traffic
5962
# to localhost DNS servers through sshuttle.
6063
for _, ip in [i for i in nslist if i[0] == family]:
6164
_ipt('-A', chain, '-j', 'REDIRECT',
62-
'--dest', '%s/32' % ip,
65+
'--dest', '%s' % ip,
6366
'-p', 'udp',
6467
'--dport', '53',
6568
'--to-ports', str(dnsport))
@@ -87,7 +90,7 @@ def _ipm(*args):
8790

8891
def restore_firewall(self, port, family, udp, user):
8992
# only ipv4 supported with NAT
90-
if family != socket.AF_INET:
93+
if family != socket.AF_INET and family != socket.AF_INET6:
9194
raise Exception(
9295
'Address family "%s" unsupported by nat method_name'
9396
% family_to_string(family))
@@ -123,6 +126,7 @@ def _ipm(*args):
123126
def get_supported_features(self):
124127
result = super(Method, self).get_supported_features()
125128
result.user = True
129+
result.ipv6 = True
126130
return result
127131

128132
def is_supported(self):

tests/client/test_methods_nat.py

Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
def test_get_supported_features():
1212
method = get_method('nat')
1313
features = method.get_supported_features()
14-
assert not features.ipv6
14+
assert features.ipv6
1515
assert not features.udp
1616
assert features.dns
1717

@@ -92,18 +92,51 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
9292
method = get_method('nat')
9393
assert method.name == 'nat'
9494

95-
with pytest.raises(Exception) as excinfo:
96-
method.setup_firewall(
97-
1024, 1026,
98-
[(AF_INET6, u'2404:6800:4004:80c::33')],
99-
AF_INET6,
100-
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 0, 0),
101-
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
102-
True,
103-
None,
104-
63, '0x01')
105-
assert str(excinfo.value) \
106-
== 'Address family "AF_INET6" unsupported by nat method_name'
95+
assert mock_ipt_chain_exists.mock_calls == []
96+
assert mock_ipt_ttl.mock_calls == []
97+
assert mock_ipt.mock_calls == []
98+
method.setup_firewall(
99+
1024, 1026,
100+
[(AF_INET6, u'2404:6800:4004:80c::33')],
101+
AF_INET6,
102+
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 0, 0),
103+
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
104+
False,
105+
None,
106+
63, '0x01')
107+
108+
assert mock_ipt_chain_exists.mock_calls == [
109+
call(AF_INET6, 'nat', 'sshuttle-1024')
110+
]
111+
assert mock_ipt_ttl.mock_calls == [
112+
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'RETURN',
113+
'-m', 'hl', '--hl-eq', '63')
114+
]
115+
assert mock_ipt.mock_calls == [
116+
call(AF_INET6, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1024'),
117+
call(AF_INET6, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1024'),
118+
call(AF_INET6, 'nat', '-F', 'sshuttle-1024'),
119+
call(AF_INET6, 'nat', '-X', 'sshuttle-1024'),
120+
call(AF_INET6, 'nat', '-N', 'sshuttle-1024'),
121+
call(AF_INET6, 'nat', '-F', 'sshuttle-1024'),
122+
call(AF_INET6, 'nat', '-I', 'OUTPUT', '1', '-j', 'sshuttle-1024'),
123+
call(AF_INET6, 'nat', '-I', 'PREROUTING', '1', '-j', 'sshuttle-1024'),
124+
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'REDIRECT',
125+
'--dest', u'2404:6800:4004:80c::33', '-p', 'udp',
126+
'--dport', '53', '--to-ports', '1026'),
127+
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'RETURN',
128+
'-m', 'addrtype', '--dst-type', 'LOCAL'),
129+
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'RETURN',
130+
'--dest', u'2404:6800:4004:80c::101f/128', '-p', 'tcp',
131+
'--dport', '80:80'),
132+
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'REDIRECT',
133+
'--dest', u'2404:6800:4004:80c::/64', '-p', 'tcp',
134+
'--to-ports', '1024')
135+
]
136+
mock_ipt_chain_exists.reset_mock()
137+
mock_ipt_ttl.reset_mock()
138+
mock_ipt.reset_mock()
139+
107140
assert mock_ipt_chain_exists.mock_calls == []
108141
assert mock_ipt_ttl.mock_calls == []
109142
assert mock_ipt.mock_calls == []
@@ -149,7 +182,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
149182
call(AF_INET, 'nat', '-I', 'OUTPUT', '1', '-j', 'sshuttle-1025'),
150183
call(AF_INET, 'nat', '-I', 'PREROUTING', '1', '-j', 'sshuttle-1025'),
151184
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
152-
'--dest', u'1.2.3.33/32', '-p', 'udp',
185+
'--dest', u'1.2.3.33', '-p', 'udp',
153186
'--dport', '53', '--to-ports', '1027'),
154187
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
155188
'-m', 'addrtype', '--dst-type', 'LOCAL'),
@@ -169,11 +202,29 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
169202
]
170203
assert mock_ipt_ttl.mock_calls == []
171204
assert mock_ipt.mock_calls == [
172-
call(AF_INET, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
173-
call(AF_INET, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'),
205+
call(AF_INET, 'nat', '-D', 'OUTPUT', '-j',
206+
'sshuttle-1025'),
207+
call(AF_INET, 'nat', '-D', 'PREROUTING', '-j',
208+
'sshuttle-1025'),
174209
call(AF_INET, 'nat', '-F', 'sshuttle-1025'),
175210
call(AF_INET, 'nat', '-X', 'sshuttle-1025')
176211
]
177212
mock_ipt_chain_exists.reset_mock()
178213
mock_ipt_ttl.reset_mock()
179214
mock_ipt.reset_mock()
215+
216+
method.restore_firewall(1025, AF_INET6, False, None)
217+
assert mock_ipt_chain_exists.mock_calls == [
218+
call(AF_INET6, 'nat', 'sshuttle-1025')
219+
]
220+
assert mock_ipt_ttl.mock_calls == []
221+
assert mock_ipt.mock_calls == [
222+
call(AF_INET6, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
223+
call(AF_INET6, 'nat', '-D', 'PREROUTING', '-j',
224+
'sshuttle-1025'),
225+
call(AF_INET6, 'nat', '-F', 'sshuttle-1025'),
226+
call(AF_INET6, 'nat', '-X', 'sshuttle-1025')
227+
]
228+
mock_ipt_chain_exists.reset_mock()
229+
mock_ipt_ttl.reset_mock()
230+
mock_ipt.reset_mock()

0 commit comments

Comments
 (0)