mirror of
https://github.com/sshuttle/sshuttle.git
synced 2024-11-22 07:53:43 +01:00
Merge pull request #550 from skuhl/nft-ipv6
IPv6 support in nft method.
This commit is contained in:
commit
5c8c707208
@ -37,14 +37,18 @@ Options
|
|||||||
netmask), and 0/0 ('just route everything through the
|
netmask), and 0/0 ('just route everything through the
|
||||||
VPN'). Any of the previous examples are also valid if you append
|
VPN'). Any of the previous examples are also valid if you append
|
||||||
a port or a port range, so 1.2.3.4:8000 will only tunnel traffic
|
a port or a port range, so 1.2.3.4:8000 will only tunnel traffic
|
||||||
that has as the destination port 8000 of 1.2.3.4 and
|
that has as the destination port 8000 of 1.2.3.4 and
|
||||||
1.2.3.0/24:8000-9000 will tunnel traffic going to any port between
|
1.2.3.0/24:8000-9000 will tunnel traffic going to any port between
|
||||||
8000 and 9000 (inclusive) for all IPs in the 1.2.3.0/24 subnet.
|
8000 and 9000 (inclusive) for all IPs in the 1.2.3.0/24 subnet.
|
||||||
It is also possible to use a name in which case the first IP it resolves
|
A hostname can be provided instead of an IP address. If the
|
||||||
to during startup will be routed over the VPN. Valid examples are
|
hostname resolves to multiple IPs, all of the IPs are included.
|
||||||
example.com, example.com:8000 and example.com:8000-9000.
|
If a width is provided with a hostname that the width is applied
|
||||||
|
to all of the hostnames IPs (if they are all either IPv4 or IPv6).
|
||||||
|
Widths cannot be supplied to hostnames that resolve to both IPv4
|
||||||
|
and IPv6. Valid examples are example.com, example.com:8000,
|
||||||
|
example.com/24, example.com/24:8000 and example.com:8000-9000.
|
||||||
|
|
||||||
.. option:: --method <auto|nat|nft|tproxy|pf>
|
.. option:: --method <auto|nat|nft|tproxy|pf|ipfw>
|
||||||
|
|
||||||
Which firewall method should sshuttle use? For auto, sshuttle attempts to
|
Which firewall method should sshuttle use? For auto, sshuttle attempts to
|
||||||
guess the appropriate method depending on what it can find in PATH. The
|
guess the appropriate method depending on what it can find in PATH. The
|
||||||
@ -64,9 +68,9 @@ Options
|
|||||||
You can use any name resolving to an IP address of the machine running
|
You can use any name resolving to an IP address of the machine running
|
||||||
:program:`sshuttle`, e.g. ``--listen localhost``.
|
:program:`sshuttle`, e.g. ``--listen localhost``.
|
||||||
|
|
||||||
For the tproxy and pf methods this can be an IPv6 address. Use this option
|
For the nft, tproxy and pf methods this can be an IPv6 address. Use
|
||||||
with comma separated values if required, to provide both IPv4 and IPv6
|
this option with comma separated values if required, to provide both
|
||||||
addresses, e.g. ``--listen 127.0.0.1:0,[::1]:0``.
|
IPv4 and IPv6 addresses, e.g. ``--listen 127.0.0.1:0,[::1]:0``.
|
||||||
|
|
||||||
.. option:: -H, --auto-hosts
|
.. option:: -H, --auto-hosts
|
||||||
|
|
||||||
@ -92,6 +96,10 @@ Options
|
|||||||
are taken automatically from the server's routing
|
are taken automatically from the server's routing
|
||||||
table.
|
table.
|
||||||
|
|
||||||
|
This feature does not detect IPv6 routes. Specify IPv6 subnets
|
||||||
|
manually. For example, specify the ``::/0`` subnet on the command
|
||||||
|
line to route all IPv6 traffic.
|
||||||
|
|
||||||
.. option:: --dns
|
.. option:: --dns
|
||||||
|
|
||||||
Capture local DNS requests and forward to the remote DNS
|
Capture local DNS requests and forward to the remote DNS
|
||||||
@ -122,9 +130,9 @@ Options
|
|||||||
|
|
||||||
.. option:: --python
|
.. option:: --python
|
||||||
|
|
||||||
Specify the name/path of the remote python interpreter.
|
Specify the name/path of the remote python interpreter. The
|
||||||
The default is just ``python``, which means to use the
|
default is to use ``python3`` (or ``python``, if ``python3``
|
||||||
default python interpreter on the remote system's PATH.
|
fails) in the remote system's PATH.
|
||||||
|
|
||||||
.. option:: -r <[username@]sshserver[:port]>, --remote=<[username@]sshserver[:port]>
|
.. option:: -r <[username@]sshserver[:port]>, --remote=<[username@]sshserver[:port]>
|
||||||
|
|
||||||
@ -221,7 +229,8 @@ Options
|
|||||||
|
|
||||||
.. option:: --disable-ipv6
|
.. option:: --disable-ipv6
|
||||||
|
|
||||||
If using tproxy or pf methods, this will disable IPv6 support.
|
Disable IPv6 support for methods that support it (nft, tproxy, and
|
||||||
|
pf).
|
||||||
|
|
||||||
.. option:: --firewall
|
.. option:: --firewall
|
||||||
|
|
||||||
|
@ -20,6 +20,18 @@ Requires:
|
|||||||
|
|
||||||
* iptables DNAT, REDIRECT, and ttl modules.
|
* iptables DNAT, REDIRECT, and ttl modules.
|
||||||
|
|
||||||
|
Linux with nft method
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Supports
|
||||||
|
|
||||||
|
* IPv4 TCP
|
||||||
|
* IPv4 DNS
|
||||||
|
* IPv6 TCP
|
||||||
|
* IPv6 DNS
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* nftables
|
||||||
|
|
||||||
Linux with TPROXY method
|
Linux with TPROXY method
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -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
|
||||||
SOCKADDR_MIN = 16
|
|
||||||
sockaddr_in = sock.getsockopt(socket.SOL_IP,
|
if family == socket.AF_INET:
|
||||||
SO_ORIGINAL_DST, SOCKADDR_MIN)
|
SOCKADDR_MIN = 16
|
||||||
(proto, port, a, b, c, d) = struct.unpack('!HHBBBB', sockaddr_in[:8])
|
sockaddr_in = sock.getsockopt(socket.SOL_IP,
|
||||||
# FIXME: decoding is IPv4 only.
|
SO_ORIGINAL_DST, SOCKADDR_MIN)
|
||||||
assert(socket.htons(proto) == socket.AF_INET)
|
port, raw_ip = struct.unpack_from('!2xH4s', sockaddr_in[:8])
|
||||||
ip = '%d.%d.%d.%d' % (a, b, c, d)
|
ip = str(ipaddress.IPv4Address(raw_ip))
|
||||||
return (ip, port)
|
elif family == socket.AF_INET6:
|
||||||
|
sockaddr_in = sock.getsockopt(41, SO_ORIGINAL_DST, 64)
|
||||||
|
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):
|
|||||||
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):
|
|||||||
# 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.
|
||||||
_nft('add rule', chain, 'ip ttl == 63 return')
|
if family == socket.AF_INET:
|
||||||
|
_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):
|
|||||||
# 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):
|
||||||
tcp_ports = ('ip', 'protocol', 'tcp')
|
if family == socket.AF_INET:
|
||||||
|
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):
|
|||||||
tcp_ports = tcp_ports + ('tcp', 'dport', '%d' % (fport))
|
tcp_ports = tcp_ports + ('tcp', 'dport', '%d' % (fport))
|
||||||
|
|
||||||
if sexclude:
|
if sexclude:
|
||||||
_nft('add rule', chain, *(tcp_ports + (
|
if family == socket.AF_INET:
|
||||||
'ip daddr %s/%s' % (snet, swidth), 'return')))
|
_nft('add rule', chain, *(tcp_ports + (
|
||||||
|
'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:
|
||||||
_nft('add rule', chain, *(tcp_ports + (
|
if family == socket.AF_INET:
|
||||||
'ip daddr %s/%s' % (snet, swidth),
|
_nft('add rule', chain, *(tcp_ports + (
|
||||||
('redirect to :' + str(port)))))
|
'ip daddr %s/%s' % (snet, swidth),
|
||||||
|
('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():
|
|||||||
|
|
||||||
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()
|
||||||
|
Loading…
Reference in New Issue
Block a user