mirror of
https://github.com/sshuttle/sshuttle.git
synced 2024-12-28 09:39:10 +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
|
||||
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
|
||||
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
|
||||
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
|
||||
to during startup will be routed over the VPN. Valid examples are
|
||||
example.com, example.com:8000 and example.com:8000-9000.
|
||||
A hostname can be provided instead of an IP address. If the
|
||||
hostname resolves to multiple IPs, all of the IPs are included.
|
||||
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
|
||||
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
|
||||
:program:`sshuttle`, e.g. ``--listen localhost``.
|
||||
|
||||
For the tproxy and pf methods this can be an IPv6 address. Use this option
|
||||
with comma separated values if required, to provide both IPv4 and IPv6
|
||||
addresses, e.g. ``--listen 127.0.0.1:0,[::1]:0``.
|
||||
For the nft, tproxy and pf methods this can be an IPv6 address. Use
|
||||
this option with comma separated values if required, to provide both
|
||||
IPv4 and IPv6 addresses, e.g. ``--listen 127.0.0.1:0,[::1]:0``.
|
||||
|
||||
.. option:: -H, --auto-hosts
|
||||
|
||||
@ -92,6 +96,10 @@ Options
|
||||
are taken automatically from the server's routing
|
||||
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
|
||||
|
||||
Capture local DNS requests and forward to the remote DNS
|
||||
@ -122,9 +130,9 @@ Options
|
||||
|
||||
.. option:: --python
|
||||
|
||||
Specify the name/path of the remote python interpreter.
|
||||
The default is just ``python``, which means to use the
|
||||
default python interpreter on the remote system's PATH.
|
||||
Specify the name/path of the remote python interpreter. The
|
||||
default is to use ``python3`` (or ``python``, if ``python3``
|
||||
fails) in the remote system's PATH.
|
||||
|
||||
.. option:: -r <[username@]sshserver[:port]>, --remote=<[username@]sshserver[:port]>
|
||||
|
||||
@ -221,7 +229,8 @@ Options
|
||||
|
||||
.. 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
|
||||
|
||||
|
@ -20,6 +20,18 @@ Requires:
|
||||
|
||||
* 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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -3,24 +3,34 @@ import importlib
|
||||
import socket
|
||||
import struct
|
||||
import errno
|
||||
import ipaddress
|
||||
from sshuttle.helpers import Fatal, debug3
|
||||
|
||||
|
||||
def original_dst(sock):
|
||||
ip = "0.0.0.0"
|
||||
port = -1
|
||||
try:
|
||||
family = sock.family
|
||||
SO_ORIGINAL_DST = 80
|
||||
SOCKADDR_MIN = 16
|
||||
sockaddr_in = sock.getsockopt(socket.SOL_IP,
|
||||
SO_ORIGINAL_DST, SOCKADDR_MIN)
|
||||
(proto, port, a, b, c, d) = struct.unpack('!HHBBBB', sockaddr_in[:8])
|
||||
# FIXME: decoding is IPv4 only.
|
||||
assert(socket.htons(proto) == socket.AF_INET)
|
||||
ip = '%d.%d.%d.%d' % (a, b, c, d)
|
||||
return (ip, port)
|
||||
|
||||
if family == socket.AF_INET:
|
||||
SOCKADDR_MIN = 16
|
||||
sockaddr_in = sock.getsockopt(socket.SOL_IP,
|
||||
SO_ORIGINAL_DST, SOCKADDR_MIN)
|
||||
port, raw_ip = struct.unpack_from('!2xH4s', sockaddr_in[:8])
|
||||
ip = str(ipaddress.IPv4Address(raw_ip))
|
||||
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:
|
||||
if e.args[0] == errno.ENOPROTOOPT:
|
||||
return sock.getsockname()
|
||||
raise
|
||||
return (ip, port)
|
||||
|
||||
|
||||
class Features(object):
|
||||
|
@ -16,7 +16,10 @@ class Method(BaseMethod):
|
||||
if udp:
|
||||
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):
|
||||
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
|
||||
# same host. The connections the sshuttle server makes will
|
||||
# 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
|
||||
# to localhost DNS servers through sshuttle.
|
||||
@ -57,7 +63,11 @@ class Method(BaseMethod):
|
||||
# create new subnet entries.
|
||||
for _, swidth, sexclude, snet, fport, lport \
|
||||
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:
|
||||
tcp_ports = \
|
||||
tcp_ports + \
|
||||
@ -66,21 +76,38 @@ class Method(BaseMethod):
|
||||
tcp_ports = tcp_ports + ('tcp', 'dport', '%d' % (fport))
|
||||
|
||||
if sexclude:
|
||||
_nft('add rule', chain, *(tcp_ports + (
|
||||
'ip daddr %s/%s' % (snet, swidth), 'return')))
|
||||
if family == socket.AF_INET:
|
||||
_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:
|
||||
_nft('add rule', chain, *(tcp_ports + (
|
||||
'ip daddr %s/%s' % (snet, swidth),
|
||||
('redirect to :' + str(port)))))
|
||||
if family == socket.AF_INET:
|
||||
_nft('add rule', chain, *(tcp_ports + (
|
||||
'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):
|
||||
if udp:
|
||||
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):
|
||||
return nft(family, table, action, *args)
|
||||
|
||||
# basic cleanup/setup of chains
|
||||
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():
|
||||
sock = Mock()
|
||||
sock.family = AF_INET
|
||||
sock.getsockopt.return_value = struct.pack(
|
||||
'!HHBBBB', socket.ntohs(AF_INET), 1024, 127, 0, 0, 1)
|
||||
method = get_method('nat')
|
||||
assert method.get_tcp_dstip(sock) == ('127.0.0.1', 1024)
|
||||
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():
|
||||
sock = Mock()
|
||||
|
Loading…
Reference in New Issue
Block a user