mirror of
https://github.com/sshuttle/sshuttle.git
synced 2025-07-04 08:40:30 +02:00
Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
b65bb29023 | |||
c093b4bd96 | |||
e76d1e14bd | |||
6c6a39fefa | |||
714bd9f81b | |||
c746d6f7db | |||
f9361d7014 | |||
c4a41ada09 | |||
ef83a5c573 | |||
af9ebd0f4b | |||
9a9015a75e | |||
d7d24f956b | |||
809fad537f | |||
abce18cfc2 | |||
5e90491344 | |||
e8ceccc3d5 | |||
e39c4afce0 | |||
0e52cce9d1 | |||
6d5d0d766f | |||
08fb3be7a0 | |||
fee5868196 | |||
fbbcc05d58 | |||
15b394da86 | |||
0ed5ef9a97 | |||
c0c3612e6d | |||
0033efca11 | |||
ae6e25302f | |||
ffd95fb776 | |||
acb5aa5386 | |||
4801ae6627 | |||
f57ad356b9 | |||
a441a03e57 | |||
d2fdb6c029 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,4 @@
|
|||||||
/sshuttle/version.py
|
/sshuttle/version.py
|
||||||
/docs/conf.py
|
|
||||||
/tmp/
|
/tmp/
|
||||||
/.cache/
|
/.cache/
|
||||||
/.eggs/
|
/.eggs/
|
||||||
|
87
CHANGES.rst
87
CHANGES.rst
@ -1,13 +1,68 @@
|
|||||||
Release 0.78.1 (6th August, 2016)
|
==========
|
||||||
=================================
|
Change log
|
||||||
|
==========
|
||||||
|
All notable changes to this project will be documented in this file. The format
|
||||||
|
is based on `Keep a Changelog`_ and this project
|
||||||
|
adheres to `Semantic Versioning`_.
|
||||||
|
|
||||||
|
.. _`Keep a Changelog`: http://keepachangelog.com/
|
||||||
|
.. _`Semantic Versioning`: http://semver.org/
|
||||||
|
|
||||||
|
|
||||||
|
0.78.3 - 2017-07-09
|
||||||
|
-------------------
|
||||||
|
The "I should have done a git pull" first release.
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
~~~~~
|
||||||
|
* Order first by port range and only then by swidth
|
||||||
|
|
||||||
|
|
||||||
|
0.78.2 - 2017-07-09
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Added
|
||||||
|
~~~~~
|
||||||
|
* Adds support for tunneling specific port ranges (#144).
|
||||||
|
* Add support for iproute2.
|
||||||
|
* Allow remote hosts with colons in the username.
|
||||||
|
* Re-introduce ipfw support for sshuttle on FreeBSD with support for --DNS option as well.
|
||||||
|
* Add support for PfSense.
|
||||||
|
* Tests and documentation for systemd integration.
|
||||||
|
* Allow subnets to be given only by file (-s).
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
~~~~~
|
||||||
|
* Work around non tabular headers in BSD netstat.
|
||||||
|
* Fix UDP and DNS support on Python 2.7 with tproxy method.
|
||||||
|
* Fixed tests after adding support for iproute2.
|
||||||
|
* Small refactoring of netstat/iproute parsing.
|
||||||
|
* Set started_by_sshuttle False after disabling pf.
|
||||||
|
* Fix punctuation and explain Type=notify.
|
||||||
|
* Move pytest-runner to tests_require.
|
||||||
|
* Fix warning: closed channel got=STOP_SENDING.
|
||||||
|
* Support sdnotify for better systemd integration.
|
||||||
|
* Fix #117 to allow for no subnets via file (-s).
|
||||||
|
* Fix argument splitting for multi-word arguments.
|
||||||
|
* requirements.rst: Fix mistakes.
|
||||||
|
* Fix typo, space not required here.
|
||||||
|
* Update installation instructions.
|
||||||
|
* Support using run from different directory.
|
||||||
|
* Ensure we update sshuttle/version.py in run.
|
||||||
|
* Don't print python version in run.
|
||||||
|
* Add CWD to PYTHONPATH in run.
|
||||||
|
|
||||||
|
|
||||||
|
0.78.1 - 2016-08-06
|
||||||
|
-------------------
|
||||||
* Fix readthedocs versioning.
|
* Fix readthedocs versioning.
|
||||||
* Don't crash on ENETUNREACH.
|
* Don't crash on ENETUNREACH.
|
||||||
* Various bug fixes.
|
* Various bug fixes.
|
||||||
* Improvements to BSD and OSX support.
|
* Improvements to BSD and OSX support.
|
||||||
|
|
||||||
|
|
||||||
Release 0.78.0 (Apr 8, 2016)
|
0.78.0 - 2016-04-08
|
||||||
============================
|
-------------------
|
||||||
|
|
||||||
* Don't force IPv6 if IPv6 nameservers supplied. Fixes #74.
|
* Don't force IPv6 if IPv6 nameservers supplied. Fixes #74.
|
||||||
* Call /bin/sh as users shell may not be POSIX compliant. Fixes #77.
|
* Call /bin/sh as users shell may not be POSIX compliant. Fixes #77.
|
||||||
@ -17,22 +72,22 @@ Release 0.78.0 (Apr 8, 2016)
|
|||||||
* Make server parts work with old versions of Python. Fixes #81.
|
* Make server parts work with old versions of Python. Fixes #81.
|
||||||
|
|
||||||
|
|
||||||
Release 0.77.2 (Mar 7, 2016)
|
0.77.2 - 2016-03-07
|
||||||
============================
|
-------------------
|
||||||
|
|
||||||
* Accidentally switched LGPL2 license with GPL2 license in 0.77.1 - now fixed.
|
* Accidentally switched LGPL2 license with GPL2 license in 0.77.1 - now fixed.
|
||||||
|
|
||||||
|
|
||||||
Release 0.77.1 (Mar 7, 2016)
|
0.77.1 - 2016-03-07
|
||||||
============================
|
-------------------
|
||||||
|
|
||||||
* Use semantic versioning. http://semver.org/
|
* Use semantic versioning. http://semver.org/
|
||||||
* Update GPL 2 license text.
|
* Update GPL 2 license text.
|
||||||
* New release to fix PyPI.
|
* New release to fix PyPI.
|
||||||
|
|
||||||
|
|
||||||
Release 0.77 (Mar 3, 2016)
|
0.77 - 2016-03-03
|
||||||
==========================
|
-----------------
|
||||||
|
|
||||||
* Various bug fixes.
|
* Various bug fixes.
|
||||||
* Fix Documentation.
|
* Fix Documentation.
|
||||||
@ -40,8 +95,8 @@ Release 0.77 (Mar 3, 2016)
|
|||||||
* Add support for OpenBSD.
|
* Add support for OpenBSD.
|
||||||
|
|
||||||
|
|
||||||
Release 0.76 (Jan 17, 2016)
|
0.76 - 2016-01-17
|
||||||
===========================
|
-----------------
|
||||||
|
|
||||||
* Add option to disable IPv6 support.
|
* Add option to disable IPv6 support.
|
||||||
* Update documentation.
|
* Update documentation.
|
||||||
@ -49,14 +104,14 @@ Release 0.76 (Jan 17, 2016)
|
|||||||
* Use setuptools-scm for automatic versioning.
|
* Use setuptools-scm for automatic versioning.
|
||||||
|
|
||||||
|
|
||||||
Release 0.75 (Jan 12, 2016)
|
0.75 - 2016-01-12
|
||||||
===========================
|
-----------------
|
||||||
|
|
||||||
* Revert change that broke sshuttle entry point.
|
* Revert change that broke sshuttle entry point.
|
||||||
|
|
||||||
|
|
||||||
Release 0.74 (Jan 10, 2016)
|
0.74 - 2016-01-10
|
||||||
===========================
|
-----------------
|
||||||
|
|
||||||
* Add CHANGES.rst file.
|
* Add CHANGES.rst file.
|
||||||
* Numerous bug fixes.
|
* Numerous bug fixes.
|
||||||
|
@ -6,7 +6,6 @@ include LICENSE
|
|||||||
include run
|
include run
|
||||||
include tox.ini
|
include tox.ini
|
||||||
exclude sshuttle/version.py
|
exclude sshuttle/version.py
|
||||||
exclude docs/conf.py
|
|
||||||
recursive-include docs *.bat
|
recursive-include docs *.bat
|
||||||
recursive-include docs *.py
|
recursive-include docs *.py
|
||||||
recursive-include docs *.rst
|
recursive-include docs *.rst
|
||||||
|
19
README.rst
19
README.rst
@ -29,12 +29,31 @@ common case:
|
|||||||
Obtaining sshuttle
|
Obtaining sshuttle
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Debian stretch or later::
|
||||||
|
|
||||||
|
apt-get install sshuttle
|
||||||
|
|
||||||
- From PyPI::
|
- From PyPI::
|
||||||
|
|
||||||
|
sudo pip install sshuttle
|
||||||
|
|
||||||
|
- Clone::
|
||||||
|
|
||||||
|
git clone https://github.com/sshuttle/sshuttle.git
|
||||||
|
sudo ./setup.py install
|
||||||
|
|
||||||
|
It is also possible to install into a virtualenv as a non-root user.
|
||||||
|
|
||||||
|
- From PyPI::
|
||||||
|
|
||||||
|
virtualenv -p python3 /tmp/sshuttle
|
||||||
|
. /tmp/sshuttle/bin/activate
|
||||||
pip install sshuttle
|
pip install sshuttle
|
||||||
|
|
||||||
- Clone::
|
- Clone::
|
||||||
|
|
||||||
|
virtualenv -p python3 /tmp/sshuttle
|
||||||
|
. /tmp/sshuttle/bin/activate
|
||||||
git clone https://github.com/sshuttle/sshuttle.git
|
git clone https://github.com/sshuttle/sshuttle.git
|
||||||
./setup.py install
|
./setup.py install
|
||||||
|
|
||||||
|
@ -1,4 +1 @@
|
|||||||
Changelog
|
|
||||||
---------
|
|
||||||
|
|
||||||
.. include:: ../CHANGES.rst
|
.. include:: ../CHANGES.rst
|
||||||
|
@ -13,9 +13,10 @@
|
|||||||
# All configuration values have a default; values that are commented out
|
# All configuration values have a default; values that are commented out
|
||||||
# serve to show the default.
|
# serve to show the default.
|
||||||
|
|
||||||
# import sys
|
import sys
|
||||||
# import os
|
import os
|
||||||
from setuptools_scm import get_version
|
sys.path.insert(0, os.path.abspath('..'))
|
||||||
|
import sshuttle.version # NOQA
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
@ -54,10 +55,10 @@ copyright = '2016, Brian May'
|
|||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
|
||||||
version = get_version(root="..")
|
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = version
|
release = sshuttle.version.version
|
||||||
|
# The short X.Y version.
|
||||||
|
version = '.'.join(release.split('.')[:2])
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
@ -31,11 +31,18 @@ Options
|
|||||||
.. option:: subnets
|
.. option:: subnets
|
||||||
|
|
||||||
A list of subnets to route over the VPN, in the form
|
A list of subnets to route over the VPN, in the form
|
||||||
``a.b.c.d[/width]``. Valid examples are 1.2.3.4 (a
|
``a.b.c.d[/width][port[-port]]``. Valid examples are 1.2.3.4 (a
|
||||||
single IP address), 1.2.3.4/32 (equivalent to 1.2.3.4),
|
single IP address), 1.2.3.4/32 (equivalent to 1.2.3.4),
|
||||||
1.2.3.0/24 (a 24-bit subnet, ie. with a 255.255.255.0
|
1.2.3.0/24 (a 24-bit subnet, ie. with a 255.255.255.0
|
||||||
netmask), and 0/0 ('just route everything through the
|
netmask), and 0/0 ('just route everything through the
|
||||||
VPN').
|
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
|
||||||
|
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.
|
||||||
|
|
||||||
.. option:: --method [auto|nat|tproxy|pf]
|
.. option:: --method [auto|nat|tproxy|pf]
|
||||||
|
|
||||||
@ -54,9 +61,11 @@ Options
|
|||||||
connections from other machines on your network (ie. to
|
connections from other machines on your network (ie. to
|
||||||
run :program:`sshuttle` on a router) try enabling IP Forwarding in
|
run :program:`sshuttle` on a router) try enabling IP Forwarding in
|
||||||
your kernel, then using ``--listen 0.0.0.0:0``.
|
your kernel, then using ``--listen 0.0.0.0:0``.
|
||||||
|
You can use any name resolving to an IP address of the machine running
|
||||||
|
:program:`sshuttle`, e.g. ``--listen localhost``.
|
||||||
|
|
||||||
For the tproxy method this can be an IPv6 address. Use this option twice if
|
For the tproxy and pf methods this can be an IPv6 address. Use this option
|
||||||
required, to provide both IPv4 and IPv6 addresses.
|
twice if required, to provide both IPv4 and IPv6 addresses.
|
||||||
|
|
||||||
.. option:: -H, --auto-hosts
|
.. option:: -H, --auto-hosts
|
||||||
|
|
||||||
@ -176,7 +185,7 @@ Options
|
|||||||
|
|
||||||
.. option:: --disable-ipv6
|
.. option:: --disable-ipv6
|
||||||
|
|
||||||
If using the tproxy method, this will disable IPv6 support.
|
If using tproxy or pf methods, this will disable IPv6 support.
|
||||||
|
|
||||||
.. option:: --firewall
|
.. option:: --firewall
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ Overview
|
|||||||
As far as I know, sshuttle is the only program that solves the following
|
As far as I know, sshuttle is the only program that solves the following
|
||||||
common case:
|
common case:
|
||||||
|
|
||||||
- Your client machine (or router) is Linux, FreeBSD, or MacOS.
|
- Your client machine (or router) is Linux, MacOS, FreeBSD, OpenBSD or pfSense.
|
||||||
|
|
||||||
- You have access to a remote network via ssh.
|
- You have access to a remote network via ssh.
|
||||||
|
|
||||||
|
@ -26,28 +26,31 @@ Linux with TPROXY method
|
|||||||
Supports:
|
Supports:
|
||||||
|
|
||||||
* IPv4 TCP
|
* IPv4 TCP
|
||||||
* IPv4 UDP (requires ``recmsg`` - see below)
|
* IPv4 UDP (requires ``recvmsg`` - see below)
|
||||||
* IPv6 DNS (requires ``recmsg`` - see below)
|
* IPv6 DNS (requires ``recvmsg`` - see below)
|
||||||
* IPv6 TCP
|
* IPv6 TCP
|
||||||
* IPv6 UDP (requires ``recmsg`` - see below)
|
* IPv6 UDP (requires ``recvmsg`` - see below)
|
||||||
* IPv6 DNS (requires ``recmsg`` - see below)
|
* IPv6 DNS (requires ``recvmsg`` - see below)
|
||||||
|
|
||||||
.. _PyXAPI: http://www.pps.univ-paris-diderot.fr/~ylg/PyXAPI/
|
.. _PyXAPI: http://www.pps.univ-paris-diderot.fr/~ylg/PyXAPI/
|
||||||
|
|
||||||
Full UDP or DNS support with the TPROXY method requires the ``recvmsg()``
|
Full UDP or DNS support with the TPROXY method requires the ``recvmsg()``
|
||||||
syscall. This is not available in Python 2, however is in Python 3.5 and
|
syscall. This is not available in Python 2, however it is in Python 3.5 and
|
||||||
later. Under Python 2 you might find it sufficient installing PyXAPI_ to get
|
later. Under Python 2 you might find it sufficient to install PyXAPI_ in
|
||||||
the ``recvmsg()`` function. See :doc:`tproxy` for more information.
|
order to get the ``recvmsg()`` function. See :doc:`tproxy` for more
|
||||||
|
information.
|
||||||
|
|
||||||
|
|
||||||
MacOS / FreeBSD / OpenBSD
|
MacOS / FreeBSD / OpenBSD / pfSense
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Method: pf
|
Method: pf
|
||||||
|
|
||||||
Supports:
|
Supports:
|
||||||
|
|
||||||
* IPv4 TCP
|
* IPv4 TCP
|
||||||
* IPv4 DNS
|
* IPv4 DNS
|
||||||
|
* IPv6 TCP
|
||||||
|
* IPv6 DNS
|
||||||
|
|
||||||
Requires:
|
Requires:
|
||||||
|
|
||||||
@ -62,12 +65,31 @@ cmd.exe with Administrator access. See :doc:`windows` for more information.
|
|||||||
|
|
||||||
Server side Requirements
|
Server side Requirements
|
||||||
------------------------
|
------------------------
|
||||||
Server requirements are more relaxed, however it is recommended that you use
|
The server can run in any version of Python between 2.4 and 3.6.
|
||||||
Python 2.7 or Python 3.5.
|
However it is recommended that you use Python 2.7, Python 3.5 or later whenever
|
||||||
|
possible as support for older versions might be dropped in the future.
|
||||||
|
|
||||||
|
|
||||||
Additional Suggested Software
|
Additional Suggested Software
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
- You may want to use autossh, available in various package management
|
- You may want to use autossh, available in various package management
|
||||||
systems
|
systems.
|
||||||
|
- If you are using systemd, sshuttle can notify it when the connection to
|
||||||
|
the remote end is established and the firewall rules are installed. For
|
||||||
|
this feature to work you must configure the process start-up type for the
|
||||||
|
sshuttle service unit to notify, as shown in the example below.
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
:emphasize-lines: 6
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=sshuttle
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=notify
|
||||||
|
ExecStart=/usr/bin/sshuttle --dns --remote <user>@<server> <subnets...>
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
@ -1 +1 @@
|
|||||||
setuptools_scm
|
setuptools-scm==1.15.6
|
||||||
|
7
run
7
run
@ -1,7 +1,10 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
if python3.5 -V 2>/dev/null; then
|
source_dir="$(dirname $0)"
|
||||||
|
(cd "$source_dir" && "$source_dir/setup.py" --version > /dev/null)
|
||||||
|
export PYTHONPATH="$source_dir:$PYTHONPATH"
|
||||||
|
if python3.5 -V >/dev/null 2>&1; then
|
||||||
exec python3.5 -m "sshuttle" "$@"
|
exec python3.5 -m "sshuttle" "$@"
|
||||||
elif python2.7 -V 2>/dev/null; then
|
elif python2.7 -V >/dev/null 2>&1; then
|
||||||
exec python2.7 -m "sshuttle" "$@"
|
exec python2.7 -m "sshuttle" "$@"
|
||||||
else
|
else
|
||||||
exec python -m "sshuttle" "$@"
|
exec python -m "sshuttle" "$@"
|
||||||
|
@ -1,2 +1,9 @@
|
|||||||
[aliases]
|
[aliases]
|
||||||
test=pytest
|
test=pytest
|
||||||
|
|
||||||
|
[bdist_wheel]
|
||||||
|
universal = 1
|
||||||
|
|
||||||
|
[upload]
|
||||||
|
sign=true
|
||||||
|
identity=0x1784577F811F6EAC
|
||||||
|
10
setup.py
10
setup.py
@ -18,25 +18,21 @@
|
|||||||
# along with python-tldap If not, see <http://www.gnu.org/licenses/>.
|
# along with python-tldap If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
import shutil
|
|
||||||
|
|
||||||
with open("./docs/conf.orig.py", "r") as src:
|
|
||||||
with open("./docs/conf.py", "w") as dst:
|
|
||||||
dst.write("# FILE COPIED FROM conf.orig.py; DO NOT CHANGE\n")
|
|
||||||
shutil.copyfileobj(src, dst)
|
|
||||||
|
|
||||||
def version_scheme(version):
|
def version_scheme(version):
|
||||||
from setuptools_scm.version import guess_next_dev_version
|
from setuptools_scm.version import guess_next_dev_version
|
||||||
version = guess_next_dev_version(version)
|
version = guess_next_dev_version(version)
|
||||||
return version.lstrip("v")
|
return version.lstrip("v")
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="sshuttle",
|
name="sshuttle",
|
||||||
use_scm_version={
|
use_scm_version={
|
||||||
'write_to': "sshuttle/version.py",
|
'write_to': "sshuttle/version.py",
|
||||||
'version_scheme': version_scheme,
|
'version_scheme': version_scheme,
|
||||||
},
|
},
|
||||||
setup_requires=['setuptools_scm', 'pytest-runner'],
|
setup_requires=['setuptools_scm'],
|
||||||
# version=version,
|
# version=version,
|
||||||
url='https://github.com/sshuttle/sshuttle',
|
url='https://github.com/sshuttle/sshuttle',
|
||||||
author='Brian May',
|
author='Brian May',
|
||||||
@ -61,6 +57,6 @@ setup(
|
|||||||
'sshuttle = sshuttle.cmdline:main',
|
'sshuttle = sshuttle.cmdline:main',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
tests_require=['pytest', 'mock'],
|
tests_require=['pytest', 'pytest-runner', 'mock'],
|
||||||
keywords="ssh vpn",
|
keywords="ssh vpn",
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import socket
|
|
||||||
import errno
|
import errno
|
||||||
import re
|
import re
|
||||||
import signal
|
import signal
|
||||||
@ -9,6 +8,7 @@ import os
|
|||||||
import sshuttle.ssnet as ssnet
|
import sshuttle.ssnet as ssnet
|
||||||
import sshuttle.ssh as ssh
|
import sshuttle.ssh as ssh
|
||||||
import sshuttle.ssyslog as ssyslog
|
import sshuttle.ssyslog as ssyslog
|
||||||
|
import sshuttle.sdnotify as sdnotify
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
from sshuttle.ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
|
from sshuttle.ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
|
||||||
@ -16,6 +16,20 @@ from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, islocal, \
|
|||||||
resolvconf_nameservers
|
resolvconf_nameservers
|
||||||
from sshuttle.methods import get_method, Features
|
from sshuttle.methods import get_method, Features
|
||||||
|
|
||||||
|
try:
|
||||||
|
# try getting recvmsg from python
|
||||||
|
import socket as pythonsocket
|
||||||
|
getattr(pythonsocket.socket, "recvmsg")
|
||||||
|
socket = pythonsocket
|
||||||
|
except AttributeError:
|
||||||
|
# try getting recvmsg from socket_ext library
|
||||||
|
try:
|
||||||
|
import socket_ext
|
||||||
|
getattr(socket_ext.socket, "recvmsg")
|
||||||
|
socket = socket_ext
|
||||||
|
except ImportError:
|
||||||
|
import socket
|
||||||
|
|
||||||
_extra_fd = os.open('/dev/null', os.O_RDONLY)
|
_extra_fd = os.open('/dev/null', os.O_RDONLY)
|
||||||
|
|
||||||
|
|
||||||
@ -241,12 +255,13 @@ class FirewallClient:
|
|||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.pfile.write(b'ROUTES\n')
|
self.pfile.write(b'ROUTES\n')
|
||||||
for (family, ip, width) in self.subnets_include + self.auto_nets:
|
for (family, ip, width, fport, lport) \
|
||||||
self.pfile.write(b'%d,%d,0,%s\n'
|
in self.subnets_include + self.auto_nets:
|
||||||
% (family, width, ip.encode("ASCII")))
|
self.pfile.write(b'%d,%d,0,%s,%d,%d\n'
|
||||||
for (family, ip, width) in self.subnets_exclude:
|
% (family, width, ip.encode("ASCII"), fport, lport))
|
||||||
self.pfile.write(b'%d,%d,1,%s\n'
|
for (family, ip, width, fport, lport) in self.subnets_exclude:
|
||||||
% (family, width, ip.encode("ASCII")))
|
self.pfile.write(b'%d,%d,1,%s,%d,%d\n'
|
||||||
|
% (family, width, ip.encode("ASCII"), fport, lport))
|
||||||
|
|
||||||
self.pfile.write(b'NSLIST\n')
|
self.pfile.write(b'NSLIST\n')
|
||||||
for (family, ip) in self.nslist:
|
for (family, ip) in self.nslist:
|
||||||
@ -470,7 +485,7 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
|||||||
debug2("Ignored auto net %d/%s/%d\n" % (family, ip, width))
|
debug2("Ignored auto net %d/%s/%d\n" % (family, ip, width))
|
||||||
else:
|
else:
|
||||||
debug2("Adding auto net %d/%s/%d\n" % (family, ip, width))
|
debug2("Adding auto net %d/%s/%d\n" % (family, ip, width))
|
||||||
fw.auto_nets.append((family, ip, width))
|
fw.auto_nets.append((family, ip, width, 0, 0))
|
||||||
|
|
||||||
# we definitely want to do this *after* starting ssh, or we might end
|
# we definitely want to do this *after* starting ssh, or we might end
|
||||||
# up intercepting the ssh connection!
|
# up intercepting the ssh connection!
|
||||||
@ -503,6 +518,9 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
|||||||
debug1('seed_hosts: %r\n' % seed_hosts)
|
debug1('seed_hosts: %r\n' % seed_hosts)
|
||||||
mux.send(0, ssnet.CMD_HOST_REQ, str.encode('\n'.join(seed_hosts)))
|
mux.send(0, ssnet.CMD_HOST_REQ, str.encode('\n'.join(seed_hosts)))
|
||||||
|
|
||||||
|
sdnotify.send(sdnotify.ready(),
|
||||||
|
sdnotify.status('Connected to %s.' % remotename))
|
||||||
|
|
||||||
while 1:
|
while 1:
|
||||||
rv = serverproc.poll()
|
rv = serverproc.poll()
|
||||||
if rv:
|
if rv:
|
||||||
@ -574,11 +592,11 @@ def main(listenip_v6, listenip_v4,
|
|||||||
|
|
||||||
if required.ipv4 and \
|
if required.ipv4 and \
|
||||||
not any(listenip_v4[0] == sex[1] for sex in subnets_v4):
|
not any(listenip_v4[0] == sex[1] for sex in subnets_v4):
|
||||||
subnets_exclude.append((socket.AF_INET, listenip_v4[0], 32))
|
subnets_exclude.append((socket.AF_INET, listenip_v4[0], 32, 0, 0))
|
||||||
|
|
||||||
if required.ipv6 and \
|
if required.ipv6 and \
|
||||||
not any(listenip_v6[0] == sex[1] for sex in subnets_v6):
|
not any(listenip_v6[0] == sex[1] for sex in subnets_v6):
|
||||||
subnets_exclude.append((socket.AF_INET6, listenip_v6[0], 128))
|
subnets_exclude.append((socket.AF_INET6, listenip_v6[0], 128, 0, 0))
|
||||||
|
|
||||||
if listenip_v6 and listenip_v6[1] and listenip_v4 and listenip_v4[1]:
|
if listenip_v6 and listenip_v6[1] and listenip_v4 and listenip_v4[1]:
|
||||||
# if both ports given, no need to search for a spare port
|
# if both ports given, no need to search for a spare port
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import re
|
import re
|
||||||
|
import socket
|
||||||
import sshuttle.helpers as helpers
|
import sshuttle.helpers as helpers
|
||||||
import sshuttle.client as client
|
import sshuttle.client as client
|
||||||
import sshuttle.firewall as firewall
|
import sshuttle.firewall as firewall
|
||||||
import sshuttle.hostwatch as hostwatch
|
import sshuttle.hostwatch as hostwatch
|
||||||
import sshuttle.ssyslog as ssyslog
|
import sshuttle.ssyslog as ssyslog
|
||||||
from sshuttle.options import parser, parse_ipport6, parse_ipport4
|
from sshuttle.options import parser, parse_ipport
|
||||||
from sshuttle.helpers import family_ip_tuple, log, Fatal
|
from sshuttle.helpers import family_ip_tuple, log, Fatal
|
||||||
|
|
||||||
|
|
||||||
@ -20,13 +21,13 @@ def main():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if opt.firewall:
|
if opt.firewall:
|
||||||
if opt.subnets:
|
if opt.subnets or opt.subnets_file:
|
||||||
parser.error('exactly zero arguments expected')
|
parser.error('exactly zero arguments expected')
|
||||||
return firewall.main(opt.method, opt.syslog)
|
return firewall.main(opt.method, opt.syslog)
|
||||||
elif opt.hostwatch:
|
elif opt.hostwatch:
|
||||||
return hostwatch.hw_main(opt.subnets)
|
return hostwatch.hw_main(opt.subnets)
|
||||||
else:
|
else:
|
||||||
includes = opt.subnets
|
includes = opt.subnets + opt.subnets_file
|
||||||
excludes = opt.exclude
|
excludes = opt.exclude
|
||||||
if not includes and not opt.auto_nets:
|
if not includes and not opt.auto_nets:
|
||||||
parser.error('at least one subnet, subnet file, '
|
parser.error('at least one subnet, subnet file, '
|
||||||
@ -46,10 +47,11 @@ def main():
|
|||||||
ipport_v4 = None
|
ipport_v4 = None
|
||||||
list = opt.listen.split(",")
|
list = opt.listen.split(",")
|
||||||
for ip in list:
|
for ip in list:
|
||||||
if '[' in ip and ']' in ip:
|
family, ip, port = parse_ipport(ip)
|
||||||
ipport_v6 = parse_ipport6(ip)
|
if family == socket.AF_INET6:
|
||||||
|
ipport_v6 = (ip, port)
|
||||||
else:
|
else:
|
||||||
ipport_v4 = parse_ipport4(ip)
|
ipport_v4 = (ip, port)
|
||||||
else:
|
else:
|
||||||
# parse_ipport4('127.0.0.1:0')
|
# parse_ipport4('127.0.0.1:0')
|
||||||
ipport_v4 = "auto"
|
ipport_v4 = "auto"
|
||||||
|
@ -74,6 +74,16 @@ def setup_daemon():
|
|||||||
return sys.stdin, sys.stdout
|
return sys.stdin, sys.stdout
|
||||||
|
|
||||||
|
|
||||||
|
# Note that we're sorting in a very particular order:
|
||||||
|
# we need to go from smaller, more specific, port ranges, to larger,
|
||||||
|
# less-specific, port ranges. At each level, we order by subnet
|
||||||
|
# width, from most-specific subnets (largest swidth) to
|
||||||
|
# least-specific. On ties, excludes come first.
|
||||||
|
# s:(inet, subnet width, exclude flag, subnet, first port, last port)
|
||||||
|
def subnet_weight(s):
|
||||||
|
return (-s[-1] + (s[-2] or -65535), s[1], s[2])
|
||||||
|
|
||||||
|
|
||||||
# This is some voodoo for setting up the kernel's transparent
|
# This is some voodoo for setting up the kernel's transparent
|
||||||
# proxying stuff. If subnets is empty, we just delete our sshuttle rules;
|
# proxying stuff. If subnets is empty, we just delete our sshuttle rules;
|
||||||
# otherwise we delete it, then make them from scratch.
|
# otherwise we delete it, then make them from scratch.
|
||||||
@ -119,10 +129,17 @@ def main(method_name, syslog):
|
|||||||
elif line.startswith("NSLIST\n"):
|
elif line.startswith("NSLIST\n"):
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
(family, width, exclude, ip) = line.strip().split(',', 3)
|
(family, width, exclude, ip, fport, lport) = \
|
||||||
|
line.strip().split(',', 5)
|
||||||
except:
|
except:
|
||||||
raise Fatal('firewall: expected route or NSLIST but got %r' % line)
|
raise Fatal('firewall: expected route or NSLIST but got %r' % line)
|
||||||
subnets.append((int(family), int(width), bool(int(exclude)), ip))
|
subnets.append((
|
||||||
|
int(family),
|
||||||
|
int(width),
|
||||||
|
bool(int(exclude)),
|
||||||
|
ip,
|
||||||
|
int(fport),
|
||||||
|
int(lport)))
|
||||||
debug2('firewall manager: Got subnets: %r\n' % subnets)
|
debug2('firewall manager: Got subnets: %r\n' % subnets)
|
||||||
|
|
||||||
nslist = []
|
nslist = []
|
||||||
@ -221,6 +238,7 @@ def main(method_name, syslog):
|
|||||||
break
|
break
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
|
sdnotify.send(sdnotify.stop())
|
||||||
debug1('firewall manager: undoing changes.\n')
|
debug1('firewall manager: undoing changes.\n')
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
@ -98,6 +98,8 @@ def get_auto_method():
|
|||||||
method_name = "nat"
|
method_name = "nat"
|
||||||
elif _program_exists('pfctl'):
|
elif _program_exists('pfctl'):
|
||||||
method_name = "pf"
|
method_name = "pf"
|
||||||
|
elif _program_exists('ipfw'):
|
||||||
|
method_name = "ipfw"
|
||||||
else:
|
else:
|
||||||
raise Fatal(
|
raise Fatal(
|
||||||
"can't find either iptables or pfctl; check your PATH")
|
"can't find either iptables or pfctl; check your PATH")
|
||||||
|
268
sshuttle/methods/ipfw.py
Normal file
268
sshuttle/methods/ipfw.py
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import struct
|
||||||
|
import subprocess as ssubprocess
|
||||||
|
from sshuttle.methods import BaseMethod
|
||||||
|
from sshuttle.helpers import log, debug1, debug3, \
|
||||||
|
Fatal, family_to_string
|
||||||
|
|
||||||
|
recvmsg = None
|
||||||
|
try:
|
||||||
|
# try getting recvmsg from python
|
||||||
|
import socket as pythonsocket
|
||||||
|
getattr(pythonsocket.socket, "recvmsg")
|
||||||
|
socket = pythonsocket
|
||||||
|
recvmsg = "python"
|
||||||
|
except AttributeError:
|
||||||
|
# try getting recvmsg from socket_ext library
|
||||||
|
try:
|
||||||
|
import socket_ext
|
||||||
|
getattr(socket_ext.socket, "recvmsg")
|
||||||
|
socket = socket_ext
|
||||||
|
recvmsg = "socket_ext"
|
||||||
|
except ImportError:
|
||||||
|
import socket
|
||||||
|
|
||||||
|
IP_BINDANY = 24
|
||||||
|
IP_RECVDSTADDR = 7
|
||||||
|
SOL_IPV6 = 41
|
||||||
|
IPV6_RECVDSTADDR = 74
|
||||||
|
|
||||||
|
if recvmsg == "python":
|
||||||
|
def recv_udp(listener, bufsize):
|
||||||
|
debug3('Accept UDP python using recvmsg.\n')
|
||||||
|
data, ancdata, msg_flags, srcip = listener.recvmsg(4096, socket.CMSG_SPACE(4))
|
||||||
|
dstip = None
|
||||||
|
family = None
|
||||||
|
for cmsg_level, cmsg_type, cmsg_data in ancdata:
|
||||||
|
if cmsg_level == socket.SOL_IP and cmsg_type == IP_RECVDSTADDR:
|
||||||
|
port = 53
|
||||||
|
ip = socket.inet_ntop(socket.AF_INET, cmsg_data[0:4])
|
||||||
|
dstip = (ip, port)
|
||||||
|
break
|
||||||
|
return (srcip, dstip, data)
|
||||||
|
elif recvmsg == "socket_ext":
|
||||||
|
def recv_udp(listener, bufsize):
|
||||||
|
debug3('Accept UDP using socket_ext recvmsg.\n')
|
||||||
|
srcip, data, adata, flags = listener.recvmsg((bufsize,), socket.CMSG_SPACE(4))
|
||||||
|
dstip = None
|
||||||
|
family = None
|
||||||
|
for a in adata:
|
||||||
|
if a.cmsg_level == socket.SOL_IP and a.cmsg_type == IP_RECVDSTADDR:
|
||||||
|
port = 53
|
||||||
|
ip = socket.inet_ntop(socket.AF_INET, cmsg_data[0:4])
|
||||||
|
dstip = (ip, port)
|
||||||
|
break
|
||||||
|
return (srcip, dstip, data[0])
|
||||||
|
else:
|
||||||
|
def recv_udp(listener, bufsize):
|
||||||
|
debug3('Accept UDP using recvfrom.\n')
|
||||||
|
data, srcip = listener.recvfrom(bufsize)
|
||||||
|
return (srcip, None, data)
|
||||||
|
|
||||||
|
|
||||||
|
def ipfw_rule_exists(n):
|
||||||
|
argv = ['ipfw', 'list']
|
||||||
|
env = {
|
||||||
|
'PATH': os.environ['PATH'],
|
||||||
|
'LC_ALL': "C",
|
||||||
|
}
|
||||||
|
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=env)
|
||||||
|
|
||||||
|
found = False
|
||||||
|
for line in p.stdout:
|
||||||
|
if line.startswith(b'%05d ' % n):
|
||||||
|
if not ('ipttl 42' in line or 'check-state' in line):
|
||||||
|
log('non-sshuttle ipfw rule: %r\n' % line.strip())
|
||||||
|
raise Fatal('non-sshuttle ipfw rule #%d already exists!' % n)
|
||||||
|
found = True
|
||||||
|
rv = p.wait()
|
||||||
|
if rv:
|
||||||
|
raise Fatal('%r returned %d' % (argv, rv))
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
_oldctls = {}
|
||||||
|
|
||||||
|
|
||||||
|
def _fill_oldctls(prefix):
|
||||||
|
argv = ['sysctl', prefix]
|
||||||
|
env = {
|
||||||
|
'PATH': os.environ['PATH'],
|
||||||
|
'LC_ALL': "C",
|
||||||
|
}
|
||||||
|
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=env)
|
||||||
|
for line in p.stdout:
|
||||||
|
line = line.decode()
|
||||||
|
assert(line[-1] == '\n')
|
||||||
|
(k, v) = line[:-1].split(': ', 1)
|
||||||
|
_oldctls[k] = v.strip()
|
||||||
|
rv = p.wait()
|
||||||
|
if rv:
|
||||||
|
raise Fatal('%r returned %d' % (argv, rv))
|
||||||
|
if not line:
|
||||||
|
raise Fatal('%r returned no data' % (argv,))
|
||||||
|
|
||||||
|
|
||||||
|
def _sysctl_set(name, val):
|
||||||
|
argv = ['sysctl', '-w', '%s=%s' % (name, val)]
|
||||||
|
debug1('>> %s\n' % ' '.join(argv))
|
||||||
|
return ssubprocess.call(argv, stdout=open('/dev/null', 'w'))
|
||||||
|
|
||||||
|
|
||||||
|
_changedctls = []
|
||||||
|
|
||||||
|
|
||||||
|
def sysctl_set(name, val, permanent=False):
|
||||||
|
PREFIX = 'net.inet.ip'
|
||||||
|
assert(name.startswith(PREFIX + '.'))
|
||||||
|
val = str(val)
|
||||||
|
if not _oldctls:
|
||||||
|
_fill_oldctls(PREFIX)
|
||||||
|
if not (name in _oldctls):
|
||||||
|
debug1('>> No such sysctl: %r\n' % name)
|
||||||
|
return False
|
||||||
|
oldval = _oldctls[name]
|
||||||
|
if val != oldval:
|
||||||
|
rv = _sysctl_set(name, val)
|
||||||
|
if rv == 0 and permanent:
|
||||||
|
debug1('>> ...saving permanently in /etc/sysctl.conf\n')
|
||||||
|
f = open('/etc/sysctl.conf', 'a')
|
||||||
|
f.write('\n'
|
||||||
|
'# Added by sshuttle\n'
|
||||||
|
'%s=%s\n' % (name, val))
|
||||||
|
f.close()
|
||||||
|
else:
|
||||||
|
_changedctls.append(name)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def ipfw(*args):
|
||||||
|
argv = ['ipfw', '-q'] + list(args)
|
||||||
|
debug1('>> %s\n' % ' '.join(argv))
|
||||||
|
rv = ssubprocess.call(argv)
|
||||||
|
if rv:
|
||||||
|
raise Fatal('%r returned %d' % (argv, rv))
|
||||||
|
|
||||||
|
|
||||||
|
def ipfw_noexit(*args):
|
||||||
|
argv = ['ipfw', '-q'] + list(args)
|
||||||
|
debug1('>> %s\n' % ' '.join(argv))
|
||||||
|
ssubprocess.call(argv)
|
||||||
|
|
||||||
|
class Method(BaseMethod):
|
||||||
|
|
||||||
|
def get_supported_features(self):
|
||||||
|
result = super(Method, self).get_supported_features()
|
||||||
|
result.ipv6 = False
|
||||||
|
result.udp = False #NOTE: Almost there, kernel patch needed
|
||||||
|
result.dns = True
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_tcp_dstip(self, sock):
|
||||||
|
return sock.getsockname()
|
||||||
|
|
||||||
|
def recv_udp(self, udp_listener, bufsize):
|
||||||
|
srcip, dstip, data = recv_udp(udp_listener, bufsize)
|
||||||
|
if not dstip:
|
||||||
|
debug1(
|
||||||
|
"-- ignored UDP from %r: "
|
||||||
|
"couldn't determine destination IP address\n" % (srcip,))
|
||||||
|
return None
|
||||||
|
return srcip, dstip, data
|
||||||
|
|
||||||
|
def send_udp(self, sock, srcip, dstip, data):
|
||||||
|
if not srcip:
|
||||||
|
debug1(
|
||||||
|
"-- ignored UDP to %r: "
|
||||||
|
"couldn't determine source IP address\n" % (dstip,))
|
||||||
|
return
|
||||||
|
|
||||||
|
#debug3('Sending SRC: %r DST: %r\n' % (srcip, dstip))
|
||||||
|
sender = socket.socket(sock.family, socket.SOCK_DGRAM)
|
||||||
|
sender.setsockopt(socket.SOL_IP, IP_BINDANY, 1)
|
||||||
|
sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||||
|
sender.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
|
||||||
|
sender.bind(srcip)
|
||||||
|
sender.sendto(data,dstip)
|
||||||
|
sender.close()
|
||||||
|
|
||||||
|
def setup_udp_listener(self, udp_listener):
|
||||||
|
if udp_listener.v4 is not None:
|
||||||
|
udp_listener.v4.setsockopt(socket.SOL_IP, IP_RECVDSTADDR, 1)
|
||||||
|
#if udp_listener.v6 is not None:
|
||||||
|
# udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVDSTADDR, 1)
|
||||||
|
|
||||||
|
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
|
||||||
|
# IPv6 not supported
|
||||||
|
if family not in [socket.AF_INET ]:
|
||||||
|
raise Exception(
|
||||||
|
'Address family "%s" unsupported by ipfw method_name'
|
||||||
|
% family_to_string(family))
|
||||||
|
|
||||||
|
#XXX: Any risk from this?
|
||||||
|
ipfw_noexit('delete', '1')
|
||||||
|
|
||||||
|
while _changedctls:
|
||||||
|
name = _changedctls.pop()
|
||||||
|
oldval = _oldctls[name]
|
||||||
|
_sysctl_set(name, oldval)
|
||||||
|
|
||||||
|
if subnets or dnsport:
|
||||||
|
sysctl_set('net.inet.ip.fw.enable', 1)
|
||||||
|
|
||||||
|
ipfw('add', '1', 'check-state', 'ip',
|
||||||
|
'from', 'any', 'to', 'any')
|
||||||
|
|
||||||
|
ipfw('add', '1', 'skipto', '2',
|
||||||
|
'tcp',
|
||||||
|
'from', 'any', 'to', 'table(125)')
|
||||||
|
ipfw('add', '1', 'fwd', '127.0.0.1,%d' % port,
|
||||||
|
'tcp',
|
||||||
|
'from', 'any', 'to', 'table(126)',
|
||||||
|
'not', 'ipttl', '42', 'keep-state', 'setup')
|
||||||
|
|
||||||
|
ipfw_noexit('table', '124', 'flush')
|
||||||
|
dnscount = 0
|
||||||
|
for f, ip in [i for i in nslist if i[0] == family]:
|
||||||
|
ipfw('table', '124', 'add', '%s' % (ip))
|
||||||
|
dnscount += 1
|
||||||
|
if dnscount > 0:
|
||||||
|
ipfw('add', '1', 'fwd', '127.0.0.1,%d' % dnsport,
|
||||||
|
'udp',
|
||||||
|
'from', 'any', 'to', 'table(124)',
|
||||||
|
'not', 'ipttl', '42')
|
||||||
|
"""if udp:
|
||||||
|
ipfw('add', '1', 'skipto', '2',
|
||||||
|
'udp',
|
||||||
|
'from', 'any', 'to', 'table(125)')
|
||||||
|
ipfw('add', '1', 'fwd', '127.0.0.1,%d' % port,
|
||||||
|
'udp',
|
||||||
|
'from', 'any', 'to', 'table(126)',
|
||||||
|
'not', 'ipttl', '42')
|
||||||
|
"""
|
||||||
|
ipfw('add', '1', 'allow',
|
||||||
|
'udp',
|
||||||
|
'from', 'any', 'to', 'any',
|
||||||
|
'ipttl', '42')
|
||||||
|
|
||||||
|
if subnets:
|
||||||
|
# create new subnet entries
|
||||||
|
for f, swidth, sexclude, snet \
|
||||||
|
in sorted(subnets, key=lambda s: s[1], reverse=True):
|
||||||
|
if sexclude:
|
||||||
|
ipfw('table', '125', 'add', '%s/%s' % (snet, swidth))
|
||||||
|
else:
|
||||||
|
ipfw('table', '126', 'add', '%s/%s' % (snet, swidth))
|
||||||
|
|
||||||
|
def restore_firewall(self, port, family, udp):
|
||||||
|
if family not in [socket.AF_INET]:
|
||||||
|
raise Exception(
|
||||||
|
'Address family "%s" unsupported by tproxy method'
|
||||||
|
% family_to_string(family))
|
||||||
|
|
||||||
|
ipfw_noexit('delete', '1')
|
||||||
|
ipfw_noexit('table', '124', 'flush')
|
||||||
|
ipfw_noexit('table', '125', 'flush')
|
||||||
|
ipfw_noexit('table', '126', 'flush')
|
||||||
|
|
@ -1,4 +1,5 @@
|
|||||||
import socket
|
import socket
|
||||||
|
from sshuttle.firewall import subnet_weight
|
||||||
from sshuttle.helpers import family_to_string
|
from sshuttle.helpers import family_to_string
|
||||||
from sshuttle.linux import ipt, ipt_ttl, ipt_chain_exists, nonfatal
|
from sshuttle.linux import ipt, ipt_ttl, ipt_chain_exists, nonfatal
|
||||||
from sshuttle.methods import BaseMethod
|
from sshuttle.methods import BaseMethod
|
||||||
@ -38,22 +39,21 @@ class Method(BaseMethod):
|
|||||||
_ipt('-I', 'OUTPUT', '1', '-j', chain)
|
_ipt('-I', 'OUTPUT', '1', '-j', chain)
|
||||||
_ipt('-I', 'PREROUTING', '1', '-j', chain)
|
_ipt('-I', 'PREROUTING', '1', '-j', chain)
|
||||||
|
|
||||||
# create new subnet entries. Note that we're sorting in a very
|
# create new subnet entries.
|
||||||
# particular order: we need to go from most-specific (largest
|
for f, swidth, sexclude, snet, fport, lport \
|
||||||
# swidth) to least-specific, and at any given level of specificity,
|
in sorted(subnets, key=subnet_weight, reverse=True):
|
||||||
# we want excludes to come first. That's why the columns are in
|
tcp_ports = ('-p', 'tcp')
|
||||||
# such a non- intuitive order.
|
if fport:
|
||||||
for f, swidth, sexclude, snet \
|
tcp_ports = tcp_ports + ('--dport', '%d:%d' % (fport, lport))
|
||||||
in sorted(subnets, key=lambda s: s[1], reverse=True):
|
|
||||||
if sexclude:
|
if sexclude:
|
||||||
_ipt('-A', chain, '-j', 'RETURN',
|
_ipt('-A', chain, '-j', 'RETURN',
|
||||||
'--dest', '%s/%s' % (snet, swidth),
|
'--dest', '%s/%s' % (snet, swidth),
|
||||||
'-p', 'tcp')
|
*tcp_ports)
|
||||||
else:
|
else:
|
||||||
_ipt_ttl('-A', chain, '-j', 'REDIRECT',
|
_ipt_ttl('-A', chain, '-j', 'REDIRECT',
|
||||||
'--dest', '%s/%s' % (snet, swidth),
|
'--dest', '%s/%s' % (snet, swidth),
|
||||||
'-p', 'tcp',
|
*(tcp_ports + ('--to-ports', str(port))))
|
||||||
'--to-ports', str(port))
|
|
||||||
|
|
||||||
for f, ip in [i for i in nslist if i[0] == family]:
|
for f, ip in [i for i in nslist if i[0] == family]:
|
||||||
_ipt_ttl('-A', chain, '-j', 'REDIRECT',
|
_ipt_ttl('-A', chain, '-j', 'REDIRECT',
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import platform
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
import subprocess as ssubprocess
|
import subprocess as ssubprocess
|
||||||
|
import shlex
|
||||||
from fcntl import ioctl
|
from fcntl import ioctl
|
||||||
from ctypes import c_char, c_uint8, c_uint16, c_uint32, Union, Structure, \
|
from ctypes import c_char, c_uint8, c_uint16, c_uint32, Union, Structure, \
|
||||||
sizeof, addressof, memmove
|
sizeof, addressof, memmove
|
||||||
|
from sshuttle.firewall import subnet_weight
|
||||||
from sshuttle.helpers import debug1, debug2, debug3, Fatal, family_to_string
|
from sshuttle.helpers import debug1, debug2, debug3, Fatal, family_to_string
|
||||||
from sshuttle.methods import BaseMethod
|
from sshuttle.methods import BaseMethod
|
||||||
|
|
||||||
@ -63,6 +66,7 @@ class Generic(object):
|
|||||||
pfctl('-a %s -F all' % anchor)
|
pfctl('-a %s -F all' % anchor)
|
||||||
if _pf_context['started_by_sshuttle']:
|
if _pf_context['started_by_sshuttle']:
|
||||||
pfctl('-d')
|
pfctl('-d')
|
||||||
|
_pf_context['started_by_sshuttle'] = False
|
||||||
|
|
||||||
def query_nat(self, family, proto, src_ip, src_port, dst_ip, dst_port):
|
def query_nat(self, family, proto, src_ip, src_port, dst_ip, dst_port):
|
||||||
[proto, family, src_port, dst_port] = [
|
[proto, family, src_port, dst_port] = [
|
||||||
@ -183,16 +187,18 @@ class FreeBsd(Generic):
|
|||||||
inet_version = self._inet_version(family)
|
inet_version = self._inet_version(family)
|
||||||
lo_addr = self._lo_addr(family)
|
lo_addr = self._lo_addr(family)
|
||||||
|
|
||||||
tables = [
|
tables = []
|
||||||
b'table <forward_subnets> {%s}' % b','.join(includes)
|
|
||||||
]
|
|
||||||
translating_rules = [
|
translating_rules = [
|
||||||
b'rdr pass on lo0 %s proto tcp to <forward_subnets> '
|
b'rdr pass on lo0 %s proto tcp to %s '
|
||||||
b'-> %s port %r' % (inet_version, lo_addr, port)
|
b'-> %s port %r' % (inet_version, subnet, lo_addr, port)
|
||||||
|
for exclude, subnet in includes if not exclude
|
||||||
]
|
]
|
||||||
filtering_rules = [
|
filtering_rules = [
|
||||||
b'pass out route-to lo0 %s proto tcp '
|
b'pass out route-to lo0 %s proto tcp '
|
||||||
b'to <forward_subnets> keep state' % inet_version
|
b'to %s keep state' % (inet_version, subnet)
|
||||||
|
if not exclude else
|
||||||
|
b'pass out quick %s proto tcp to %s' % (inet_version, subnet)
|
||||||
|
for exclude, subnet in includes
|
||||||
]
|
]
|
||||||
|
|
||||||
if len(nslist) > 0:
|
if len(nslist) > 0:
|
||||||
@ -251,16 +257,18 @@ class OpenBsd(Generic):
|
|||||||
inet_version = self._inet_version(family)
|
inet_version = self._inet_version(family)
|
||||||
lo_addr = self._lo_addr(family)
|
lo_addr = self._lo_addr(family)
|
||||||
|
|
||||||
tables = [
|
tables = []
|
||||||
b'table <forward_subnets> {%s}' % b','.join(includes)
|
|
||||||
]
|
|
||||||
translating_rules = [
|
translating_rules = [
|
||||||
b'pass in on lo0 %s proto tcp to <forward_subnets> '
|
b'pass in on lo0 %s proto tcp to %s '
|
||||||
b'divert-to %s port %r' % (inet_version, lo_addr, port)
|
b'divert-to %s port %r' % (inet_version, subnet, lo_addr, port)
|
||||||
|
for exclude, subnet in includes if not exclude
|
||||||
]
|
]
|
||||||
filtering_rules = [
|
filtering_rules = [
|
||||||
b'pass out %s proto tcp to <forward_subnets> '
|
b'pass out %s proto tcp to %s '
|
||||||
b'route-to lo0 keep state' % inet_version
|
b'route-to lo0 keep state' % (inet_version, subnet)
|
||||||
|
if not exclude else
|
||||||
|
b'pass out quick %s proto tcp to %s' % (inet_version, subnet)
|
||||||
|
for exclude, subnet in includes
|
||||||
]
|
]
|
||||||
|
|
||||||
if len(nslist) > 0:
|
if len(nslist) > 0:
|
||||||
@ -333,16 +341,26 @@ class Darwin(FreeBsd):
|
|||||||
return xport.port
|
return xport.port
|
||||||
|
|
||||||
|
|
||||||
|
class PfSense(FreeBsd):
|
||||||
|
RULE_ACTION_OFFSET = 3040
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.pfioc_rule = c_char * 3112
|
||||||
|
super(PfSense, self).__init__()
|
||||||
|
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'darwin':
|
||||||
pf = Darwin()
|
pf = Darwin()
|
||||||
elif sys.platform.startswith('openbsd'):
|
elif sys.platform.startswith('openbsd'):
|
||||||
pf = OpenBsd()
|
pf = OpenBsd()
|
||||||
|
elif platform.version().endswith('pfSense'):
|
||||||
|
pf = PfSense()
|
||||||
else:
|
else:
|
||||||
pf = FreeBsd()
|
pf = FreeBsd()
|
||||||
|
|
||||||
|
|
||||||
def pfctl(args, stdin=None):
|
def pfctl(args, stdin=None):
|
||||||
argv = ['pfctl'] + list(args.split(" "))
|
argv = ['pfctl'] + shlex.split(args)
|
||||||
debug1('>> %s\n' % ' '.join(argv))
|
debug1('>> %s\n' % ' '.join(argv))
|
||||||
|
|
||||||
env = {
|
env = {
|
||||||
@ -416,12 +434,12 @@ class Method(BaseMethod):
|
|||||||
# If a given subnet is both included and excluded, list the
|
# If a given subnet is both included and excluded, list the
|
||||||
# exclusion first; the table will ignore the second, opposite
|
# exclusion first; the table will ignore the second, opposite
|
||||||
# definition
|
# definition
|
||||||
for f, swidth, sexclude, snet in sorted(
|
for f, swidth, sexclude, snet, fport, lport \
|
||||||
subnets, key=lambda s: (s[1], s[2]), reverse=True):
|
in sorted(subnets, key=subnet_weight, reverse=True):
|
||||||
includes.append(b"%s%s/%d" %
|
includes.append((sexclude, b"%s/%d%s" % (
|
||||||
(b"!" if sexclude else b"",
|
snet.encode("ASCII"),
|
||||||
snet.encode("ASCII"),
|
swidth,
|
||||||
swidth))
|
b" port %d:%d" % (fport, lport) if fport else b"")))
|
||||||
|
|
||||||
anchor = pf_get_anchor(family, port)
|
anchor = pf_get_anchor(family, port)
|
||||||
pf.add_anchors(anchor)
|
pf.add_anchors(anchor)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import struct
|
import struct
|
||||||
|
from sshuttle.firewall import subnet_weight
|
||||||
from sshuttle.helpers import family_to_string
|
from sshuttle.helpers import family_to_string
|
||||||
from sshuttle.linux import ipt, ipt_ttl, ipt_chain_exists
|
from sshuttle.linux import ipt, ipt_ttl, ipt_chain_exists
|
||||||
from sshuttle.methods import BaseMethod
|
from sshuttle.methods import BaseMethod
|
||||||
@ -163,6 +164,11 @@ class Method(BaseMethod):
|
|||||||
def _ipt_ttl(*args):
|
def _ipt_ttl(*args):
|
||||||
return ipt_ttl(family, table, *args)
|
return ipt_ttl(family, table, *args)
|
||||||
|
|
||||||
|
def _ipt_proto_ports(proto, fport, lport):
|
||||||
|
return proto + ('--dport', '%d:%d' % (fport, lport)) \
|
||||||
|
if fport else proto
|
||||||
|
|
||||||
|
|
||||||
mark_chain = 'sshuttle-m-%s' % port
|
mark_chain = 'sshuttle-m-%s' % port
|
||||||
tproxy_chain = 'sshuttle-t-%s' % port
|
tproxy_chain = 'sshuttle-t-%s' % port
|
||||||
divert_chain = 'sshuttle-d-%s' % port
|
divert_chain = 'sshuttle-d-%s' % port
|
||||||
@ -197,33 +203,44 @@ class Method(BaseMethod):
|
|||||||
'-m', 'udp', '-p', 'udp', '--dport', '53',
|
'-m', 'udp', '-p', 'udp', '--dport', '53',
|
||||||
'--on-port', str(dnsport))
|
'--on-port', str(dnsport))
|
||||||
|
|
||||||
for f, swidth, sexclude, snet \
|
for f, swidth, sexclude, snet, fport, lport \
|
||||||
in sorted(subnets, key=lambda s: s[1], reverse=True):
|
in sorted(subnets, key=subnet_weight, reverse=True):
|
||||||
|
tcp_ports = ('-p', 'tcp')
|
||||||
|
tcp_ports = _ipt_proto_ports(tcp_ports, fport, lport)
|
||||||
|
|
||||||
if sexclude:
|
if sexclude:
|
||||||
_ipt('-A', mark_chain, '-j', 'RETURN',
|
_ipt('-A', mark_chain, '-j', 'RETURN',
|
||||||
'--dest', '%s/%s' % (snet, swidth),
|
'--dest', '%s/%s' % (snet, swidth),
|
||||||
'-m', 'tcp', '-p', 'tcp')
|
'-m', 'tcp',
|
||||||
|
*tcp_ports)
|
||||||
_ipt('-A', tproxy_chain, '-j', 'RETURN',
|
_ipt('-A', tproxy_chain, '-j', 'RETURN',
|
||||||
'--dest', '%s/%s' % (snet, swidth),
|
'--dest', '%s/%s' % (snet, swidth),
|
||||||
'-m', 'tcp', '-p', 'tcp')
|
'-m', 'tcp',
|
||||||
|
*tcp_ports)
|
||||||
else:
|
else:
|
||||||
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
|
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
|
||||||
'--dest', '%s/%s' % (snet, swidth),
|
'--dest', '%s/%s' % (snet, swidth),
|
||||||
'-m', 'tcp', '-p', 'tcp')
|
'-m', 'tcp',
|
||||||
|
*tcp_ports)
|
||||||
_ipt('-A', tproxy_chain, '-j', 'TPROXY',
|
_ipt('-A', tproxy_chain, '-j', 'TPROXY',
|
||||||
'--tproxy-mark', '0x1/0x1',
|
'--tproxy-mark', '0x1/0x1',
|
||||||
'--dest', '%s/%s' % (snet, swidth),
|
'--dest', '%s/%s' % (snet, swidth),
|
||||||
'-m', 'tcp', '-p', 'tcp',
|
'-m', 'tcp',
|
||||||
'--on-port', str(port))
|
*(tcp_ports + ('--on-port', str(port))))
|
||||||
|
|
||||||
if udp:
|
if udp:
|
||||||
|
udp_ports = ('-p', 'udp')
|
||||||
|
udp_ports = _ipt_proto_ports(udp_ports, fport, lport)
|
||||||
|
|
||||||
if sexclude:
|
if sexclude:
|
||||||
_ipt('-A', mark_chain, '-j', 'RETURN',
|
_ipt('-A', mark_chain, '-j', 'RETURN',
|
||||||
'--dest', '%s/%s' % (snet, swidth),
|
'--dest', '%s/%s' % (snet, swidth),
|
||||||
'-m', 'udp', '-p', 'udp')
|
'-m', 'udp',
|
||||||
|
*udp_ports)
|
||||||
_ipt('-A', tproxy_chain, '-j', 'RETURN',
|
_ipt('-A', tproxy_chain, '-j', 'RETURN',
|
||||||
'--dest', '%s/%s' % (snet, swidth),
|
'--dest', '%s/%s' % (snet, swidth),
|
||||||
'-m', 'udp', '-p', 'udp')
|
'-m', 'udp',
|
||||||
|
*udp_ports)
|
||||||
else:
|
else:
|
||||||
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
|
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
|
||||||
'--dest', '%s/%s' % (snet, swidth),
|
'--dest', '%s/%s' % (snet, swidth),
|
||||||
@ -231,8 +248,8 @@ class Method(BaseMethod):
|
|||||||
_ipt('-A', tproxy_chain, '-j', 'TPROXY',
|
_ipt('-A', tproxy_chain, '-j', 'TPROXY',
|
||||||
'--tproxy-mark', '0x1/0x1',
|
'--tproxy-mark', '0x1/0x1',
|
||||||
'--dest', '%s/%s' % (snet, swidth),
|
'--dest', '%s/%s' % (snet, swidth),
|
||||||
'-m', 'udp', '-p', 'udp',
|
'-m', 'udp',
|
||||||
'--on-port', str(port))
|
*(udp_ports + ('--on-port', str(port))))
|
||||||
|
|
||||||
def restore_firewall(self, port, family, udp):
|
def restore_firewall(self, port, family, udp):
|
||||||
if family not in [socket.AF_INET, socket.AF_INET6]:
|
if family not in [socket.AF_INET, socket.AF_INET6]:
|
||||||
|
@ -4,41 +4,8 @@ from argparse import ArgumentParser, Action, ArgumentTypeError as Fatal
|
|||||||
from sshuttle import __version__
|
from sshuttle import __version__
|
||||||
|
|
||||||
|
|
||||||
# 1.2.3.4/5 or just 1.2.3.4
|
|
||||||
def parse_subnet4(s):
|
|
||||||
m = re.match(r'(\d+)(?:\.(\d+)\.(\d+)\.(\d+))?(?:/(\d+))?$', s)
|
|
||||||
if not m:
|
|
||||||
raise Fatal('%r is not a valid IP subnet format' % s)
|
|
||||||
(a, b, c, d, width) = m.groups()
|
|
||||||
(a, b, c, d) = (int(a or 0), int(b or 0), int(c or 0), int(d or 0))
|
|
||||||
if width is None:
|
|
||||||
width = 32
|
|
||||||
else:
|
|
||||||
width = int(width)
|
|
||||||
if a > 255 or b > 255 or c > 255 or d > 255:
|
|
||||||
raise Fatal('%d.%d.%d.%d has numbers > 255' % (a, b, c, d))
|
|
||||||
if width > 32:
|
|
||||||
raise Fatal('*/%d is greater than the maximum of 32' % width)
|
|
||||||
return(socket.AF_INET, '%d.%d.%d.%d' % (a, b, c, d), width)
|
|
||||||
|
|
||||||
|
|
||||||
# 1:2::3/64 or just 1:2::3
|
|
||||||
def parse_subnet6(s):
|
|
||||||
m = re.match(r'(?:([a-fA-F\d:]+))?(?:/(\d+))?$', s)
|
|
||||||
if not m:
|
|
||||||
raise Fatal('%r is not a valid IP subnet format' % s)
|
|
||||||
(net, width) = m.groups()
|
|
||||||
if width is None:
|
|
||||||
width = 128
|
|
||||||
else:
|
|
||||||
width = int(width)
|
|
||||||
if width > 128:
|
|
||||||
raise Fatal('*/%d is greater than the maximum of 128' % width)
|
|
||||||
return(socket.AF_INET6, net, width)
|
|
||||||
|
|
||||||
|
|
||||||
# Subnet file, supporting empty lines and hash-started comment lines
|
# Subnet file, supporting empty lines and hash-started comment lines
|
||||||
def parse_subnet_file(s):
|
def parse_subnetport_file(s):
|
||||||
try:
|
try:
|
||||||
handle = open(s, 'r')
|
handle = open(s, 'r')
|
||||||
except OSError:
|
except OSError:
|
||||||
@ -52,47 +19,66 @@ def parse_subnet_file(s):
|
|||||||
continue
|
continue
|
||||||
if line[0] == '#':
|
if line[0] == '#':
|
||||||
continue
|
continue
|
||||||
subnets.append(parse_subnet(line))
|
subnets.append(parse_subnetport(line))
|
||||||
|
|
||||||
return subnets
|
return subnets
|
||||||
|
|
||||||
|
|
||||||
# 1.2.3.4/5 or just 1.2.3.4
|
# 1.2.3.4/5:678, 1.2.3.4:567, 1.2.3.4/16 or just 1.2.3.4
|
||||||
# 1:2::3/64 or just 1:2::3
|
# [1:2::3/64]:456, [1:2::3]:456, 1:2::3/64 or just 1:2::3
|
||||||
def parse_subnet(subnet_str):
|
# example.com:123 or just example.com
|
||||||
if ':' in subnet_str:
|
def parse_subnetport(s):
|
||||||
return parse_subnet6(subnet_str)
|
if s.count(':') > 1:
|
||||||
|
rx = r'(?:\[?([\w\:]+)(?:/(\d+))?]?)(?::(\d+)(?:-(\d+))?)?$'
|
||||||
else:
|
else:
|
||||||
return parse_subnet4(subnet_str)
|
rx = r'([\w\.]+)(?:/(\d+))?(?::(\d+)(?:-(\d+))?)?$'
|
||||||
|
|
||||||
|
m = re.match(rx, s)
|
||||||
|
if not m:
|
||||||
|
raise Fatal('%r is not a valid address/mask:port format' % s)
|
||||||
|
|
||||||
|
addr, width, fport, lport = m.groups()
|
||||||
|
try:
|
||||||
|
addrinfo = socket.getaddrinfo(addr, 0, 0, socket.SOCK_STREAM)
|
||||||
|
except socket.gaierror:
|
||||||
|
raise Fatal('Unable to resolve address: %s' % addr)
|
||||||
|
|
||||||
|
family, _, _, _, addr = min(addrinfo)
|
||||||
|
max_width = 32 if family == socket.AF_INET else 128
|
||||||
|
width = int(width or max_width)
|
||||||
|
if not 0 <= width <= max_width:
|
||||||
|
raise Fatal('width %d is not between 0 and %d' % (width, max_width))
|
||||||
|
|
||||||
|
return (family, addr[0], width, int(fport or 0), int(lport or fport or 0))
|
||||||
|
|
||||||
|
|
||||||
# 1.2.3.4:567 or just 1.2.3.4 or just 567
|
# 1.2.3.4:567 or just 1.2.3.4 or just 567
|
||||||
def parse_ipport4(s):
|
# [1:2::3]:456 or [1:2::3] or just [::]:567
|
||||||
|
# example.com:123 or just example.com
|
||||||
|
def parse_ipport(s):
|
||||||
s = str(s)
|
s = str(s)
|
||||||
m = re.match(r'(?:(\d+)\.(\d+)\.(\d+)\.(\d+))?(?::)?(?:(\d+))?$', s)
|
if s.isdigit():
|
||||||
|
rx = r'()(\d+)$'
|
||||||
|
elif ']' in s:
|
||||||
|
rx = r'(?:\[([^]]+)])(?::(\d+))?$'
|
||||||
|
else:
|
||||||
|
rx = r'([\w\.]+)(?::(\d+))?$'
|
||||||
|
|
||||||
|
m = re.match(rx, s)
|
||||||
if not m:
|
if not m:
|
||||||
raise Fatal('%r is not a valid IP:port format' % s)
|
raise Fatal('%r is not a valid IP:port format' % s)
|
||||||
(a, b, c, d, port) = m.groups()
|
|
||||||
(a, b, c, d, port) = (int(a or 0), int(b or 0), int(c or 0), int(d or 0),
|
|
||||||
int(port or 0))
|
|
||||||
if a > 255 or b > 255 or c > 255 or d > 255:
|
|
||||||
raise Fatal('%d.%d.%d.%d has numbers > 255' % (a, b, c, d))
|
|
||||||
if port > 65535:
|
|
||||||
raise Fatal('*:%d is greater than the maximum of 65535' % port)
|
|
||||||
if a is None:
|
|
||||||
a = b = c = d = 0
|
|
||||||
return ('%d.%d.%d.%d' % (a, b, c, d), port)
|
|
||||||
|
|
||||||
|
ip, port = m.groups()
|
||||||
|
ip = ip or '0.0.0.0'
|
||||||
|
port = int(port or 0)
|
||||||
|
|
||||||
# [1:2::3]:456 or [1:2::3] or 456
|
try:
|
||||||
def parse_ipport6(s):
|
addrinfo = socket.getaddrinfo(ip, port, 0, socket.SOCK_STREAM)
|
||||||
s = str(s)
|
except socket.gaierror:
|
||||||
m = re.match(r'(?:\[([^]]*)])?(?::)?(?:(\d+))?$', s)
|
raise Fatal('%r is not a valid IP:port format' % s)
|
||||||
if not m:
|
|
||||||
raise Fatal('%s is not a valid IP:port format' % s)
|
family, _, _, _, addr = min(addrinfo)
|
||||||
(ip, port) = m.groups()
|
return (family,) + addr[:2]
|
||||||
(ip, port) = (ip or '::', int(port or 0))
|
|
||||||
return (ip, port)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_list(list):
|
def parse_list(list):
|
||||||
@ -106,7 +92,7 @@ class Concat(Action):
|
|||||||
super(Concat, self).__init__(option_strings, dest, **kwargs)
|
super(Concat, self).__init__(option_strings, dest, **kwargs)
|
||||||
|
|
||||||
def __call__(self, parser, namespace, values, option_string=None):
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
curr_value = getattr(namespace, self.dest, [])
|
curr_value = getattr(namespace, self.dest, None) or []
|
||||||
setattr(namespace, self.dest, curr_value + values)
|
setattr(namespace, self.dest, curr_value + values)
|
||||||
|
|
||||||
|
|
||||||
@ -116,9 +102,9 @@ parser = ArgumentParser(
|
|||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"subnets",
|
"subnets",
|
||||||
metavar="IP/MASK [IP/MASK...]",
|
metavar="IP/MASK[:PORT[-PORT]]...",
|
||||||
nargs="*",
|
nargs="*",
|
||||||
type=parse_subnet,
|
type=parse_subnetport,
|
||||||
help="""
|
help="""
|
||||||
capture and forward traffic to these subnets (whitespace separated)
|
capture and forward traffic to these subnets (whitespace separated)
|
||||||
"""
|
"""
|
||||||
@ -162,7 +148,7 @@ parser.add_argument(
|
|||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--method",
|
"--method",
|
||||||
choices=["auto", "nat", "tproxy", "pf"],
|
choices=["auto", "nat", "tproxy", "pf", "ipfw"],
|
||||||
metavar="TYPE",
|
metavar="TYPE",
|
||||||
default="auto",
|
default="auto",
|
||||||
help="""
|
help="""
|
||||||
@ -185,10 +171,10 @@ parser.add_argument(
|
|||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-x", "--exclude",
|
"-x", "--exclude",
|
||||||
metavar="IP/MASK",
|
metavar="IP/MASK[:PORT[-PORT]]",
|
||||||
action="append",
|
action="append",
|
||||||
default=[],
|
default=[],
|
||||||
type=parse_subnet,
|
type=parse_subnetport,
|
||||||
help="""
|
help="""
|
||||||
exclude this subnet (can be used more than once)
|
exclude this subnet (can be used more than once)
|
||||||
"""
|
"""
|
||||||
@ -198,7 +184,7 @@ parser.add_argument(
|
|||||||
metavar="PATH",
|
metavar="PATH",
|
||||||
action=Concat,
|
action=Concat,
|
||||||
dest="exclude",
|
dest="exclude",
|
||||||
type=parse_subnet_file,
|
type=parse_subnetport_file,
|
||||||
help="""
|
help="""
|
||||||
exclude the subnets in a file (whitespace separated)
|
exclude the subnets in a file (whitespace separated)
|
||||||
"""
|
"""
|
||||||
@ -269,8 +255,9 @@ parser.add_argument(
|
|||||||
"-s", "--subnets",
|
"-s", "--subnets",
|
||||||
metavar="PATH",
|
metavar="PATH",
|
||||||
action=Concat,
|
action=Concat,
|
||||||
dest="subnets",
|
dest="subnets_file",
|
||||||
type=parse_subnet_file,
|
default=[],
|
||||||
|
type=parse_subnetport_file,
|
||||||
help="""
|
help="""
|
||||||
file where the subnets are stored, instead of on the command line
|
file where the subnets are stored, instead of on the command line
|
||||||
"""
|
"""
|
||||||
|
40
sshuttle/sdnotify.py
Normal file
40
sshuttle/sdnotify.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import socket
|
||||||
|
import os
|
||||||
|
from sshuttle.helpers import debug1
|
||||||
|
|
||||||
|
def _notify(message):
|
||||||
|
addr = os.environ.get("NOTIFY_SOCKET", None)
|
||||||
|
|
||||||
|
if not addr or len(addr) == 1 or addr[0] not in ('/', '@'):
|
||||||
|
return False
|
||||||
|
|
||||||
|
addr = '\0' + addr[1:] if addr[0] == '@' else addr
|
||||||
|
|
||||||
|
try:
|
||||||
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
debug1("Error creating socket to notify systemd: %s\n" % e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not message:
|
||||||
|
return False
|
||||||
|
|
||||||
|
assert isinstance(message, bytes)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return (sock.sendto(message, addr) > 0)
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
debug1("Error notifying systemd: %s\n" % e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send(*messages):
|
||||||
|
return _notify(b'\n'.join(messages))
|
||||||
|
|
||||||
|
def ready():
|
||||||
|
return b"READY=1"
|
||||||
|
|
||||||
|
def stop():
|
||||||
|
return b"STOPPING=1"
|
||||||
|
|
||||||
|
def status(message):
|
||||||
|
return b"STATUS=%s" % message.encode('utf8')
|
@ -15,6 +15,11 @@ from sshuttle.ssnet import Handler, Proxy, Mux, MuxWrapper
|
|||||||
from sshuttle.helpers import b, log, debug1, debug2, debug3, Fatal, \
|
from sshuttle.helpers import b, log, debug1, debug2, debug3, Fatal, \
|
||||||
resolvconf_random_nameserver
|
resolvconf_random_nameserver
|
||||||
|
|
||||||
|
try:
|
||||||
|
from shutil import which
|
||||||
|
except ImportError:
|
||||||
|
from distutils.spawn import find_executable as which
|
||||||
|
|
||||||
|
|
||||||
def _ipmatch(ipstr):
|
def _ipmatch(ipstr):
|
||||||
# FIXME: IPv4 only
|
# FIXME: IPv4 only
|
||||||
@ -60,9 +65,27 @@ def _shl(n, bits):
|
|||||||
return n * int(2 ** bits)
|
return n * int(2 ** bits)
|
||||||
|
|
||||||
|
|
||||||
def _list_routes():
|
def _route_netstat(line):
|
||||||
|
cols = line.split(None)
|
||||||
|
if len(cols) < 3:
|
||||||
|
return None, None
|
||||||
|
ipw = _ipmatch(cols[0])
|
||||||
|
maskw = _ipmatch(cols[2]) # linux only
|
||||||
|
mask = _maskbits(maskw) # returns 32 if maskw is null
|
||||||
|
return ipw, mask
|
||||||
|
|
||||||
|
|
||||||
|
def _route_iproute(line):
|
||||||
|
ipm = line.split(None, 1)[0]
|
||||||
|
if '/' not in ipm:
|
||||||
|
return None, None
|
||||||
|
ip, mask = ipm.split('/')
|
||||||
|
ipw = _ipmatch(ip)
|
||||||
|
return ipw, int(mask)
|
||||||
|
|
||||||
|
|
||||||
|
def _list_routes(argv, extract_route):
|
||||||
# FIXME: IPv4 only
|
# FIXME: IPv4 only
|
||||||
argv = ['netstat', '-rn']
|
|
||||||
env = {
|
env = {
|
||||||
'PATH': os.environ['PATH'],
|
'PATH': os.environ['PATH'],
|
||||||
'LC_ALL': "C",
|
'LC_ALL': "C",
|
||||||
@ -70,12 +93,11 @@ def _list_routes():
|
|||||||
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=env)
|
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=env)
|
||||||
routes = []
|
routes = []
|
||||||
for line in p.stdout:
|
for line in p.stdout:
|
||||||
cols = re.split(r'\s+', line.decode("ASCII"))
|
if not line.strip():
|
||||||
ipw = _ipmatch(cols[0])
|
continue
|
||||||
|
ipw, mask = extract_route(line.decode("ASCII"))
|
||||||
if not ipw:
|
if not ipw:
|
||||||
continue # some lines won't be parseable; never mind
|
continue
|
||||||
maskw = _ipmatch(cols[2]) # linux only
|
|
||||||
mask = _maskbits(maskw) # returns 32 if maskw is null
|
|
||||||
width = min(ipw[1], mask)
|
width = min(ipw[1], mask)
|
||||||
ip = ipw[0] & _shl(_shl(1, width) - 1, 32 - width)
|
ip = ipw[0] & _shl(_shl(1, width) - 1, 32 - width)
|
||||||
routes.append(
|
routes.append(
|
||||||
@ -84,11 +106,20 @@ def _list_routes():
|
|||||||
if rv != 0:
|
if rv != 0:
|
||||||
log('WARNING: %r returned %d\n' % (argv, rv))
|
log('WARNING: %r returned %d\n' % (argv, rv))
|
||||||
log('WARNING: That prevents --auto-nets from working.\n')
|
log('WARNING: That prevents --auto-nets from working.\n')
|
||||||
|
|
||||||
return routes
|
return routes
|
||||||
|
|
||||||
|
|
||||||
def list_routes():
|
def list_routes():
|
||||||
for (family, ip, width) in _list_routes():
|
if which('ip'):
|
||||||
|
routes = _list_routes(['ip', 'route'], _route_iproute)
|
||||||
|
elif which('netstat'):
|
||||||
|
routes = _list_routes(['netstat', '-rn'], _route_netstat)
|
||||||
|
else:
|
||||||
|
log('WARNING: Neither ip nor netstat were found on the server.\n')
|
||||||
|
routes = []
|
||||||
|
|
||||||
|
for (family, ip, width) in routes:
|
||||||
if not ip.startswith('0.') and not ip.startswith('127.'):
|
if not ip.startswith('0.') and not ip.startswith('127.'):
|
||||||
yield (family, ip, width)
|
yield (family, ip, width)
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import socket
|
|||||||
import zlib
|
import zlib
|
||||||
import imp
|
import imp
|
||||||
import subprocess as ssubprocess
|
import subprocess as ssubprocess
|
||||||
|
import shlex
|
||||||
import sshuttle.helpers as helpers
|
import sshuttle.helpers as helpers
|
||||||
from sshuttle.helpers import debug2
|
from sshuttle.helpers import debug2
|
||||||
|
|
||||||
@ -62,7 +63,7 @@ def empackage(z, name, data=None):
|
|||||||
def connect(ssh_cmd, rhostport, python, stderr, options):
|
def connect(ssh_cmd, rhostport, python, stderr, options):
|
||||||
portl = []
|
portl = []
|
||||||
|
|
||||||
if (rhostport or '').count(':') > 1:
|
if re.sub(r'.*@', '', rhostport or '').count(':') > 1:
|
||||||
if rhostport.count(']') or rhostport.count('['):
|
if rhostport.count(']') or rhostport.count('['):
|
||||||
result = rhostport.split(']')
|
result = rhostport.split(']')
|
||||||
rhost = result[0].strip('[')
|
rhost = result[0].strip('[')
|
||||||
@ -75,7 +76,7 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
|
|||||||
else:
|
else:
|
||||||
rhost = rhostport
|
rhost = rhostport
|
||||||
else: # IPv4
|
else: # IPv4
|
||||||
l = (rhostport or '').split(':', 1)
|
l = (rhostport or '').rsplit(':', 1)
|
||||||
rhost = l[0]
|
rhost = l[0]
|
||||||
if len(l) > 1:
|
if len(l) > 1:
|
||||||
portl = ['-p', str(int(l[1]))]
|
portl = ['-p', str(int(l[1]))]
|
||||||
@ -109,7 +110,7 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
|
|||||||
argv = [sys.executable, '-c', pyscript]
|
argv = [sys.executable, '-c', pyscript]
|
||||||
else:
|
else:
|
||||||
if ssh_cmd:
|
if ssh_cmd:
|
||||||
sshl = ssh_cmd.split(' ')
|
sshl = shlex.split(ssh_cmd)
|
||||||
else:
|
else:
|
||||||
sshl = ['ssh']
|
sshl = ['ssh']
|
||||||
if python:
|
if python:
|
||||||
|
@ -503,17 +503,25 @@ class MuxWrapper(SockWrapper):
|
|||||||
return 'SW%r:Mux#%d' % (self.peername, self.channel)
|
return 'SW%r:Mux#%d' % (self.peername, self.channel)
|
||||||
|
|
||||||
def noread(self):
|
def noread(self):
|
||||||
|
if not self.shut_read:
|
||||||
|
self.mux.send(self.channel, CMD_TCP_STOP_SENDING, b(''))
|
||||||
|
self.setnoread()
|
||||||
|
|
||||||
|
def setnoread(self):
|
||||||
if not self.shut_read:
|
if not self.shut_read:
|
||||||
debug2('%r: done reading\n' % self)
|
debug2('%r: done reading\n' % self)
|
||||||
self.shut_read = True
|
self.shut_read = True
|
||||||
self.mux.send(self.channel, CMD_TCP_STOP_SENDING, b(''))
|
|
||||||
self.maybe_close()
|
self.maybe_close()
|
||||||
|
|
||||||
def nowrite(self):
|
def nowrite(self):
|
||||||
|
if not self.shut_write:
|
||||||
|
self.mux.send(self.channel, CMD_TCP_EOF, b(''))
|
||||||
|
self.setnowrite()
|
||||||
|
|
||||||
|
def setnowrite(self):
|
||||||
if not self.shut_write:
|
if not self.shut_write:
|
||||||
debug2('%r: done writing\n' % self)
|
debug2('%r: done writing\n' % self)
|
||||||
self.shut_write = True
|
self.shut_write = True
|
||||||
self.mux.send(self.channel, CMD_TCP_EOF, b(''))
|
|
||||||
self.maybe_close()
|
self.maybe_close()
|
||||||
|
|
||||||
def maybe_close(self):
|
def maybe_close(self):
|
||||||
@ -542,9 +550,11 @@ class MuxWrapper(SockWrapper):
|
|||||||
|
|
||||||
def got_packet(self, cmd, data):
|
def got_packet(self, cmd, data):
|
||||||
if cmd == CMD_TCP_EOF:
|
if cmd == CMD_TCP_EOF:
|
||||||
self.noread()
|
# Remote side already knows the status - set flag but don't notify
|
||||||
|
self.setnoread()
|
||||||
elif cmd == CMD_TCP_STOP_SENDING:
|
elif cmd == CMD_TCP_STOP_SENDING:
|
||||||
self.nowrite()
|
# Remote side already knows the status - set flag but don't notify
|
||||||
|
self.setnowrite()
|
||||||
elif cmd == CMD_TCP_DATA:
|
elif cmd == CMD_TCP_DATA:
|
||||||
self.buf.append(data)
|
self.buf.append(data)
|
||||||
else:
|
else:
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
from mock import Mock, patch, call
|
from mock import Mock, patch, call
|
||||||
import io
|
import io
|
||||||
|
import socket
|
||||||
|
|
||||||
import sshuttle.firewall
|
import sshuttle.firewall
|
||||||
|
|
||||||
|
|
||||||
def setup_daemon():
|
def setup_daemon():
|
||||||
stdin = io.StringIO(u"""ROUTES
|
stdin = io.StringIO(u"""ROUTES
|
||||||
2,24,0,1.2.3.0
|
2,24,0,1.2.3.0,8000,9000
|
||||||
2,32,1,1.2.3.66
|
2,32,1,1.2.3.66,8080,8080
|
||||||
10,64,0,2404:6800:4004:80c::
|
10,64,0,2404:6800:4004:80c::,0,0
|
||||||
10,128,1,2404:6800:4004:80c::101f
|
10,128,1,2404:6800:4004:80c::101f,80,80
|
||||||
NSLIST
|
NSLIST
|
||||||
2,1.2.3.33
|
2,1.2.3.33
|
||||||
10,2404:6800:4004:80c::33
|
10,2404:6800:4004:80c::33
|
||||||
@ -58,6 +59,36 @@ def test_rewrite_etc_hosts(tmpdir):
|
|||||||
assert orig_hosts.computehash() == new_hosts.computehash()
|
assert orig_hosts.computehash() == new_hosts.computehash()
|
||||||
|
|
||||||
|
|
||||||
|
def test_subnet_weight():
|
||||||
|
subnets = [
|
||||||
|
(socket.AF_INET, 16, 0, '192.168.0.0', 0, 0),
|
||||||
|
(socket.AF_INET, 24, 0, '192.168.69.0', 0, 0),
|
||||||
|
(socket.AF_INET, 32, 0, '192.168.69.70', 0, 0),
|
||||||
|
(socket.AF_INET, 32, 1, '192.168.69.70', 0, 0),
|
||||||
|
(socket.AF_INET, 32, 1, '192.168.69.70', 80, 80),
|
||||||
|
(socket.AF_INET, 0, 1, '0.0.0.0', 0, 0),
|
||||||
|
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 9000),
|
||||||
|
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 8500),
|
||||||
|
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 8000),
|
||||||
|
(socket.AF_INET, 0, 1, '0.0.0.0', 400, 450)
|
||||||
|
]
|
||||||
|
subnets_sorted = [
|
||||||
|
(socket.AF_INET, 32, 1, '192.168.69.70', 80, 80),
|
||||||
|
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 8000),
|
||||||
|
(socket.AF_INET, 0, 1, '0.0.0.0', 400, 450),
|
||||||
|
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 8500),
|
||||||
|
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 9000),
|
||||||
|
(socket.AF_INET, 32, 1, '192.168.69.70', 0, 0),
|
||||||
|
(socket.AF_INET, 32, 0, '192.168.69.70', 0, 0),
|
||||||
|
(socket.AF_INET, 24, 0, '192.168.69.0', 0, 0),
|
||||||
|
(socket.AF_INET, 16, 0, '192.168.0.0', 0, 0),
|
||||||
|
(socket.AF_INET, 0, 1, '0.0.0.0', 0, 0)
|
||||||
|
]
|
||||||
|
|
||||||
|
assert subnets_sorted == \
|
||||||
|
sorted(subnets, key=sshuttle.firewall.subnet_weight, reverse=True)
|
||||||
|
|
||||||
|
|
||||||
@patch('sshuttle.firewall.rewrite_etc_hosts')
|
@patch('sshuttle.firewall.rewrite_etc_hosts')
|
||||||
@patch('sshuttle.firewall.setup_daemon')
|
@patch('sshuttle.firewall.setup_daemon')
|
||||||
@patch('sshuttle.firewall.get_method')
|
@patch('sshuttle.firewall.get_method')
|
||||||
@ -88,14 +119,15 @@ def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts):
|
|||||||
1024, 1026,
|
1024, 1026,
|
||||||
[(10, u'2404:6800:4004:80c::33')],
|
[(10, u'2404:6800:4004:80c::33')],
|
||||||
10,
|
10,
|
||||||
[(10, 64, False, u'2404:6800:4004:80c::'),
|
[(10, 64, False, u'2404:6800:4004:80c::', 0, 0),
|
||||||
(10, 128, True, u'2404:6800:4004:80c::101f')],
|
(10, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
|
||||||
True),
|
True),
|
||||||
call().setup_firewall(
|
call().setup_firewall(
|
||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(2, u'1.2.3.33')],
|
||||||
2,
|
2,
|
||||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
[(2, 24, False, u'1.2.3.0', 8000, 9000),
|
||||||
|
(2, 32, True, u'1.2.3.66', 8080, 8080)],
|
||||||
True),
|
True),
|
||||||
call().restore_firewall(1024, 10, True),
|
call().restore_firewall(1024, 10, True),
|
||||||
call().restore_firewall(1025, 2, True),
|
call().restore_firewall(1025, 2, True),
|
||||||
|
@ -86,8 +86,8 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
|||||||
1024, 1026,
|
1024, 1026,
|
||||||
[(10, u'2404:6800:4004:80c::33')],
|
[(10, u'2404:6800:4004:80c::33')],
|
||||||
10,
|
10,
|
||||||
[(10, 64, False, u'2404:6800:4004:80c::'),
|
[(10, 64, False, u'2404:6800:4004:80c::', 0, 0),
|
||||||
(10, 128, True, u'2404:6800:4004:80c::101f')],
|
(10, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
|
||||||
True)
|
True)
|
||||||
assert str(excinfo.value) \
|
assert str(excinfo.value) \
|
||||||
== 'Address family "AF_INET6" unsupported by nat method_name'
|
== 'Address family "AF_INET6" unsupported by nat method_name'
|
||||||
@ -100,7 +100,8 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
|||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(2, u'1.2.3.33')],
|
||||||
2,
|
2,
|
||||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
[(2, 24, False, u'1.2.3.0', 8000, 9000),
|
||||||
|
(2, 32, True, u'1.2.3.66', 8080, 8080)],
|
||||||
True)
|
True)
|
||||||
assert str(excinfo.value) == 'UDP not supported by nat method_name'
|
assert str(excinfo.value) == 'UDP not supported by nat method_name'
|
||||||
assert mock_ipt_chain_exists.mock_calls == []
|
assert mock_ipt_chain_exists.mock_calls == []
|
||||||
@ -111,14 +112,16 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
|||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(2, u'1.2.3.33')],
|
||||||
2,
|
2,
|
||||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
[(2, 24, False, u'1.2.3.0', 8000, 9000),
|
||||||
|
(2, 32, True, u'1.2.3.66', 8080, 8080)],
|
||||||
False)
|
False)
|
||||||
assert mock_ipt_chain_exists.mock_calls == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(2, 'nat', 'sshuttle-1025')
|
call(2, 'nat', 'sshuttle-1025')
|
||||||
]
|
]
|
||||||
assert mock_ipt_ttl.mock_calls == [
|
assert mock_ipt_ttl.mock_calls == [
|
||||||
call(2, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
|
call(2, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
|
||||||
'--dest', u'1.2.3.0/24', '-p', 'tcp', '--to-ports', '1025'),
|
'--dest', u'1.2.3.0/24', '-p', 'tcp', '--dport', '8000:9000',
|
||||||
|
'--to-ports', '1025'),
|
||||||
call(2, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
|
call(2, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
|
||||||
'--dest', u'1.2.3.33/32', '-p', 'udp',
|
'--dest', u'1.2.3.33/32', '-p', 'udp',
|
||||||
'--dport', '53', '--to-ports', '1027')
|
'--dport', '53', '--to-ports', '1027')
|
||||||
@ -133,7 +136,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
|||||||
call(2, 'nat', '-I', 'OUTPUT', '1', '-j', 'sshuttle-1025'),
|
call(2, 'nat', '-I', 'OUTPUT', '1', '-j', 'sshuttle-1025'),
|
||||||
call(2, 'nat', '-I', 'PREROUTING', '1', '-j', 'sshuttle-1025'),
|
call(2, 'nat', '-I', 'PREROUTING', '1', '-j', 'sshuttle-1025'),
|
||||||
call(2, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
|
call(2, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
|
||||||
'--dest', u'1.2.3.66/32', '-p', 'tcp')
|
'--dest', u'1.2.3.66/32', '-p', 'tcp', '--dport', '8080:8080')
|
||||||
]
|
]
|
||||||
mock_ipt_chain_exists.reset_mock()
|
mock_ipt_chain_exists.reset_mock()
|
||||||
mock_ipt_ttl.reset_mock()
|
mock_ipt_ttl.reset_mock()
|
||||||
|
@ -182,8 +182,8 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
1024, 1026,
|
1024, 1026,
|
||||||
[(10, u'2404:6800:4004:80c::33')],
|
[(10, u'2404:6800:4004:80c::33')],
|
||||||
10,
|
10,
|
||||||
[(10, 64, False, u'2404:6800:4004:80c::'),
|
[(10, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
|
||||||
(10, 128, True, u'2404:6800:4004:80c::101f')],
|
(10, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||||
False)
|
False)
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||||
@ -198,16 +198,15 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
call('-f /dev/stdin', b'pass on lo\n'),
|
call('-f /dev/stdin', b'pass on lo\n'),
|
||||||
call('-s all'),
|
call('-s all'),
|
||||||
call('-a sshuttle6-1024 -f /dev/stdin',
|
call('-a sshuttle6-1024 -f /dev/stdin',
|
||||||
b'table <forward_subnets> {'
|
|
||||||
b'!2404:6800:4004:80c::101f/128,2404:6800:4004:80c::/64'
|
|
||||||
b'}\n'
|
|
||||||
b'table <dns_servers> {2404:6800:4004:80c::33}\n'
|
b'table <dns_servers> {2404:6800:4004:80c::33}\n'
|
||||||
b'rdr pass on lo0 inet6 proto tcp '
|
b'rdr pass on lo0 inet6 proto tcp to '
|
||||||
b'to <forward_subnets> -> ::1 port 1024\n'
|
b'2404:6800:4004:80c::/64 port 8000:9000 -> ::1 port 1024\n'
|
||||||
b'rdr pass on lo0 inet6 proto udp '
|
b'rdr pass on lo0 inet6 proto udp '
|
||||||
b'to <dns_servers> port 53 -> ::1 port 1026\n'
|
b'to <dns_servers> port 53 -> ::1 port 1026\n'
|
||||||
b'pass out route-to lo0 inet6 proto tcp '
|
b'pass out quick inet6 proto tcp to '
|
||||||
b'to <forward_subnets> keep state\n'
|
b'2404:6800:4004:80c::101f/128 port 8080:8080\n'
|
||||||
|
b'pass out route-to lo0 inet6 proto tcp to '
|
||||||
|
b'2404:6800:4004:80c::/64 port 8000:9000 keep state\n'
|
||||||
b'pass out route-to lo0 inet6 proto udp '
|
b'pass out route-to lo0 inet6 proto udp '
|
||||||
b'to <dns_servers> port 53 keep state\n'),
|
b'to <dns_servers> port 53 keep state\n'),
|
||||||
call('-E'),
|
call('-E'),
|
||||||
@ -221,7 +220,8 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(2, u'1.2.3.33')],
|
||||||
2,
|
2,
|
||||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
[(2, 24, False, u'1.2.3.0', 0, 0),
|
||||||
|
(2, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
True)
|
True)
|
||||||
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
||||||
assert mock_pf_get_dev.mock_calls == []
|
assert mock_pf_get_dev.mock_calls == []
|
||||||
@ -232,7 +232,7 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(2, u'1.2.3.33')],
|
||||||
2,
|
2,
|
||||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
[(2, 24, False, u'1.2.3.0', 0, 0), (2, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
False)
|
False)
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||||
@ -247,14 +247,13 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
call('-f /dev/stdin', b'pass on lo\n'),
|
call('-f /dev/stdin', b'pass on lo\n'),
|
||||||
call('-s all'),
|
call('-s all'),
|
||||||
call('-a sshuttle-1025 -f /dev/stdin',
|
call('-a sshuttle-1025 -f /dev/stdin',
|
||||||
b'table <forward_subnets> {!1.2.3.66/32,1.2.3.0/24}\n'
|
|
||||||
b'table <dns_servers> {1.2.3.33}\n'
|
b'table <dns_servers> {1.2.3.33}\n'
|
||||||
b'rdr pass on lo0 inet proto tcp '
|
b'rdr pass on lo0 inet proto tcp to 1.2.3.0/24 '
|
||||||
b'to <forward_subnets> -> 127.0.0.1 port 1025\n'
|
b'-> 127.0.0.1 port 1025\n'
|
||||||
b'rdr pass on lo0 inet proto udp '
|
b'rdr pass on lo0 inet proto udp '
|
||||||
b'to <dns_servers> port 53 -> 127.0.0.1 port 1027\n'
|
b'to <dns_servers> port 53 -> 127.0.0.1 port 1027\n'
|
||||||
b'pass out route-to lo0 inet proto tcp '
|
b'pass out quick inet proto tcp to 1.2.3.66/32 port 80:80\n'
|
||||||
b'to <forward_subnets> keep state\n'
|
b'pass out route-to lo0 inet proto tcp to 1.2.3.0/24 keep state\n'
|
||||||
b'pass out route-to lo0 inet proto udp '
|
b'pass out route-to lo0 inet proto udp '
|
||||||
b'to <dns_servers> port 53 keep state\n'),
|
b'to <dns_servers> port 53 keep state\n'),
|
||||||
call('-E'),
|
call('-E'),
|
||||||
@ -289,23 +288,22 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
1024, 1026,
|
1024, 1026,
|
||||||
[(10, u'2404:6800:4004:80c::33')],
|
[(10, u'2404:6800:4004:80c::33')],
|
||||||
10,
|
10,
|
||||||
[(10, 64, False, u'2404:6800:4004:80c::'),
|
[(10, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
|
||||||
(10, 128, True, u'2404:6800:4004:80c::101f')],
|
(10, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||||
False)
|
False)
|
||||||
|
|
||||||
assert mock_pfctl.mock_calls == [
|
assert mock_pfctl.mock_calls == [
|
||||||
call('-s all'),
|
call('-s all'),
|
||||||
call('-a sshuttle6-1024 -f /dev/stdin',
|
call('-a sshuttle6-1024 -f /dev/stdin',
|
||||||
b'table <forward_subnets> {'
|
|
||||||
b'!2404:6800:4004:80c::101f/128,2404:6800:4004:80c::/64'
|
|
||||||
b'}\n'
|
|
||||||
b'table <dns_servers> {2404:6800:4004:80c::33}\n'
|
b'table <dns_servers> {2404:6800:4004:80c::33}\n'
|
||||||
b'rdr pass on lo0 inet6 proto tcp '
|
b'rdr pass on lo0 inet6 proto tcp to 2404:6800:4004:80c::/64 '
|
||||||
b'to <forward_subnets> -> ::1 port 1024\n'
|
b'port 8000:9000 -> ::1 port 1024\n'
|
||||||
b'rdr pass on lo0 inet6 proto udp '
|
b'rdr pass on lo0 inet6 proto udp '
|
||||||
b'to <dns_servers> port 53 -> ::1 port 1026\n'
|
b'to <dns_servers> port 53 -> ::1 port 1026\n'
|
||||||
b'pass out route-to lo0 inet6 proto tcp '
|
b'pass out quick inet6 proto tcp to '
|
||||||
b'to <forward_subnets> keep state\n'
|
b'2404:6800:4004:80c::101f/128 port 8080:8080\n'
|
||||||
|
b'pass out route-to lo0 inet6 proto tcp to '
|
||||||
|
b'2404:6800:4004:80c::/64 port 8000:9000 keep state\n'
|
||||||
b'pass out route-to lo0 inet6 proto udp '
|
b'pass out route-to lo0 inet6 proto udp '
|
||||||
b'to <dns_servers> port 53 keep state\n'),
|
b'to <dns_servers> port 53 keep state\n'),
|
||||||
call('-e'),
|
call('-e'),
|
||||||
@ -319,7 +317,8 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(2, u'1.2.3.33')],
|
||||||
2,
|
2,
|
||||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
[(2, 24, False, u'1.2.3.0', 0, 0),
|
||||||
|
(2, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
True)
|
True)
|
||||||
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
||||||
assert mock_pf_get_dev.mock_calls == []
|
assert mock_pf_get_dev.mock_calls == []
|
||||||
@ -330,7 +329,7 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(2, u'1.2.3.33')],
|
||||||
2,
|
2,
|
||||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
[(2, 24, False, u'1.2.3.0', 0, 0), (2, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
False)
|
False)
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||||
@ -343,14 +342,13 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
assert mock_pfctl.mock_calls == [
|
assert mock_pfctl.mock_calls == [
|
||||||
call('-s all'),
|
call('-s all'),
|
||||||
call('-a sshuttle-1025 -f /dev/stdin',
|
call('-a sshuttle-1025 -f /dev/stdin',
|
||||||
b'table <forward_subnets> {!1.2.3.66/32,1.2.3.0/24}\n'
|
|
||||||
b'table <dns_servers> {1.2.3.33}\n'
|
b'table <dns_servers> {1.2.3.33}\n'
|
||||||
b'rdr pass on lo0 inet proto tcp '
|
b'rdr pass on lo0 inet proto tcp to 1.2.3.0/24 -> '
|
||||||
b'to <forward_subnets> -> 127.0.0.1 port 1025\n'
|
b'127.0.0.1 port 1025\n'
|
||||||
b'rdr pass on lo0 inet proto udp '
|
b'rdr pass on lo0 inet proto udp '
|
||||||
b'to <dns_servers> port 53 -> 127.0.0.1 port 1027\n'
|
b'to <dns_servers> port 53 -> 127.0.0.1 port 1027\n'
|
||||||
b'pass out route-to lo0 inet proto tcp '
|
b'pass out quick inet proto tcp to 1.2.3.66/32 port 80:80\n'
|
||||||
b'to <forward_subnets> keep state\n'
|
b'pass out route-to lo0 inet proto tcp to 1.2.3.0/24 keep state\n'
|
||||||
b'pass out route-to lo0 inet proto udp '
|
b'pass out route-to lo0 inet proto udp '
|
||||||
b'to <dns_servers> port 53 keep state\n'),
|
b'to <dns_servers> port 53 keep state\n'),
|
||||||
call('-e'),
|
call('-e'),
|
||||||
@ -385,8 +383,8 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
1024, 1026,
|
1024, 1026,
|
||||||
[(10, u'2404:6800:4004:80c::33')],
|
[(10, u'2404:6800:4004:80c::33')],
|
||||||
10,
|
10,
|
||||||
[(10, 64, False, u'2404:6800:4004:80c::'),
|
[(10, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
|
||||||
(10, 128, True, u'2404:6800:4004:80c::101f')],
|
(10, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||||
False)
|
False)
|
||||||
|
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
@ -398,16 +396,15 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
call('-f /dev/stdin', b'match on lo\n'),
|
call('-f /dev/stdin', b'match on lo\n'),
|
||||||
call('-s all'),
|
call('-s all'),
|
||||||
call('-a sshuttle6-1024 -f /dev/stdin',
|
call('-a sshuttle6-1024 -f /dev/stdin',
|
||||||
b'table <forward_subnets> {'
|
|
||||||
b'!2404:6800:4004:80c::101f/128,2404:6800:4004:80c::/64'
|
|
||||||
b'}\n'
|
|
||||||
b'table <dns_servers> {2404:6800:4004:80c::33}\n'
|
b'table <dns_servers> {2404:6800:4004:80c::33}\n'
|
||||||
b'pass in on lo0 inet6 proto tcp to '
|
b'pass in on lo0 inet6 proto tcp to 2404:6800:4004:80c::/64 '
|
||||||
b'<forward_subnets> divert-to ::1 port 1024\n'
|
b'port 8000:9000 divert-to ::1 port 1024\n'
|
||||||
b'pass in on lo0 inet6 proto udp '
|
b'pass in on lo0 inet6 proto udp '
|
||||||
b'to <dns_servers> port 53 rdr-to ::1 port 1026\n'
|
b'to <dns_servers> port 53 rdr-to ::1 port 1026\n'
|
||||||
b'pass out inet6 proto tcp to '
|
b'pass out quick inet6 proto tcp to '
|
||||||
b'<forward_subnets> route-to lo0 keep state\n'
|
b'2404:6800:4004:80c::101f/128 port 8080:8080\n'
|
||||||
|
b'pass out inet6 proto tcp to 2404:6800:4004:80c::/64 '
|
||||||
|
b'port 8000:9000 route-to lo0 keep state\n'
|
||||||
b'pass out inet6 proto udp to '
|
b'pass out inet6 proto udp to '
|
||||||
b'<dns_servers> port 53 route-to lo0 keep state\n'),
|
b'<dns_servers> port 53 route-to lo0 keep state\n'),
|
||||||
call('-e'),
|
call('-e'),
|
||||||
@ -421,7 +418,8 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(2, u'1.2.3.33')],
|
||||||
2,
|
2,
|
||||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
[(2, 24, False, u'1.2.3.0', 0, 0),
|
||||||
|
(2, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
True)
|
True)
|
||||||
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
||||||
assert mock_pf_get_dev.mock_calls == []
|
assert mock_pf_get_dev.mock_calls == []
|
||||||
@ -432,7 +430,8 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(2, u'1.2.3.33')],
|
||||||
2,
|
2,
|
||||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
[(2, 24, False, u'1.2.3.0', 0, 0),
|
||||||
|
(2, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
False)
|
False)
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
call(mock_pf_get_dev(), 0xcd48441a, ANY),
|
call(mock_pf_get_dev(), 0xcd48441a, ANY),
|
||||||
@ -443,13 +442,13 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
call('-f /dev/stdin', b'match on lo\n'),
|
call('-f /dev/stdin', b'match on lo\n'),
|
||||||
call('-s all'),
|
call('-s all'),
|
||||||
call('-a sshuttle-1025 -f /dev/stdin',
|
call('-a sshuttle-1025 -f /dev/stdin',
|
||||||
b'table <forward_subnets> {!1.2.3.66/32,1.2.3.0/24}\n'
|
|
||||||
b'table <dns_servers> {1.2.3.33}\n'
|
b'table <dns_servers> {1.2.3.33}\n'
|
||||||
b'pass in on lo0 inet proto tcp to <forward_subnets> divert-to 127.0.0.1 port 1025\n'
|
b'pass in on lo0 inet proto tcp to 1.2.3.0/24 divert-to '
|
||||||
|
b'127.0.0.1 port 1025\n'
|
||||||
b'pass in on lo0 inet proto udp to '
|
b'pass in on lo0 inet proto udp to '
|
||||||
b'<dns_servers> port 53 rdr-to 127.0.0.1 port 1027\n'
|
b'<dns_servers> port 53 rdr-to 127.0.0.1 port 1027\n'
|
||||||
b'pass out inet proto tcp to '
|
b'pass out quick inet proto tcp to 1.2.3.66/32 port 80:80\n'
|
||||||
b'<forward_subnets> route-to lo0 keep state\n'
|
b'pass out inet proto tcp to 1.2.3.0/24 route-to lo0 keep state\n'
|
||||||
b'pass out inet proto udp to '
|
b'pass out inet proto udp to '
|
||||||
b'<dns_servers> port 53 route-to lo0 keep state\n'),
|
b'<dns_servers> port 53 route-to lo0 keep state\n'),
|
||||||
call('-e'),
|
call('-e'),
|
||||||
|
@ -102,8 +102,8 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
|||||||
1024, 1026,
|
1024, 1026,
|
||||||
[(10, u'2404:6800:4004:80c::33')],
|
[(10, u'2404:6800:4004:80c::33')],
|
||||||
10,
|
10,
|
||||||
[(10, 64, False, u'2404:6800:4004:80c::'),
|
[(10, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
|
||||||
(10, 128, True, u'2404:6800:4004:80c::101f')],
|
(10, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||||
True)
|
True)
|
||||||
assert mock_ipt_chain_exists.mock_calls == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(10, 'mangle', 'sshuttle-m-1024'),
|
call(10, 'mangle', 'sshuttle-m-1024'),
|
||||||
@ -144,28 +144,30 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
|||||||
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1026'),
|
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1026'),
|
||||||
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
|
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
|
||||||
'--dest', u'2404:6800:4004:80c::101f/128',
|
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||||
'-m', 'tcp', '-p', 'tcp'),
|
'-m', 'tcp', '-p', 'tcp', '--dport', '8080:8080'),
|
||||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'RETURN',
|
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'RETURN',
|
||||||
'--dest', u'2404:6800:4004:80c::101f/128',
|
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||||
'-m', 'tcp', '-p', 'tcp'),
|
'-m', 'tcp', '-p', 'tcp', '--dport', '8080:8080'),
|
||||||
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
|
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
|
||||||
'--dest', u'2404:6800:4004:80c::101f/128',
|
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||||
'-m', 'udp', '-p', 'udp'),
|
'-m', 'udp', '-p', 'udp', '--dport', '8080:8080'),
|
||||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'RETURN',
|
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'RETURN',
|
||||||
'--dest', u'2404:6800:4004:80c::101f/128',
|
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||||
'-m', 'udp', '-p', 'udp'),
|
'-m', 'udp', '-p', 'udp', '--dport', '8080:8080'),
|
||||||
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
|
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
|
||||||
'--set-mark', '1', '--dest', u'2404:6800:4004:80c::/64',
|
'--set-mark', '1', '--dest', u'2404:6800:4004:80c::/64',
|
||||||
'-m', 'tcp', '-p', 'tcp'),
|
'-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000'),
|
||||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
|
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
|
||||||
'--tproxy-mark', '0x1/0x1', '--dest', u'2404:6800:4004:80c::/64',
|
'--tproxy-mark', '0x1/0x1', '--dest', u'2404:6800:4004:80c::/64',
|
||||||
'-m', 'tcp', '-p', 'tcp', '--on-port', '1024'),
|
'-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000',
|
||||||
|
'--on-port', '1024'),
|
||||||
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
|
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
|
||||||
'--set-mark', '1', '--dest', u'2404:6800:4004:80c::/64',
|
'--set-mark', '1', '--dest', u'2404:6800:4004:80c::/64',
|
||||||
'-m', 'udp', '-p', 'udp'),
|
'-m', 'udp', '-p', 'udp'),
|
||||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
|
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
|
||||||
'--tproxy-mark', '0x1/0x1', '--dest', u'2404:6800:4004:80c::/64',
|
'--tproxy-mark', '0x1/0x1', '--dest', u'2404:6800:4004:80c::/64',
|
||||||
'-m', 'udp', '-p', 'udp', '--on-port', '1024')
|
'-m', 'udp', '-p', 'udp', '--dport', '8000:9000',
|
||||||
|
'--on-port', '1024')
|
||||||
]
|
]
|
||||||
mock_ipt_chain_exists.reset_mock()
|
mock_ipt_chain_exists.reset_mock()
|
||||||
mock_ipt_ttl.reset_mock()
|
mock_ipt_ttl.reset_mock()
|
||||||
@ -198,7 +200,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
|||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(2, u'1.2.3.33')],
|
||||||
2,
|
2,
|
||||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
[(2, 24, False, u'1.2.3.0', 0, 0), (2, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
True)
|
True)
|
||||||
assert mock_ipt_chain_exists.mock_calls == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(2, 'mangle', 'sshuttle-m-1025'),
|
call(2, 'mangle', 'sshuttle-m-1025'),
|
||||||
@ -237,13 +239,17 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
|||||||
'--tproxy-mark', '0x1/0x1', '--dest', u'1.2.3.33/32',
|
'--tproxy-mark', '0x1/0x1', '--dest', u'1.2.3.33/32',
|
||||||
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1027'),
|
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1027'),
|
||||||
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
||||||
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp'),
|
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp',
|
||||||
|
'--dport', '80:80'),
|
||||||
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'RETURN',
|
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'RETURN',
|
||||||
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp'),
|
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp',
|
||||||
|
'--dport', '80:80'),
|
||||||
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
||||||
'--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp'),
|
'--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp',
|
||||||
|
'--dport', '80:80'),
|
||||||
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'RETURN',
|
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'RETURN',
|
||||||
'--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp'),
|
'--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp',
|
||||||
|
'--dport', '80:80'),
|
||||||
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
|
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
|
||||||
'--set-mark', '1', '--dest', u'1.2.3.0/24',
|
'--set-mark', '1', '--dest', u'1.2.3.0/24',
|
||||||
'-m', 'tcp', '-p', 'tcp'),
|
'-m', 'tcp', '-p', 'tcp'),
|
||||||
|
101
sshuttle/tests/client/test_options.py
Normal file
101
sshuttle/tests/client/test_options.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import socket
|
||||||
|
import pytest
|
||||||
|
import sshuttle.options
|
||||||
|
from argparse import ArgumentTypeError as Fatal
|
||||||
|
|
||||||
|
_ip4_reprs = {
|
||||||
|
'0.0.0.0': '0.0.0.0',
|
||||||
|
'255.255.255.255': '255.255.255.255',
|
||||||
|
'10.0': '10.0.0.0',
|
||||||
|
'184.172.10.74': '184.172.10.74',
|
||||||
|
'3098282570': '184.172.10.74',
|
||||||
|
'0xb8.0xac.0x0a.0x4a': '184.172.10.74',
|
||||||
|
'0270.0254.0012.0112': '184.172.10.74',
|
||||||
|
'localhost': '127.0.0.1'
|
||||||
|
}
|
||||||
|
|
||||||
|
_ip4_swidths = (1, 8, 22, 27, 32)
|
||||||
|
|
||||||
|
_ip6_reprs = {
|
||||||
|
'::': '::',
|
||||||
|
'::1': '::1',
|
||||||
|
'fc00::': 'fc00::',
|
||||||
|
'2a01:7e00:e000:188::1': '2a01:7e00:e000:188::1'
|
||||||
|
}
|
||||||
|
|
||||||
|
_ip6_swidths = (48, 64, 96, 115, 128)
|
||||||
|
|
||||||
|
def test_parse_subnetport_ip4():
|
||||||
|
for ip_repr, ip in _ip4_reprs.items():
|
||||||
|
assert sshuttle.options.parse_subnetport(ip_repr) \
|
||||||
|
== (socket.AF_INET, ip, 32, 0, 0)
|
||||||
|
with pytest.raises(Fatal) as excinfo:
|
||||||
|
sshuttle.options.parse_subnetport('10.256.0.0')
|
||||||
|
assert str(excinfo.value) == 'Unable to resolve address: 10.256.0.0'
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_subnetport_ip4_with_mask():
|
||||||
|
for ip_repr, ip in _ip4_reprs.items():
|
||||||
|
for swidth in _ip4_swidths:
|
||||||
|
assert sshuttle.options.parse_subnetport(
|
||||||
|
'/'.join((ip_repr, str(swidth)))
|
||||||
|
) == (socket.AF_INET, ip, swidth, 0, 0)
|
||||||
|
assert sshuttle.options.parse_subnetport('0/0') \
|
||||||
|
== (socket.AF_INET, '0.0.0.0', 0, 0, 0)
|
||||||
|
with pytest.raises(Fatal) as excinfo:
|
||||||
|
sshuttle.options.parse_subnetport('10.0.0.0/33')
|
||||||
|
assert str(excinfo.value) == 'width 33 is not between 0 and 32'
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_subnetport_ip4_with_port():
|
||||||
|
for ip_repr, ip in _ip4_reprs.items():
|
||||||
|
assert sshuttle.options.parse_subnetport(':'.join((ip_repr, '80'))) \
|
||||||
|
== (socket.AF_INET, ip, 32, 80, 80)
|
||||||
|
assert sshuttle.options.parse_subnetport(':'.join((ip_repr, '80-90'))) \
|
||||||
|
== (socket.AF_INET, ip, 32, 80, 90)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_subnetport_ip4_with_mask_and_port():
|
||||||
|
for ip_repr, ip in _ip4_reprs.items():
|
||||||
|
assert sshuttle.options.parse_subnetport(ip_repr + '/32:80') \
|
||||||
|
== (socket.AF_INET, ip, 32, 80, 80)
|
||||||
|
assert sshuttle.options.parse_subnetport(ip_repr + '/16:80-90') \
|
||||||
|
== (socket.AF_INET, ip, 16, 80, 90)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_subnetport_ip6():
|
||||||
|
for ip_repr, ip in _ip6_reprs.items():
|
||||||
|
assert sshuttle.options.parse_subnetport(ip_repr) \
|
||||||
|
== (socket.AF_INET6, ip, 128, 0, 0)
|
||||||
|
with pytest.raises(Fatal) as excinfo:
|
||||||
|
sshuttle.options.parse_subnetport('2001::1::3f')
|
||||||
|
assert str(excinfo.value) == 'Unable to resolve address: 2001::1::3f'
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_subnetport_ip6_with_mask():
|
||||||
|
for ip_repr, ip in _ip6_reprs.items():
|
||||||
|
for swidth in _ip4_swidths + _ip6_swidths:
|
||||||
|
assert sshuttle.options.parse_subnetport(
|
||||||
|
'/'.join((ip_repr, str(swidth)))
|
||||||
|
) == (socket.AF_INET6, ip, swidth, 0, 0)
|
||||||
|
assert sshuttle.options.parse_subnetport('::/0') \
|
||||||
|
== (socket.AF_INET6, '::', 0, 0, 0)
|
||||||
|
with pytest.raises(Fatal) as excinfo:
|
||||||
|
sshuttle.options.parse_subnetport('fc00::/129')
|
||||||
|
assert str(excinfo.value) == 'width 129 is not between 0 and 128'
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_subnetport_ip6_with_port():
|
||||||
|
for ip_repr, ip in _ip6_reprs.items():
|
||||||
|
assert sshuttle.options.parse_subnetport('[' + ip_repr + ']:80') \
|
||||||
|
== (socket.AF_INET6, ip, 128, 80, 80)
|
||||||
|
assert sshuttle.options.parse_subnetport('[' + ip_repr + ']:80-90') \
|
||||||
|
== (socket.AF_INET6, ip, 128, 80, 90)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_subnetport_ip6_with_mask_and_port():
|
||||||
|
for ip_repr, ip in _ip6_reprs.items():
|
||||||
|
assert sshuttle.options.parse_subnetport('[' + ip_repr + '/128]:80') \
|
||||||
|
== (socket.AF_INET6, ip, 128, 80, 80)
|
||||||
|
assert sshuttle.options.parse_subnetport('[' + ip_repr + '/16]:80-90') \
|
||||||
|
== (socket.AF_INET6, ip, 16, 80, 90)
|
66
sshuttle/tests/client/test_sdnotify.py
Normal file
66
sshuttle/tests/client/test_sdnotify.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
from mock import Mock, patch, call
|
||||||
|
import sys
|
||||||
|
import io
|
||||||
|
import socket
|
||||||
|
|
||||||
|
import sshuttle.sdnotify
|
||||||
|
|
||||||
|
|
||||||
|
@patch('sshuttle.sdnotify.os.environ.get')
|
||||||
|
def test_notify_invalid_socket_path(mock_get):
|
||||||
|
mock_get.return_value = 'invalid_path'
|
||||||
|
assert not sshuttle.sdnotify.send(sshuttle.sdnotify.ready())
|
||||||
|
|
||||||
|
|
||||||
|
@patch('sshuttle.sdnotify.os.environ.get')
|
||||||
|
def test_notify_socket_not_there(mock_get):
|
||||||
|
mock_get.return_value = '/run/valid_nonexistent_path'
|
||||||
|
assert not sshuttle.sdnotify.send(sshuttle.sdnotify.ready())
|
||||||
|
|
||||||
|
|
||||||
|
@patch('sshuttle.sdnotify.os.environ.get')
|
||||||
|
def test_notify_no_message(mock_get):
|
||||||
|
mock_get.return_value = '/run/valid_path'
|
||||||
|
assert not sshuttle.sdnotify.send()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('sshuttle.sdnotify.socket.socket')
|
||||||
|
@patch('sshuttle.sdnotify.os.environ.get')
|
||||||
|
def test_notify_socket_error(mock_get, mock_socket):
|
||||||
|
mock_get.return_value = '/run/valid_path'
|
||||||
|
mock_socket.side_effect = socket.error('test error')
|
||||||
|
assert not sshuttle.sdnotify.send(sshuttle.sdnotify.ready())
|
||||||
|
|
||||||
|
|
||||||
|
@patch('sshuttle.sdnotify.socket.socket')
|
||||||
|
@patch('sshuttle.sdnotify.os.environ.get')
|
||||||
|
def test_notify_sendto_error(mock_get, mock_socket):
|
||||||
|
message = sshuttle.sdnotify.ready()
|
||||||
|
socket_path = '/run/valid_path'
|
||||||
|
|
||||||
|
sock = Mock()
|
||||||
|
sock.sendto.side_effect = socket.error('test error')
|
||||||
|
mock_get.return_value = '/run/valid_path'
|
||||||
|
mock_socket.return_value = sock
|
||||||
|
|
||||||
|
assert not sshuttle.sdnotify.send(message)
|
||||||
|
assert sock.sendto.mock_calls == [
|
||||||
|
call(message, socket_path),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@patch('sshuttle.sdnotify.socket.socket')
|
||||||
|
@patch('sshuttle.sdnotify.os.environ.get')
|
||||||
|
def test_notify(mock_get, mock_socket):
|
||||||
|
messages = [sshuttle.sdnotify.ready(), sshuttle.sdnotify.status('Running')]
|
||||||
|
socket_path = '/run/valid_path'
|
||||||
|
|
||||||
|
sock = Mock()
|
||||||
|
sock.sendto.return_value = 1
|
||||||
|
mock_get.return_value = '/run/valid_path'
|
||||||
|
mock_socket.return_value = sock
|
||||||
|
|
||||||
|
assert sshuttle.sdnotify.send(*messages)
|
||||||
|
assert sock.sendto.mock_calls == [
|
||||||
|
call(b'\n'.join(messages), socket_path),
|
||||||
|
]
|
@ -21,36 +21,9 @@ def test__maskbits():
|
|||||||
sshuttle.server._maskbits(netmask)
|
sshuttle.server._maskbits(netmask)
|
||||||
|
|
||||||
|
|
||||||
|
@patch('sshuttle.server.which', side_effect=lambda x: x == 'netstat')
|
||||||
@patch('sshuttle.server.ssubprocess.Popen')
|
@patch('sshuttle.server.ssubprocess.Popen')
|
||||||
def test__listroutes(mock_popen):
|
def test_listroutes_netstat(mock_popen, mock_which):
|
||||||
mock_pobj = Mock()
|
|
||||||
mock_pobj.stdout = io.BytesIO(b"""
|
|
||||||
Kernel IP routing table
|
|
||||||
Destination Gateway Genmask Flags MSS Window irtt Iface
|
|
||||||
0.0.0.0 192.168.1.1 0.0.0.0 UG 0 0 0 wlan0
|
|
||||||
192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 wlan0
|
|
||||||
""")
|
|
||||||
mock_pobj.wait.return_value = 0
|
|
||||||
mock_popen.return_value = mock_pobj
|
|
||||||
|
|
||||||
routes = sshuttle.server._list_routes()
|
|
||||||
|
|
||||||
env = {
|
|
||||||
'PATH': os.environ['PATH'],
|
|
||||||
'LC_ALL': "C",
|
|
||||||
}
|
|
||||||
assert mock_popen.mock_calls == [
|
|
||||||
call(['netstat', '-rn'], stdout=-1, env=env),
|
|
||||||
call().wait()
|
|
||||||
]
|
|
||||||
assert routes == [
|
|
||||||
(socket.AF_INET, '0.0.0.0', 0),
|
|
||||||
(socket.AF_INET, '192.168.1.0', 24)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@patch('sshuttle.server.ssubprocess.Popen')
|
|
||||||
def test_listroutes(mock_popen):
|
|
||||||
mock_pobj = Mock()
|
mock_pobj = Mock()
|
||||||
mock_pobj.stdout = io.BytesIO(b"""
|
mock_pobj.stdout = io.BytesIO(b"""
|
||||||
Kernel IP routing table
|
Kernel IP routing table
|
||||||
@ -66,3 +39,21 @@ Destination Gateway Genmask Flags MSS Window irtt Iface
|
|||||||
assert list(routes) == [
|
assert list(routes) == [
|
||||||
(socket.AF_INET, '192.168.1.0', 24)
|
(socket.AF_INET, '192.168.1.0', 24)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@patch('sshuttle.server.which', side_effect=lambda x: x == 'ip')
|
||||||
|
@patch('sshuttle.server.ssubprocess.Popen')
|
||||||
|
def test_listroutes_iproute(mock_popen, mock_which):
|
||||||
|
mock_pobj = Mock()
|
||||||
|
mock_pobj.stdout = io.BytesIO(b"""
|
||||||
|
default via 192.168.1.1 dev wlan0 proto static
|
||||||
|
192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.1
|
||||||
|
""")
|
||||||
|
mock_pobj.wait.return_value = 0
|
||||||
|
mock_popen.return_value = mock_pobj
|
||||||
|
|
||||||
|
routes = sshuttle.server.list_routes()
|
||||||
|
|
||||||
|
assert list(routes) == [
|
||||||
|
(socket.AF_INET, '192.168.1.0', 24)
|
||||||
|
]
|
||||||
|
Reference in New Issue
Block a user