Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
S
sshuttle
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
pub
sshuttle
Commits
5c8c7072
Unverified
Commit
5c8c7072
authored
Oct 25, 2020
by
Brian May
Committed by
GitHub
Oct 25, 2020
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #550 from skuhl/nft-ipv6
IPv6 support in nft method.
parents
ebf87d8f
c02b93e7
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
97 additions
and
29 deletions
+97
-29
manpage.rst
docs/manpage.rst
+21
-12
requirements.rst
docs/requirements.rst
+12
-0
__init__.py
sshuttle/methods/__init__.py
+18
-8
nft.py
sshuttle/methods/nft.py
+36
-9
test_methods_nat.py
tests/client/test_methods_nat.py
+10
-0
No files found.
docs/manpage.rst
View file @
5c8c7072
...
@@ -37,14 +37,18 @@ Options
...
@@ -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
...
@@ -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
...
@@ -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
...
@@ -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 o
n the remote system's PATH.
fails) i
n 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
...
@@ -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
...
...
docs/requirements.rst
View file @
5c8c7072
...
@@ -20,6 +20,18 @@ Requires:
...
@@ -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
~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~
...
...
sshuttle/methods/__init__.py
View file @
5c8c7072
...
@@ -3,24 +3,34 @@ import importlib
...
@@ -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
):
...
...
sshuttle/methods/nft.py
View file @
5c8c7072
...
@@ -16,7 +16,10 @@ class Method(BaseMethod):
...
@@ -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):
...
@@ -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):
...
@@ -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):
...
@@ -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
tests/client/test_methods_nat.py
View file @
5c8c7072
...
@@ -18,12 +18,22 @@ def test_get_supported_features():
...
@@ -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
()
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment