mirror of
https://github.com/sshuttle/sshuttle.git
synced 2025-07-04 08:40:30 +02:00
Compare commits
84 Commits
Author | SHA1 | Date | |
---|---|---|---|
6ec42adbf4 | |||
2200d824bf | |||
9715a1d6f2 | |||
8bfc03b256 | |||
884bd6deb0 | |||
a215f1b227 | |||
11455d0bcd | |||
74acc10385 | |||
084bf5f0f2 | |||
1940b524f1 | |||
d11f5b9d16 | |||
93b969a049 | |||
f27b27b0e8 | |||
fc08fb4086 | |||
e82d5a8e7c | |||
d9d61e6ab2 | |||
179bb107e1 | |||
9a176aa96f | |||
6b48301b86 | |||
be90cc8abd | |||
512396e06b | |||
7a71ae1380 | |||
3a6f6cb795 | |||
81ab587698 | |||
817284c2f8 | |||
71d65f3831 | |||
9f238ebca8 | |||
9b315746d1 | |||
6a488b3db9 | |||
112931dd2c | |||
ad676029c7 | |||
47030e846b | |||
416636fa9b | |||
4300a02343 | |||
4e8c5411b5 | |||
6cdc4da1e4 | |||
8add00866c | |||
94ea0a3bed | |||
9b7ce2811e | |||
7726dea27c | |||
3635cc17ad | |||
ae13316e83 | |||
e173eb6016 | |||
29cd75b6f7 | |||
4c50be0bc7 | |||
2fa0cd06fb | |||
4d8b758d32 | |||
4e8c2b9c68 | |||
be559fc78b | |||
d2e97a60f7 | |||
cdbb379910 | |||
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 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,5 +1,4 @@
|
||||
/sshuttle/version.py
|
||||
/docs/conf.py
|
||||
/tmp/
|
||||
/.cache/
|
||||
/.eggs/
|
||||
@ -14,3 +13,5 @@
|
||||
/.do_built
|
||||
/.do_built.dir
|
||||
/.redo
|
||||
/.pytest_cache/
|
||||
/.python-version
|
||||
|
24
.prospector.yml
Normal file
24
.prospector.yml
Normal file
@ -0,0 +1,24 @@
|
||||
strictness: medium
|
||||
|
||||
pylint:
|
||||
disable:
|
||||
- too-many-statements
|
||||
- too-many-locals
|
||||
- too-many-function-args
|
||||
- too-many-arguments
|
||||
- too-many-branches
|
||||
- bare-except
|
||||
- protected-access
|
||||
- no-else-return
|
||||
- unused-argument
|
||||
- method-hidden
|
||||
- arguments-differ
|
||||
- wrong-import-position
|
||||
- raising-bad-type
|
||||
|
||||
pep8:
|
||||
options:
|
||||
max-line-length: 79
|
||||
|
||||
mccabe:
|
||||
run: false
|
10
.travis.yml
10
.travis.yml
@ -1,13 +1,19 @@
|
||||
language: python
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 3.6
|
||||
- pypy
|
||||
|
||||
install:
|
||||
- travis_retry pip install -q pytest mock
|
||||
- travis_retry pip install -q -r requirements-tests.txt
|
||||
|
||||
before_script:
|
||||
# stop the build if there are Python syntax errors or undefined names.
|
||||
- flake8 sshuttle --count --select=E901,E999,F821,F822,F823 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide.
|
||||
- flake8 sshuttle --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
|
||||
script:
|
||||
- PYTHONPATH=. py.test
|
||||
|
138
CHANGES.rst
138
CHANGES.rst
@ -1,13 +1,119 @@
|
||||
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.4 - 2018-04-02
|
||||
-------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
* Add homebrew instructions.
|
||||
* Route traffic by linux user.
|
||||
* Add nat-like method using nftables instead of iptables.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
* Talk to custom DNS server on pod, instead of the ones in /etc/resolv.conf.
|
||||
* Add new option for overriding destination DNS server.
|
||||
* Changed subnet parsing. Previously 10/8 become 10.0.0.0/8. Now it gets
|
||||
parsed as 0.0.0.10/8.
|
||||
* Make hostwatch find both fqdn and hostname.
|
||||
* Use versions of python3 greater than 3.5 when available (e.g. 3.6).
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
* Remove Python 2.6 from automatic tests.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
* Fix case where there is no --dns.
|
||||
* [pf] Avoid port forwarding from loopback address.
|
||||
* Use getaddrinfo to obtain a correct sockaddr.
|
||||
* Skip empty lines on incoming routes data.
|
||||
* Just skip empty lines of routes data instead of stopping processing.
|
||||
* [pf] Load pf kernel module when enabling pf.
|
||||
* [pf] Test double restore (ipv4, ipv6) disables only once; test kldload.
|
||||
* Fixes UDP and DNS proxies binding to the same socket address.
|
||||
* Mock socket bind to avoid depending on local IPs being available in test box.
|
||||
* Fix no value passed for argument auto_hosts in hw_main call.
|
||||
* Fixed incorrect license information in setup.py.
|
||||
* Preserve peer and port properly.
|
||||
* Make --to-dns and --ns-host work well together.
|
||||
* Remove test that fails under OSX.
|
||||
* Specify pip requirements for tests.
|
||||
* Use flake8 to find Python syntax errors or undefined names.
|
||||
* Fix compatibility with the sudoers file.
|
||||
* Stop using SO_REUSEADDR on sockets.
|
||||
* Declare 'verbosity' as global variable to placate linters.
|
||||
* Adds 'cd sshuttle' after 'git' to README and docs.
|
||||
* Documentation for loading options from configuration file.
|
||||
* Load options from a file.
|
||||
* Fix firewall.py.
|
||||
* Move sdnotify after setting up firewall rules.
|
||||
* Fix tests on Macos.
|
||||
|
||||
|
||||
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.
|
||||
* Don't crash on ENETUNREACH.
|
||||
* Various bug fixes.
|
||||
* 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.
|
||||
* Call /bin/sh as users shell may not be POSIX compliant. Fixes #77.
|
||||
@ -17,22 +123,22 @@ Release 0.78.0 (Apr 8, 2016)
|
||||
* 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.
|
||||
|
||||
|
||||
Release 0.77.1 (Mar 7, 2016)
|
||||
============================
|
||||
0.77.1 - 2016-03-07
|
||||
-------------------
|
||||
|
||||
* Use semantic versioning. http://semver.org/
|
||||
* Update GPL 2 license text.
|
||||
* New release to fix PyPI.
|
||||
|
||||
|
||||
Release 0.77 (Mar 3, 2016)
|
||||
==========================
|
||||
0.77 - 2016-03-03
|
||||
-----------------
|
||||
|
||||
* Various bug fixes.
|
||||
* Fix Documentation.
|
||||
@ -40,8 +146,8 @@ Release 0.77 (Mar 3, 2016)
|
||||
* Add support for OpenBSD.
|
||||
|
||||
|
||||
Release 0.76 (Jan 17, 2016)
|
||||
===========================
|
||||
0.76 - 2016-01-17
|
||||
-----------------
|
||||
|
||||
* Add option to disable IPv6 support.
|
||||
* Update documentation.
|
||||
@ -49,14 +155,14 @@ Release 0.76 (Jan 17, 2016)
|
||||
* Use setuptools-scm for automatic versioning.
|
||||
|
||||
|
||||
Release 0.75 (Jan 12, 2016)
|
||||
===========================
|
||||
0.75 - 2016-01-12
|
||||
-----------------
|
||||
|
||||
* Revert change that broke sshuttle entry point.
|
||||
|
||||
|
||||
Release 0.74 (Jan 10, 2016)
|
||||
===========================
|
||||
0.74 - 2016-01-10
|
||||
-----------------
|
||||
|
||||
* Add CHANGES.rst file.
|
||||
* Numerous bug fixes.
|
||||
|
@ -6,7 +6,6 @@ include LICENSE
|
||||
include run
|
||||
include tox.ini
|
||||
exclude sshuttle/version.py
|
||||
exclude docs/conf.py
|
||||
recursive-include docs *.bat
|
||||
recursive-include docs *.py
|
||||
recursive-include docs *.rst
|
||||
|
28
README.rst
28
README.rst
@ -29,15 +29,41 @@ common case:
|
||||
Obtaining sshuttle
|
||||
------------------
|
||||
|
||||
- Debian stretch or later::
|
||||
|
||||
apt-get install sshuttle
|
||||
|
||||
- From PyPI::
|
||||
|
||||
pip install sshuttle
|
||||
sudo pip install sshuttle
|
||||
|
||||
- Clone::
|
||||
|
||||
git clone https://github.com/sshuttle/sshuttle.git
|
||||
cd sshuttle
|
||||
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
|
||||
|
||||
- Clone::
|
||||
|
||||
virtualenv -p python3 /tmp/sshuttle
|
||||
. /tmp/sshuttle/bin/activate
|
||||
git clone https://github.com/sshuttle/sshuttle.git
|
||||
cd sshuttle
|
||||
./setup.py install
|
||||
|
||||
- Homebrew::
|
||||
|
||||
brew install sshuttle
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
The documentation for the stable version is available at:
|
||||
|
9
bandit.yml
Normal file
9
bandit.yml
Normal file
@ -0,0 +1,9 @@
|
||||
exclude_dirs:
|
||||
- sshuttle/tests
|
||||
skips:
|
||||
- B101
|
||||
- B104
|
||||
- B404
|
||||
- B603
|
||||
- B606
|
||||
- B607
|
@ -1,4 +1 @@
|
||||
Changelog
|
||||
---------
|
||||
|
||||
.. include:: ../CHANGES.rst
|
||||
|
@ -13,9 +13,10 @@
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
# import sys
|
||||
# import os
|
||||
from setuptools_scm import get_version
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
import sshuttle.version # NOQA
|
||||
|
||||
# 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
|
||||
@ -54,10 +55,10 @@ copyright = '2016, Brian May'
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = get_version(root="..")
|
||||
# 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
|
||||
# for a list of supported languages.
|
@ -8,4 +8,5 @@ Installation
|
||||
- Clone::
|
||||
|
||||
git clone https://github.com/sshuttle/sshuttle.git
|
||||
cd sshuttle
|
||||
./setup.py install
|
||||
|
@ -31,11 +31,18 @@ Options
|
||||
.. option:: subnets
|
||||
|
||||
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),
|
||||
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
|
||||
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]
|
||||
|
||||
@ -54,9 +61,11 @@ Options
|
||||
connections from other machines on your network (ie. to
|
||||
run :program:`sshuttle` on a router) try enabling IP Forwarding in
|
||||
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
|
||||
required, to provide both IPv4 and IPv6 addresses.
|
||||
For the tproxy and pf methods this can be an IPv6 address. Use this option
|
||||
twice if required, to provide both IPv4 and IPv6 addresses.
|
||||
|
||||
.. option:: -H, --auto-hosts
|
||||
|
||||
@ -176,7 +185,7 @@ Options
|
||||
|
||||
.. 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
|
||||
|
||||
@ -196,6 +205,29 @@ Options
|
||||
feature.
|
||||
|
||||
|
||||
Configuration File
|
||||
------------------
|
||||
All the options described above can optionally be specified in a configuration
|
||||
file.
|
||||
|
||||
To run :program:`sshuttle` with options defined in, e.g., `/etc/ssshuttle.conf`
|
||||
just pass the path to the file preceded by the `@` character, e.g.
|
||||
:option:`@/etc/ssshuttle.conf`.
|
||||
|
||||
When running :program:`sshuttle` with options defined in a configuratio file,
|
||||
options can still be passed via the command line in addition to what is
|
||||
defined in the file. If a given option is defined both in the file and in
|
||||
the command line, the value in the command line will take precedence.
|
||||
|
||||
Arguments read from a file must be one per line, as shown below::
|
||||
|
||||
value
|
||||
--option1
|
||||
value1
|
||||
--option2
|
||||
value2
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
Test locally by proxying all local connections, without using ssh::
|
||||
@ -244,6 +276,24 @@ and subnet guessing::
|
||||
c : Keyboard interrupt: exiting.
|
||||
c : SW#6:192.168.42.121:60554: deleting
|
||||
|
||||
Run :program:`sshuttle` with a `/etc/sshuttle.conf` configuration file::
|
||||
|
||||
$ sshuttle @/etc/sshuttle.conf
|
||||
|
||||
Use the options defined in `/etc/sshuttle.conf` but be more verbose::
|
||||
|
||||
$ sshuttle @/etc/sshuttle.conf -vvv
|
||||
|
||||
Override the remote server defined in `/etc/sshuttle.conf`::
|
||||
|
||||
$ sshuttle @/etc/sshuttle.conf -r otheruser@test.example.com
|
||||
|
||||
Example configuration file::
|
||||
|
||||
192.168.0.0/16
|
||||
--remote
|
||||
user@example.com
|
||||
|
||||
|
||||
Discussion
|
||||
----------
|
||||
|
@ -4,7 +4,7 @@ Overview
|
||||
As far as I know, sshuttle is the only program that solves the following
|
||||
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.
|
||||
|
||||
|
@ -26,28 +26,31 @@ Linux with TPROXY method
|
||||
Supports:
|
||||
|
||||
* IPv4 TCP
|
||||
* IPv4 UDP (requires ``recmsg`` - see below)
|
||||
* IPv6 DNS (requires ``recmsg`` - see below)
|
||||
* IPv4 UDP (requires ``recvmsg`` - see below)
|
||||
* IPv6 DNS (requires ``recvmsg`` - see below)
|
||||
* IPv6 TCP
|
||||
* IPv6 UDP (requires ``recmsg`` - see below)
|
||||
* IPv6 DNS (requires ``recmsg`` - see below)
|
||||
* IPv6 UDP (requires ``recvmsg`` - see below)
|
||||
* IPv6 DNS (requires ``recvmsg`` - see below)
|
||||
|
||||
.. _PyXAPI: http://www.pps.univ-paris-diderot.fr/~ylg/PyXAPI/
|
||||
|
||||
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
|
||||
later. Under Python 2 you might find it sufficient installing PyXAPI_ to get
|
||||
the ``recvmsg()`` function. See :doc:`tproxy` for more information.
|
||||
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 to install PyXAPI_ in
|
||||
order to get the ``recvmsg()`` function. See :doc:`tproxy` for more
|
||||
information.
|
||||
|
||||
|
||||
MacOS / FreeBSD / OpenBSD
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
MacOS / FreeBSD / OpenBSD / pfSense
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Method: pf
|
||||
|
||||
Supports:
|
||||
|
||||
* IPv4 TCP
|
||||
* IPv4 DNS
|
||||
* IPv6 TCP
|
||||
* IPv6 DNS
|
||||
|
||||
Requires:
|
||||
|
||||
@ -62,12 +65,31 @@ cmd.exe with Administrator access. See :doc:`windows` for more information.
|
||||
|
||||
Server side Requirements
|
||||
------------------------
|
||||
Server requirements are more relaxed, however it is recommended that you use
|
||||
Python 2.7 or Python 3.5.
|
||||
The server can run in any version of Python between 2.4 and 3.6.
|
||||
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
|
||||
-----------------------------
|
||||
|
||||
- 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
|
||||
|
4
requirements-tests.txt
Normal file
4
requirements-tests.txt
Normal file
@ -0,0 +1,4 @@
|
||||
-r requirements.txt
|
||||
pytest==3.4.2
|
||||
mock==2.0.0
|
||||
flake8==3.5.0
|
@ -1 +1 @@
|
||||
setuptools_scm
|
||||
setuptools-scm==1.15.6
|
||||
|
24
run
24
run
@ -1,8 +1,16 @@
|
||||
#!/bin/sh
|
||||
if python3.5 -V 2>/dev/null; then
|
||||
exec python3.5 -m "sshuttle" "$@"
|
||||
elif python2.7 -V 2>/dev/null; then
|
||||
exec python2.7 -m "sshuttle" "$@"
|
||||
else
|
||||
exec python -m "sshuttle" "$@"
|
||||
fi
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
export PYTHONPATH="$(dirname $0):$PYTHONPATH"
|
||||
|
||||
python_best_version() {
|
||||
if [ -x "$(command -v python3)" ] &&
|
||||
python3 -c "import sys; sys.exit(not sys.version_info > (3, 5))"; then
|
||||
exec python3 "$@"
|
||||
elif [ -x "$(command -v python2.7)" ]; then
|
||||
exec python2.7 "$@"
|
||||
else
|
||||
exec python "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
python_best_version -m "sshuttle" "$@"
|
||||
|
@ -1,2 +1,9 @@
|
||||
[aliases]
|
||||
test=pytest
|
||||
|
||||
[bdist_wheel]
|
||||
universal = 1
|
||||
|
||||
[upload]
|
||||
sign=true
|
||||
identity=0x1784577F811F6EAC
|
||||
|
32
setup.py
32
setup.py
@ -2,55 +2,51 @@
|
||||
|
||||
# Copyright 2012-2014 Brian May
|
||||
#
|
||||
# This file is part of python-tldap.
|
||||
# This file is part of sshuttle.
|
||||
#
|
||||
# python-tldap is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
# sshuttle is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 2.1 of
|
||||
# the License, or (at your option) any later version.
|
||||
#
|
||||
# python-tldap is distributed in the hope that it will be useful,
|
||||
# sshuttle is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with python-tldap If not, see <http://www.gnu.org/licenses/>.
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with sshuttle; If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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):
|
||||
from setuptools_scm.version import guess_next_dev_version
|
||||
version = guess_next_dev_version(version)
|
||||
return version.lstrip("v")
|
||||
|
||||
|
||||
setup(
|
||||
name="sshuttle",
|
||||
use_scm_version={
|
||||
'write_to': "sshuttle/version.py",
|
||||
'version_scheme': version_scheme,
|
||||
},
|
||||
setup_requires=['setuptools_scm', 'pytest-runner'],
|
||||
setup_requires=['setuptools_scm'],
|
||||
# version=version,
|
||||
url='https://github.com/sshuttle/sshuttle',
|
||||
author='Brian May',
|
||||
author_email='brian@linuxpenguins.xyz',
|
||||
description='Full-featured" VPN over an SSH tunnel',
|
||||
packages=find_packages(),
|
||||
license="GPL2+",
|
||||
license="LGPL2.1+",
|
||||
long_description=open('README.rst').read(),
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: End Users/Desktop",
|
||||
"License :: OSI Approved :: "
|
||||
"GNU General Public License v2 or later (GPLv2+)",
|
||||
"GNU Lesser General Public License v2 or later (LGPLv2+)",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
@ -61,6 +57,6 @@ setup(
|
||||
'sshuttle = sshuttle.cmdline:main',
|
||||
],
|
||||
},
|
||||
tests_require=['pytest', 'mock'],
|
||||
tests_require=['pytest', 'pytest-runner', 'mock'],
|
||||
keywords="ssh vpn",
|
||||
)
|
||||
|
@ -2,6 +2,7 @@ import sys
|
||||
import zlib
|
||||
import imp
|
||||
|
||||
verbosity = verbosity # noqa: F821 must be a previously defined global
|
||||
z = zlib.decompressobj()
|
||||
while 1:
|
||||
name = sys.stdin.readline().strip()
|
||||
@ -21,7 +22,7 @@ while 1:
|
||||
setattr(sys.modules[parent], parent_name, module)
|
||||
|
||||
code = compile(content, name, "exec")
|
||||
exec(code, module.__dict__)
|
||||
exec(code, module.__dict__) # nosec
|
||||
sys.modules[name] = module
|
||||
else:
|
||||
break
|
||||
@ -34,4 +35,4 @@ sshuttle.helpers.verbose = verbosity
|
||||
|
||||
import sshuttle.cmdline_options as options
|
||||
from sshuttle.server import main
|
||||
main(options.latency_control, options.auto_hosts)
|
||||
main(options.latency_control, options.auto_hosts, options.to_nameserver)
|
||||
|
@ -1,4 +1,3 @@
|
||||
import socket
|
||||
import errno
|
||||
import re
|
||||
import signal
|
||||
@ -15,6 +14,24 @@ from sshuttle.ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
|
||||
from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, islocal, \
|
||||
resolvconf_nameservers
|
||||
from sshuttle.methods import get_method, Features
|
||||
try:
|
||||
from pwd import getpwnam
|
||||
except ImportError:
|
||||
getpwnam = None
|
||||
|
||||
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)
|
||||
|
||||
@ -94,8 +111,8 @@ def daemon_cleanup():
|
||||
|
||||
class MultiListener:
|
||||
|
||||
def __init__(self, type=socket.SOCK_STREAM, proto=0):
|
||||
self.type = type
|
||||
def __init__(self, kind=socket.SOCK_STREAM, proto=0):
|
||||
self.type = kind
|
||||
self.proto = proto
|
||||
self.v6 = None
|
||||
self.v4 = None
|
||||
@ -143,13 +160,11 @@ class MultiListener:
|
||||
self.bind_called = True
|
||||
if address_v6 is not None:
|
||||
self.v6 = socket.socket(socket.AF_INET6, self.type, self.proto)
|
||||
self.v6.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.v6.bind(address_v6)
|
||||
else:
|
||||
self.v6 = None
|
||||
if address_v4 is not None:
|
||||
self.v4 = socket.socket(socket.AF_INET, self.type, self.proto)
|
||||
self.v4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.v4.bind(address_v4)
|
||||
else:
|
||||
self.v4 = None
|
||||
@ -178,8 +193,8 @@ class FirewallClient:
|
||||
if ssyslog._p:
|
||||
argvbase += ['--syslog']
|
||||
argv_tries = [
|
||||
['sudo', '-p', '[local sudo] Password: ',
|
||||
('PYTHONPATH=%s' % python_path), '--'] + argvbase,
|
||||
['sudo', '-p', '[local sudo] Password: ', '/usr/bin/env',
|
||||
('PYTHONPATH=%s' % python_path)] + argvbase,
|
||||
argvbase
|
||||
]
|
||||
|
||||
@ -224,7 +239,8 @@ class FirewallClient:
|
||||
self.method.set_firewall(self)
|
||||
|
||||
def setup(self, subnets_include, subnets_exclude, nslist,
|
||||
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp):
|
||||
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp,
|
||||
user):
|
||||
self.subnets_include = subnets_include
|
||||
self.subnets_exclude = subnets_exclude
|
||||
self.nslist = nslist
|
||||
@ -233,6 +249,7 @@ class FirewallClient:
|
||||
self.dnsport_v6 = dnsport_v6
|
||||
self.dnsport_v4 = dnsport_v4
|
||||
self.udp = udp
|
||||
self.user = user
|
||||
|
||||
def check(self):
|
||||
rv = self.p.poll()
|
||||
@ -241,12 +258,13 @@ class FirewallClient:
|
||||
|
||||
def start(self):
|
||||
self.pfile.write(b'ROUTES\n')
|
||||
for (family, ip, width) in self.subnets_include + self.auto_nets:
|
||||
self.pfile.write(b'%d,%d,0,%s\n'
|
||||
% (family, width, ip.encode("ASCII")))
|
||||
for (family, ip, width) in self.subnets_exclude:
|
||||
self.pfile.write(b'%d,%d,1,%s\n'
|
||||
% (family, width, ip.encode("ASCII")))
|
||||
for (family, ip, width, fport, lport) \
|
||||
in self.subnets_include + self.auto_nets:
|
||||
self.pfile.write(b'%d,%d,0,%s,%d,%d\n'
|
||||
% (family, width, ip.encode("ASCII"), fport, lport))
|
||||
for (family, ip, width, fport, lport) in self.subnets_exclude:
|
||||
self.pfile.write(b'%d,%d,1,%s,%d,%d\n'
|
||||
% (family, width, ip.encode("ASCII"), fport, lport))
|
||||
|
||||
self.pfile.write(b'NSLIST\n')
|
||||
for (family, ip) in self.nslist:
|
||||
@ -261,8 +279,14 @@ class FirewallClient:
|
||||
udp = 0
|
||||
if self.udp:
|
||||
udp = 1
|
||||
if self.user is None:
|
||||
user = b'-'
|
||||
elif isinstance(self.user, str):
|
||||
user = bytes(self.user, 'utf-8')
|
||||
else:
|
||||
user = b'%d' % self.user
|
||||
|
||||
self.pfile.write(b'GO %d\n' % udp)
|
||||
self.pfile.write(b'GO %d %s\n' % (udp, user))
|
||||
self.pfile.flush()
|
||||
|
||||
line = self.pfile.readline()
|
||||
@ -271,7 +295,7 @@ class FirewallClient:
|
||||
raise Fatal('%r expected STARTED, got %r' % (self.argv, line))
|
||||
|
||||
def sethostip(self, hostname, ip):
|
||||
assert(not re.search(b'[^-\w]', hostname))
|
||||
assert(not re.search(b'[^-\w\.]', hostname))
|
||||
assert(not re.search(b'[^0-9.]', ip))
|
||||
self.pfile.write(b'HOST %s,%s\n' % (hostname, ip))
|
||||
self.pfile.flush()
|
||||
@ -362,7 +386,7 @@ def onaccept_udp(listener, method, mux, handlers):
|
||||
srcip, dstip, data = t
|
||||
debug1('Accept UDP: %r -> %r.\n' % (srcip, dstip,))
|
||||
if srcip in udp_by_src:
|
||||
chan, timeout = udp_by_src[srcip]
|
||||
chan, _ = udp_by_src[srcip]
|
||||
else:
|
||||
chan = mux.next_channel()
|
||||
mux.channels[chan] = lambda cmd, data: udp_done(
|
||||
@ -400,7 +424,8 @@ def ondns(listener, method, mux, handlers):
|
||||
|
||||
def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
||||
python, latency_control,
|
||||
dns_listener, seed_hosts, auto_hosts, auto_nets, daemon):
|
||||
dns_listener, seed_hosts, auto_hosts, auto_nets, daemon,
|
||||
to_nameserver):
|
||||
|
||||
debug1('Starting client with Python version %s\n'
|
||||
% platform.python_version())
|
||||
@ -419,7 +444,8 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
||||
ssh_cmd, remotename, python,
|
||||
stderr=ssyslog._p and ssyslog._p.stdin,
|
||||
options=dict(latency_control=latency_control,
|
||||
auto_hosts=auto_hosts))
|
||||
auto_hosts=auto_hosts,
|
||||
to_nameserver=to_nameserver))
|
||||
except socket.error as e:
|
||||
if e.args[0] == errno.EPIPE:
|
||||
raise Fatal("failed to establish ssh session (1)")
|
||||
@ -460,6 +486,7 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
||||
def onroutes(routestr):
|
||||
if auto_nets:
|
||||
for line in routestr.strip().split(b'\n'):
|
||||
if not line: continue
|
||||
(family, ip, width) = line.split(b',', 2)
|
||||
family = int(family)
|
||||
width = int(width)
|
||||
@ -470,7 +497,7 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
||||
debug2("Ignored auto net %d/%s/%d\n" % (family, ip, width))
|
||||
else:
|
||||
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
|
||||
# up intercepting the ssh connection!
|
||||
@ -516,7 +543,8 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
||||
def main(listenip_v6, listenip_v4,
|
||||
ssh_cmd, remotename, python, latency_control, dns, nslist,
|
||||
method_name, seed_hosts, auto_hosts, auto_nets,
|
||||
subnets_include, subnets_exclude, daemon, pidfile):
|
||||
subnets_include, subnets_exclude, daemon, to_nameserver, pidfile,
|
||||
user):
|
||||
|
||||
if daemon:
|
||||
try:
|
||||
@ -531,6 +559,11 @@ def main(listenip_v6, listenip_v4,
|
||||
# Get family specific subnet lists
|
||||
if dns:
|
||||
nslist += resolvconf_nameservers()
|
||||
if to_nameserver is not None:
|
||||
to_nameserver = "%s@%s" % tuple(to_nameserver[1:])
|
||||
else:
|
||||
# option doesn't make sense if we aren't proxying dns
|
||||
to_nameserver = None
|
||||
|
||||
subnets = subnets_include + subnets_exclude # we don't care here
|
||||
subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6]
|
||||
@ -548,10 +581,19 @@ def main(listenip_v6, listenip_v4,
|
||||
else:
|
||||
listenip_v6 = None
|
||||
|
||||
if user is not None:
|
||||
if getpwnam is None:
|
||||
raise Fatal("Routing by user not available on this system.")
|
||||
try:
|
||||
user = getpwnam(user).pw_uid
|
||||
except KeyError:
|
||||
raise Fatal("User %s does not exist." % user)
|
||||
|
||||
required.ipv6 = len(subnets_v6) > 0 or listenip_v6 is not None
|
||||
required.ipv4 = len(subnets_v4) > 0 or listenip_v4 is not None
|
||||
required.udp = avail.udp
|
||||
required.dns = len(nslist) > 0
|
||||
required.user = False if user is None else True
|
||||
|
||||
# if IPv6 not supported, ignore IPv6 DNS servers
|
||||
if not required.ipv6:
|
||||
@ -567,6 +609,7 @@ def main(listenip_v6, listenip_v4,
|
||||
debug1("IPv6 enabled: %r\n" % required.ipv6)
|
||||
debug1("UDP enabled: %r\n" % required.udp)
|
||||
debug1("DNS enabled: %r\n" % required.dns)
|
||||
debug1("User enabled: %r\n" % required.user)
|
||||
|
||||
# bind to required ports
|
||||
if listenip_v4 == "auto":
|
||||
@ -574,11 +617,11 @@ def main(listenip_v6, listenip_v4,
|
||||
|
||||
if required.ipv4 and \
|
||||
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 \
|
||||
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 both ports given, no need to search for a spare port
|
||||
@ -586,6 +629,9 @@ def main(listenip_v6, listenip_v4,
|
||||
else:
|
||||
# if at least one port missing, we have to search
|
||||
ports = range(12300, 9000, -1)
|
||||
# keep track of failed bindings and used ports to avoid trying to
|
||||
# bind to the same socket address twice in different listeners
|
||||
used_ports = []
|
||||
|
||||
# search for free ports and try to bind
|
||||
last_e = None
|
||||
@ -627,10 +673,12 @@ def main(listenip_v6, listenip_v4,
|
||||
if udp_listener:
|
||||
udp_listener.bind(lv6, lv4)
|
||||
bound = True
|
||||
used_ports.append(port)
|
||||
break
|
||||
except socket.error as e:
|
||||
if e.errno == errno.EADDRINUSE:
|
||||
last_e = e
|
||||
used_ports.append(port)
|
||||
else:
|
||||
raise e
|
||||
|
||||
@ -650,6 +698,8 @@ def main(listenip_v6, listenip_v4,
|
||||
ports = range(12300, 9000, -1)
|
||||
for port in ports:
|
||||
debug2(' %d' % port)
|
||||
if port in used_ports: continue
|
||||
|
||||
dns_listener = MultiListener(socket.SOCK_DGRAM)
|
||||
|
||||
if listenip_v6:
|
||||
@ -669,10 +719,12 @@ def main(listenip_v6, listenip_v4,
|
||||
try:
|
||||
dns_listener.bind(lv6, lv4)
|
||||
bound = True
|
||||
used_ports.append(port)
|
||||
break
|
||||
except socket.error as e:
|
||||
if e.errno == errno.EADDRINUSE:
|
||||
last_e = e
|
||||
used_ports.append(port)
|
||||
else:
|
||||
raise e
|
||||
debug2('\n')
|
||||
@ -688,22 +740,22 @@ def main(listenip_v6, listenip_v4,
|
||||
# Last minute sanity checks.
|
||||
# These should never fail.
|
||||
# If these do fail, something is broken above.
|
||||
if len(subnets_v6) > 0:
|
||||
if subnets_v6:
|
||||
assert required.ipv6
|
||||
if redirectport_v6 == 0:
|
||||
raise Fatal("IPv6 subnets defined but not listening")
|
||||
|
||||
if len(nslist_v6) > 0:
|
||||
if nslist_v6:
|
||||
assert required.dns
|
||||
assert required.ipv6
|
||||
if dnsport_v6 == 0:
|
||||
raise Fatal("IPv6 ns servers defined but not listening")
|
||||
|
||||
if len(subnets_v4) > 0:
|
||||
if subnets_v4:
|
||||
if redirectport_v4 == 0:
|
||||
raise Fatal("IPv4 subnets defined but not listening")
|
||||
|
||||
if len(nslist_v4) > 0:
|
||||
if nslist_v4:
|
||||
if dnsport_v4 == 0:
|
||||
raise Fatal("IPv4 ns servers defined but not listening")
|
||||
|
||||
@ -717,13 +769,13 @@ def main(listenip_v6, listenip_v4,
|
||||
# start the firewall
|
||||
fw.setup(subnets_include, subnets_exclude, nslist,
|
||||
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4,
|
||||
required.udp)
|
||||
required.udp, user)
|
||||
|
||||
# start the client process
|
||||
try:
|
||||
return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
||||
python, latency_control, dns_listener,
|
||||
seed_hosts, auto_hosts, auto_nets, daemon)
|
||||
seed_hosts, auto_hosts, auto_nets, daemon, to_nameserver)
|
||||
finally:
|
||||
try:
|
||||
if daemon:
|
||||
|
@ -1,10 +1,11 @@
|
||||
import re
|
||||
import socket
|
||||
import sshuttle.helpers as helpers
|
||||
import sshuttle.client as client
|
||||
import sshuttle.firewall as firewall
|
||||
import sshuttle.hostwatch as hostwatch
|
||||
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
|
||||
|
||||
|
||||
@ -20,13 +21,13 @@ def main():
|
||||
|
||||
try:
|
||||
if opt.firewall:
|
||||
if opt.subnets:
|
||||
if opt.subnets or opt.subnets_file:
|
||||
parser.error('exactly zero arguments expected')
|
||||
return firewall.main(opt.method, opt.syslog)
|
||||
elif opt.hostwatch:
|
||||
return hostwatch.hw_main(opt.subnets)
|
||||
return hostwatch.hw_main(opt.subnets, opt.auto_hosts)
|
||||
else:
|
||||
includes = opt.subnets
|
||||
includes = opt.subnets + opt.subnets_file
|
||||
excludes = opt.exclude
|
||||
if not includes and not opt.auto_nets:
|
||||
parser.error('at least one subnet, subnet file, '
|
||||
@ -44,12 +45,13 @@ def main():
|
||||
if opt.listen:
|
||||
ipport_v6 = None
|
||||
ipport_v4 = None
|
||||
list = opt.listen.split(",")
|
||||
for ip in list:
|
||||
if '[' in ip and ']' in ip:
|
||||
ipport_v6 = parse_ipport6(ip)
|
||||
lst = opt.listen.split(",")
|
||||
for ip in lst:
|
||||
family, ip, port = parse_ipport(ip)
|
||||
if family == socket.AF_INET6:
|
||||
ipport_v6 = (ip, port)
|
||||
else:
|
||||
ipport_v4 = parse_ipport4(ip)
|
||||
ipport_v4 = (ip, port)
|
||||
else:
|
||||
# parse_ipport4('127.0.0.1:0')
|
||||
ipport_v4 = "auto"
|
||||
@ -71,7 +73,10 @@ def main():
|
||||
opt.auto_nets,
|
||||
includes,
|
||||
excludes,
|
||||
opt.daemon, opt.pidfile)
|
||||
opt.daemon,
|
||||
opt.to_ns,
|
||||
opt.pidfile,
|
||||
opt.user)
|
||||
|
||||
if return_code == 0:
|
||||
log('Normal exit code, exiting...')
|
||||
|
@ -2,6 +2,7 @@ import errno
|
||||
import socket
|
||||
import signal
|
||||
import sshuttle.ssyslog as ssyslog
|
||||
import sshuttle.sdnotify as sdnotify
|
||||
import sys
|
||||
import os
|
||||
import platform
|
||||
@ -74,6 +75,16 @@ def setup_daemon():
|
||||
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
|
||||
# proxying stuff. If subnets is empty, we just delete our sshuttle rules;
|
||||
# otherwise we delete it, then make them from scratch.
|
||||
@ -119,10 +130,17 @@ def main(method_name, syslog):
|
||||
elif line.startswith("NSLIST\n"):
|
||||
break
|
||||
try:
|
||||
(family, width, exclude, ip) = line.strip().split(',', 3)
|
||||
(family, width, exclude, ip, fport, lport) = \
|
||||
line.strip().split(',', 5)
|
||||
except:
|
||||
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)
|
||||
|
||||
nslist = []
|
||||
@ -147,7 +165,7 @@ def main(method_name, syslog):
|
||||
_, _, ports = line.partition(" ")
|
||||
ports = ports.split(",")
|
||||
if len(ports) != 4:
|
||||
raise Fatal('firewall: expected 4 ports but got %n' % len(ports))
|
||||
raise Fatal('firewall: expected 4 ports but got %d' % len(ports))
|
||||
port_v6 = int(ports[0])
|
||||
port_v4 = int(ports[1])
|
||||
dnsport_v6 = int(ports[2])
|
||||
@ -171,9 +189,12 @@ def main(method_name, syslog):
|
||||
elif not line.startswith("GO "):
|
||||
raise Fatal('firewall: expected GO but got %r' % line)
|
||||
|
||||
_, _, udp = line.partition(" ")
|
||||
_, _, args = line.partition(" ")
|
||||
udp, user = args.strip().split(" ", 1)
|
||||
udp = bool(int(udp))
|
||||
debug2('firewall manager: Got udp: %r\n' % udp)
|
||||
if user == '-':
|
||||
user = None
|
||||
debug2('firewall manager: Got udp: %r, user: %r\n' % (udp, user))
|
||||
|
||||
subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6]
|
||||
nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6]
|
||||
@ -183,19 +204,23 @@ def main(method_name, syslog):
|
||||
try:
|
||||
debug1('firewall manager: setting up.\n')
|
||||
|
||||
if len(subnets_v6) > 0 or len(nslist_v6) > 0:
|
||||
if subnets_v6 or nslist_v6:
|
||||
debug2('firewall manager: setting up IPv6.\n')
|
||||
method.setup_firewall(
|
||||
port_v6, dnsport_v6, nslist_v6,
|
||||
socket.AF_INET6, subnets_v6, udp)
|
||||
socket.AF_INET6, subnets_v6, udp,
|
||||
user)
|
||||
|
||||
if len(subnets_v4) > 0 or len(nslist_v4) > 0:
|
||||
if subnets_v4 or nslist_v4:
|
||||
debug2('firewall manager: setting up IPv4.\n')
|
||||
method.setup_firewall(
|
||||
port_v4, dnsport_v4, nslist_v4,
|
||||
socket.AF_INET, subnets_v4, udp)
|
||||
socket.AF_INET, subnets_v4, udp,
|
||||
user)
|
||||
|
||||
stdout.write('STARTED\n')
|
||||
sdnotify.send(sdnotify.ready(),
|
||||
sdnotify.status('Connected'))
|
||||
|
||||
try:
|
||||
stdout.flush()
|
||||
@ -221,14 +246,15 @@ def main(method_name, syslog):
|
||||
break
|
||||
finally:
|
||||
try:
|
||||
sdnotify.send(sdnotify.stop())
|
||||
debug1('firewall manager: undoing changes.\n')
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
if len(subnets_v6) > 0 or len(nslist_v6) > 0:
|
||||
if subnets_v6 or nslist_v6:
|
||||
debug2('firewall manager: undoing IPv6 changes.\n')
|
||||
method.restore_firewall(port_v6, socket.AF_INET6, udp)
|
||||
method.restore_firewall(port_v6, socket.AF_INET6, udp, user)
|
||||
except:
|
||||
try:
|
||||
debug1("firewall manager: "
|
||||
@ -239,9 +265,9 @@ def main(method_name, syslog):
|
||||
pass
|
||||
|
||||
try:
|
||||
if len(subnets_v4) > 0 or len(nslist_v4) > 0:
|
||||
if subnets_v4 or nslist_v4:
|
||||
debug2('firewall manager: undoing IPv4 changes.\n')
|
||||
method.restore_firewall(port_v4, socket.AF_INET, udp)
|
||||
method.restore_firewall(port_v4, socket.AF_INET, udp, user)
|
||||
except:
|
||||
try:
|
||||
debug1("firewall manager: "
|
||||
|
@ -61,23 +61,27 @@ def read_host_cache():
|
||||
words = line.strip().split(',')
|
||||
if len(words) == 2:
|
||||
(name, ip) = words
|
||||
name = re.sub(r'[^-\w]', '-', name).strip()
|
||||
name = re.sub(r'[^-\w\.]', '-', name).strip()
|
||||
ip = re.sub(r'[^0-9.]', '', ip).strip()
|
||||
if name and ip:
|
||||
found_host(name, ip)
|
||||
|
||||
|
||||
def found_host(hostname, ip):
|
||||
hostname = re.sub(r'\..*', '', hostname)
|
||||
hostname = re.sub(r'[^-\w]', '_', hostname)
|
||||
def found_host(name, ip):
|
||||
hostname = re.sub(r'\..*', '', name)
|
||||
hostname = re.sub(r'[^-\w\.]', '_', hostname)
|
||||
if (ip.startswith('127.') or ip.startswith('255.') or
|
||||
hostname == 'localhost'):
|
||||
return
|
||||
oldip = hostnames.get(hostname)
|
||||
|
||||
if hostname != name:
|
||||
found_host(hostname, ip)
|
||||
|
||||
oldip = hostnames.get(name)
|
||||
if oldip != ip:
|
||||
hostnames[hostname] = ip
|
||||
debug1('Found: %s: %s\n' % (hostname, ip))
|
||||
sys.stdout.write('%s,%s\n' % (hostname, ip))
|
||||
hostnames[name] = ip
|
||||
debug1('Found: %s: %s\n' % (name, ip))
|
||||
sys.stdout.write('%s,%s\n' % (name, ip))
|
||||
write_host_cache()
|
||||
|
||||
|
||||
@ -247,7 +251,7 @@ def _enqueue(op, *args):
|
||||
|
||||
|
||||
def _stdin_still_ok(timeout):
|
||||
r, w, x = select.select([sys.stdin.fileno()], [], [], timeout)
|
||||
r, _, _ = select.select([sys.stdin.fileno()], [], [], timeout)
|
||||
if r:
|
||||
b = os.read(sys.stdin.fileno(), 4096)
|
||||
if not b:
|
||||
|
@ -1,3 +1,4 @@
|
||||
import re
|
||||
import os
|
||||
import socket
|
||||
import subprocess as ssubprocess
|
||||
@ -49,6 +50,39 @@ def ipt(family, table, *args):
|
||||
raise Fatal('%r returned %d' % (argv, rv))
|
||||
|
||||
|
||||
def nft(family, table, action, *args):
|
||||
if family == socket.AF_INET:
|
||||
argv = ['nft', action, 'ip', table] + list(args)
|
||||
elif family == socket.AF_INET6:
|
||||
argv = ['nft', action, 'ip6', table] + list(args)
|
||||
else:
|
||||
raise Exception('Unsupported family "%s"' % family_to_string(family))
|
||||
debug1('>> %s\n' % ' '.join(argv))
|
||||
env = {
|
||||
'PATH': os.environ['PATH'],
|
||||
'LC_ALL': "C",
|
||||
}
|
||||
rv = ssubprocess.call(argv, env=env)
|
||||
if rv:
|
||||
raise Fatal('%r returned %d' % (argv, rv))
|
||||
|
||||
|
||||
def nft_get_handle(expression, chain):
|
||||
cmd = 'nft'
|
||||
argv = [cmd, 'list', expression, '-a']
|
||||
env = {
|
||||
'PATH': os.environ['PATH'],
|
||||
'LC_ALL': "C",
|
||||
}
|
||||
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=env)
|
||||
for line in p.stdout:
|
||||
if (b'jump %s' % chain.encode('utf-8')) in line:
|
||||
return re.sub('.*# ', '', line.decode('utf-8'))
|
||||
rv = p.wait()
|
||||
if rv:
|
||||
raise Fatal('%r returned %d' % (argv, rv))
|
||||
|
||||
|
||||
_no_ttl_module = False
|
||||
|
||||
|
||||
|
@ -35,17 +35,21 @@ class BaseMethod(object):
|
||||
def set_firewall(self, firewall):
|
||||
self.firewall = firewall
|
||||
|
||||
def get_supported_features(self):
|
||||
@staticmethod
|
||||
def get_supported_features():
|
||||
result = Features()
|
||||
result.ipv6 = False
|
||||
result.udp = False
|
||||
result.dns = True
|
||||
result.user = False
|
||||
return result
|
||||
|
||||
def get_tcp_dstip(self, sock):
|
||||
@staticmethod
|
||||
def get_tcp_dstip(sock):
|
||||
return original_dst(sock)
|
||||
|
||||
def recv_udp(self, udp_listener, bufsize):
|
||||
@staticmethod
|
||||
def recv_udp(udp_listener, bufsize):
|
||||
debug3('Accept UDP using recvfrom.\n')
|
||||
data, srcip = udp_listener.recvfrom(bufsize)
|
||||
return (srcip, None, data)
|
||||
@ -64,19 +68,21 @@ class BaseMethod(object):
|
||||
|
||||
def assert_features(self, features):
|
||||
avail = self.get_supported_features()
|
||||
for key in ["udp", "dns", "ipv6"]:
|
||||
for key in ["udp", "dns", "ipv6", "user"]:
|
||||
if getattr(features, key) and not getattr(avail, key):
|
||||
raise Fatal(
|
||||
"Feature %s not supported with method %s.\n" %
|
||||
(key, self.name))
|
||||
|
||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
|
||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
||||
user):
|
||||
raise NotImplementedError()
|
||||
|
||||
def restore_firewall(self, port, family, udp):
|
||||
def restore_firewall(self, port, family, udp, user):
|
||||
raise NotImplementedError()
|
||||
|
||||
def firewall_command(self, line):
|
||||
@staticmethod
|
||||
def firewall_command(line):
|
||||
return False
|
||||
|
||||
|
||||
@ -96,10 +102,14 @@ def get_method(method_name):
|
||||
def get_auto_method():
|
||||
if _program_exists('iptables'):
|
||||
method_name = "nat"
|
||||
elif _program_exists('nft'):
|
||||
method_name = "nft"
|
||||
elif _program_exists('pfctl'):
|
||||
method_name = "pf"
|
||||
elif _program_exists('ipfw'):
|
||||
method_name = "ipfw"
|
||||
else:
|
||||
raise Fatal(
|
||||
"can't find either iptables or pfctl; check your PATH")
|
||||
"can't find either iptables, nft or pfctl; check your PATH")
|
||||
|
||||
return get_method(method_name)
|
||||
|
257
sshuttle/methods/ipfw.py
Normal file
257
sshuttle/methods/ipfw.py
Normal file
@ -0,0 +1,257 @@
|
||||
import os
|
||||
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, _, srcip = \
|
||||
listener.recvmsg(4096, socket.CMSG_SPACE(4))
|
||||
dstip = 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, _ = \
|
||||
listener.recvmsg((bufsize,), socket.CMSG_SPACE(4))
|
||||
dstip = 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, a.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,
|
||||
user):
|
||||
# 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 _, 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')
|
||||
ipfw('add', '1', 'allow',
|
||||
'udp',
|
||||
'from', 'any', 'to', 'any',
|
||||
'ipttl', '42')
|
||||
|
||||
if subnets:
|
||||
# create new subnet entries
|
||||
for _, 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, user):
|
||||
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
|
||||
from sshuttle.firewall import subnet_weight
|
||||
from sshuttle.helpers import family_to_string
|
||||
from sshuttle.linux import ipt, ipt_ttl, ipt_chain_exists, nonfatal
|
||||
from sshuttle.methods import BaseMethod
|
||||
@ -11,7 +12,8 @@ class Method(BaseMethod):
|
||||
# the multiple copies shouldn't have overlapping subnets, or only the most-
|
||||
# recently-started one will win (because we use "-I OUTPUT 1" instead of
|
||||
# "-A OUTPUT").
|
||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
|
||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
||||
user):
|
||||
# only ipv4 supported with NAT
|
||||
if family != socket.AF_INET:
|
||||
raise Exception(
|
||||
@ -28,41 +30,50 @@ class Method(BaseMethod):
|
||||
def _ipt_ttl(*args):
|
||||
return ipt_ttl(family, table, *args)
|
||||
|
||||
def _ipm(*args):
|
||||
return ipt(family, "mangle", *args)
|
||||
|
||||
chain = 'sshuttle-%s' % port
|
||||
|
||||
# basic cleanup/setup of chains
|
||||
self.restore_firewall(port, family, udp)
|
||||
self.restore_firewall(port, family, udp, user)
|
||||
|
||||
_ipt('-N', chain)
|
||||
_ipt('-F', chain)
|
||||
_ipt('-I', 'OUTPUT', '1', '-j', chain)
|
||||
_ipt('-I', 'PREROUTING', '1', '-j', chain)
|
||||
if user is not None:
|
||||
_ipm('-I', 'OUTPUT', '1', '-m', 'owner', '--uid-owner', str(user),
|
||||
'-j', 'MARK', '--set-mark', str(port))
|
||||
args = '-m', 'mark', '--mark', str(port), '-j', chain
|
||||
else:
|
||||
args = '-j', chain
|
||||
|
||||
_ipt('-I', 'OUTPUT', '1', *args)
|
||||
_ipt('-I', 'PREROUTING', '1', *args)
|
||||
|
||||
# create new subnet entries.
|
||||
for _, swidth, sexclude, snet, fport, lport \
|
||||
in sorted(subnets, key=subnet_weight, reverse=True):
|
||||
tcp_ports = ('-p', 'tcp')
|
||||
if fport:
|
||||
tcp_ports = tcp_ports + ('--dport', '%d:%d' % (fport, lport))
|
||||
|
||||
# create new subnet entries. Note that we're sorting in a very
|
||||
# particular order: we need to go from most-specific (largest
|
||||
# swidth) to least-specific, and at any given level of specificity,
|
||||
# we want excludes to come first. That's why the columns are in
|
||||
# such a non- intuitive order.
|
||||
for f, swidth, sexclude, snet \
|
||||
in sorted(subnets, key=lambda s: s[1], reverse=True):
|
||||
if sexclude:
|
||||
_ipt('-A', chain, '-j', 'RETURN',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-p', 'tcp')
|
||||
*tcp_ports)
|
||||
else:
|
||||
_ipt_ttl('-A', chain, '-j', 'REDIRECT',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-p', 'tcp',
|
||||
'--to-ports', str(port))
|
||||
*(tcp_ports + ('--to-ports', str(port))))
|
||||
|
||||
for f, ip in [i for i in nslist if i[0] == family]:
|
||||
for _, ip in [i for i in nslist if i[0] == family]:
|
||||
_ipt_ttl('-A', chain, '-j', 'REDIRECT',
|
||||
'--dest', '%s/32' % ip,
|
||||
'-p', 'udp',
|
||||
'--dport', '53',
|
||||
'--to-ports', str(dnsport))
|
||||
|
||||
def restore_firewall(self, port, family, udp):
|
||||
def restore_firewall(self, port, family, udp, user):
|
||||
# only ipv4 supported with NAT
|
||||
if family != socket.AF_INET:
|
||||
raise Exception(
|
||||
@ -79,11 +90,25 @@ class Method(BaseMethod):
|
||||
def _ipt_ttl(*args):
|
||||
return ipt_ttl(family, table, *args)
|
||||
|
||||
def _ipm(*args):
|
||||
return ipt(family, "mangle", *args)
|
||||
|
||||
chain = 'sshuttle-%s' % port
|
||||
|
||||
# basic cleanup/setup of chains
|
||||
if ipt_chain_exists(family, table, chain):
|
||||
nonfatal(_ipt, '-D', 'OUTPUT', '-j', chain)
|
||||
nonfatal(_ipt, '-D', 'PREROUTING', '-j', chain)
|
||||
if user is not None:
|
||||
nonfatal(_ipm, '-D', 'OUTPUT', '-m', 'owner', '--uid-owner',
|
||||
str(user), '-j', 'MARK', '--set-mark', str(port))
|
||||
args = '-m', 'mark', '--mark', str(port), '-j', chain
|
||||
else:
|
||||
args = '-j', chain
|
||||
nonfatal(_ipt, '-D', 'OUTPUT', *args)
|
||||
nonfatal(_ipt, '-D', 'PREROUTING', *args)
|
||||
nonfatal(_ipt, '-F', chain)
|
||||
_ipt('-X', chain)
|
||||
|
||||
def get_supported_features(self):
|
||||
result = super(Method, self).get_supported_features()
|
||||
result.user = True
|
||||
return result
|
||||
|
80
sshuttle/methods/nft.py
Normal file
80
sshuttle/methods/nft.py
Normal file
@ -0,0 +1,80 @@
|
||||
import socket
|
||||
from sshuttle.firewall import subnet_weight
|
||||
from sshuttle.linux import nft, nft_get_handle, nonfatal
|
||||
from sshuttle.methods import BaseMethod
|
||||
|
||||
|
||||
class Method(BaseMethod):
|
||||
|
||||
# We name the chain based on the transproxy port number so that it's
|
||||
# possible to run multiple copies of sshuttle at the same time. Of course,
|
||||
# the multiple copies shouldn't have overlapping subnets, or only the most-
|
||||
# recently-started one will win (because we use "-I OUTPUT 1" instead of
|
||||
# "-A OUTPUT").
|
||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
||||
user):
|
||||
if udp:
|
||||
raise Exception("UDP not supported by nft")
|
||||
|
||||
table = "nat"
|
||||
|
||||
def _nft(action, *args):
|
||||
return nft(family, table, action, *args)
|
||||
|
||||
chain = 'sshuttle-%s' % port
|
||||
|
||||
# basic cleanup/setup of chains
|
||||
_nft('add table', '')
|
||||
_nft('add chain', 'prerouting',
|
||||
'{ type nat hook prerouting priority -100; policy accept; }')
|
||||
_nft('add chain', 'postrouting',
|
||||
'{ type nat hook postrouting priority 100; policy accept; }')
|
||||
_nft('add chain', 'output',
|
||||
'{ type nat hook output priority -100; policy accept; }')
|
||||
_nft('add chain', chain)
|
||||
_nft('flush chain', chain)
|
||||
_nft('add rule', 'output jump %s' % chain)
|
||||
_nft('add rule', 'prerouting jump %s' % chain)
|
||||
|
||||
# create new subnet entries.
|
||||
for _, swidth, sexclude, snet, fport, lport \
|
||||
in sorted(subnets, key=subnet_weight, reverse=True):
|
||||
tcp_ports = ('ip', 'protocol', 'tcp')
|
||||
if fport:
|
||||
tcp_ports = tcp_ports + ('dport { %d-%d }' % (fport, lport))
|
||||
|
||||
if sexclude:
|
||||
_nft('add rule', chain, *(tcp_ports + (
|
||||
'ip daddr %s/%s' % (snet, swidth), 'return')))
|
||||
else:
|
||||
_nft('add rule', chain, *(tcp_ports + (
|
||||
'ip daddr %s/%s' % (snet, swidth), 'ip ttl != 42',
|
||||
('redirect to :' + str(port)))))
|
||||
|
||||
for _, ip in [i for i in nslist if i[0] == family]:
|
||||
if family == socket.AF_INET:
|
||||
_nft('add rule', chain, 'ip protocol udp ip daddr %s' % ip,
|
||||
'udp dport { 53 }', 'ip ttl != 42',
|
||||
('redirect to :' + str(dnsport)))
|
||||
elif family == socket.AF_INET6:
|
||||
_nft('add rule', chain, 'ip6 protocol udp ip6 daddr %s' % ip,
|
||||
'udp dport { 53 }', 'ip ttl != 42',
|
||||
('redirect to :' + str(dnsport)))
|
||||
|
||||
def restore_firewall(self, port, family, udp, user):
|
||||
if udp:
|
||||
raise Exception("UDP not supported by nft method_name")
|
||||
|
||||
table = "nat"
|
||||
|
||||
def _nft(action, *args):
|
||||
return nft(family, table, action, *args)
|
||||
|
||||
chain = 'sshuttle-%s' % port
|
||||
|
||||
# basic cleanup/setup of chains
|
||||
handle = nft_get_handle('chain ip nat output', chain)
|
||||
nonfatal(_nft, 'delete rule', 'output', handle)
|
||||
handle = nft_get_handle('chain ip nat prerouting', chain)
|
||||
nonfatal(_nft, 'delete rule', 'prerouting', handle)
|
||||
nonfatal(_nft, 'delete chain', chain)
|
@ -1,17 +1,24 @@
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import re
|
||||
import socket
|
||||
import struct
|
||||
import subprocess as ssubprocess
|
||||
import shlex
|
||||
from fcntl import ioctl
|
||||
from ctypes import c_char, c_uint8, c_uint16, c_uint32, Union, Structure, \
|
||||
sizeof, addressof, memmove
|
||||
from sshuttle.firewall import subnet_weight
|
||||
from sshuttle.helpers import debug1, debug2, debug3, Fatal, family_to_string
|
||||
from sshuttle.methods import BaseMethod
|
||||
|
||||
|
||||
_pf_context = {'started_by_sshuttle': False, 'Xtoken': []}
|
||||
_pf_context = {
|
||||
'started_by_sshuttle': 0,
|
||||
'loaded_by_sshuttle': True,
|
||||
'Xtoken': []
|
||||
}
|
||||
_pf_fd = None
|
||||
|
||||
|
||||
@ -28,11 +35,11 @@ class Generic(object):
|
||||
|
||||
class pf_addr(Structure):
|
||||
class _pfa(Union):
|
||||
_fields_ = [("v4", c_uint32), # struct in_addr
|
||||
("v6", c_uint32 * 4), # struct in6_addr
|
||||
("addr8", c_uint8 * 16),
|
||||
("addr16", c_uint16 * 8),
|
||||
("addr32", c_uint32 * 4)]
|
||||
_fields_ = [("v4", c_uint32), # struct in_addr
|
||||
("v6", c_uint32 * 4), # struct in6_addr
|
||||
("addr8", c_uint8 * 16),
|
||||
("addr16", c_uint16 * 8),
|
||||
("addr32", c_uint32 * 4)]
|
||||
|
||||
_fields_ = [("pfa", _pfa)]
|
||||
_anonymous_ = ("pfa",)
|
||||
@ -57,12 +64,14 @@ class Generic(object):
|
||||
def enable(self):
|
||||
if b'INFO:\nStatus: Disabled' in self.status:
|
||||
pfctl('-e')
|
||||
_pf_context['started_by_sshuttle'] = True
|
||||
_pf_context['started_by_sshuttle'] += 1
|
||||
|
||||
def disable(self, anchor):
|
||||
@staticmethod
|
||||
def disable(anchor):
|
||||
pfctl('-a %s -F all' % anchor)
|
||||
if _pf_context['started_by_sshuttle']:
|
||||
if _pf_context['started_by_sshuttle'] == 1:
|
||||
pfctl('-d')
|
||||
_pf_context['started_by_sshuttle'] -= 1
|
||||
|
||||
def query_nat(self, family, proto, src_ip, src_port, dst_ip, dst_port):
|
||||
[proto, family, src_port, dst_port] = [
|
||||
@ -90,11 +99,13 @@ class Generic(object):
|
||||
port = socket.ntohs(self._get_natlook_port(pnl.rdxport))
|
||||
return (ip, port)
|
||||
|
||||
def _add_natlook_ports(self, pnl, src_port, dst_port):
|
||||
@staticmethod
|
||||
def _add_natlook_ports(pnl, src_port, dst_port):
|
||||
pnl.sxport = socket.htons(src_port)
|
||||
pnl.dxport = socket.htons(dst_port)
|
||||
|
||||
def _get_natlook_port(self, xport):
|
||||
@staticmethod
|
||||
def _get_natlook_port(xport):
|
||||
return xport
|
||||
|
||||
def add_anchors(self, anchor, status=None):
|
||||
@ -104,35 +115,39 @@ class Generic(object):
|
||||
if ('\nanchor "%s"' % anchor).encode('ASCII') not in status:
|
||||
self._add_anchor_rule(self.PF_PASS, anchor.encode('ASCII'))
|
||||
|
||||
def _add_anchor_rule(self, type, name, pr=None):
|
||||
def _add_anchor_rule(self, kind, name, pr=None):
|
||||
if pr is None:
|
||||
pr = self.pfioc_rule()
|
||||
|
||||
memmove(addressof(pr) + self.ANCHOR_CALL_OFFSET, name,
|
||||
min(self.MAXPATHLEN, len(name))) # anchor_call = name
|
||||
min(self.MAXPATHLEN, len(name))) # anchor_call = name
|
||||
memmove(addressof(pr) + self.RULE_ACTION_OFFSET,
|
||||
struct.pack('I', type), 4) # rule.action = type
|
||||
struct.pack('I', kind), 4) # rule.action = kind
|
||||
|
||||
memmove(addressof(pr) + self.ACTION_OFFSET, struct.pack(
|
||||
'I', self.PF_CHANGE_GET_TICKET), 4) # action = PF_CHANGE_GET_TICKET
|
||||
'I', self.PF_CHANGE_GET_TICKET), 4) # action = PF_CHANGE_GET_TICKET
|
||||
ioctl(pf_get_dev(), pf.DIOCCHANGERULE, pr)
|
||||
|
||||
memmove(addressof(pr) + self.ACTION_OFFSET, struct.pack(
|
||||
'I', self.PF_CHANGE_ADD_TAIL), 4) # action = PF_CHANGE_ADD_TAIL
|
||||
'I', self.PF_CHANGE_ADD_TAIL), 4) # action = PF_CHANGE_ADD_TAIL
|
||||
ioctl(pf_get_dev(), pf.DIOCCHANGERULE, pr)
|
||||
|
||||
def _inet_version(self, family):
|
||||
@staticmethod
|
||||
def _inet_version(family):
|
||||
return b'inet' if family == socket.AF_INET else b'inet6'
|
||||
|
||||
def _lo_addr(self, family):
|
||||
@staticmethod
|
||||
def _lo_addr(family):
|
||||
return b'127.0.0.1' if family == socket.AF_INET else b'::1'
|
||||
|
||||
def add_rules(self, anchor, rules):
|
||||
@staticmethod
|
||||
def add_rules(anchor, rules):
|
||||
assert isinstance(rules, bytes)
|
||||
debug3("rules:\n" + rules.decode("ASCII"))
|
||||
pfctl('-a %s -f /dev/stdin' % anchor, rules)
|
||||
|
||||
def has_skip_loopback(self):
|
||||
@staticmethod
|
||||
def has_skip_loopback():
|
||||
return b'skip' in pfctl('-s Interfaces -i lo -v')[0]
|
||||
|
||||
|
||||
@ -161,8 +176,17 @@ class FreeBsd(Generic):
|
||||
freebsd.pfioc_natlook = pfioc_natlook
|
||||
return freebsd
|
||||
|
||||
def __init__(self):
|
||||
super(FreeBsd, self).__init__()
|
||||
def enable(self):
|
||||
returncode = ssubprocess.call(['kldload', 'pf'])
|
||||
super(FreeBsd, self).enable()
|
||||
if returncode == 0:
|
||||
_pf_context['loaded_by_sshuttle'] = True
|
||||
|
||||
def disable(self, anchor):
|
||||
super(FreeBsd, self).disable(anchor)
|
||||
if _pf_context['loaded_by_sshuttle'] and \
|
||||
_pf_context['started_by_sshuttle'] == 0:
|
||||
ssubprocess.call(['kldunload', 'pf'])
|
||||
|
||||
def add_anchors(self, anchor):
|
||||
status = pfctl('-s all')[0]
|
||||
@ -170,32 +194,34 @@ class FreeBsd(Generic):
|
||||
self._add_anchor_rule(self.PF_RDR, anchor.encode('ASCII'))
|
||||
super(FreeBsd, self).add_anchors(anchor, status=status)
|
||||
|
||||
def _add_anchor_rule(self, type, name):
|
||||
pr = self.pfioc_rule()
|
||||
def _add_anchor_rule(self, kind, name, pr=None):
|
||||
pr = pr or self.pfioc_rule()
|
||||
ppa = self.pfioc_pooladdr()
|
||||
|
||||
ioctl(pf_get_dev(), self.DIOCBEGINADDRS, ppa)
|
||||
# pool ticket
|
||||
memmove(addressof(pr) + self.POOL_TICKET_OFFSET, ppa[4:8], 4)
|
||||
super(FreeBsd, self)._add_anchor_rule(type, name, pr=pr)
|
||||
super(FreeBsd, self)._add_anchor_rule(kind, name, pr=pr)
|
||||
|
||||
def add_rules(self, anchor, includes, port, dnsport, nslist, family):
|
||||
inet_version = self._inet_version(family)
|
||||
lo_addr = self._lo_addr(family)
|
||||
|
||||
tables = [
|
||||
b'table <forward_subnets> {%s}' % b','.join(includes)
|
||||
]
|
||||
tables = []
|
||||
translating_rules = [
|
||||
b'rdr pass on lo0 %s proto tcp to <forward_subnets> '
|
||||
b'-> %s port %r' % (inet_version, lo_addr, port)
|
||||
b'rdr pass on lo0 %s proto tcp from ! %s to %s '
|
||||
b'-> %s port %r' % (inet_version, lo_addr, subnet, lo_addr, port)
|
||||
for exclude, subnet in includes if not exclude
|
||||
]
|
||||
filtering_rules = [
|
||||
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 nslist:
|
||||
tables.append(
|
||||
b'table <dns_servers> {%s}' %
|
||||
b','.join([ns[1].encode("ASCII") for ns in nslist]))
|
||||
@ -251,19 +277,21 @@ class OpenBsd(Generic):
|
||||
inet_version = self._inet_version(family)
|
||||
lo_addr = self._lo_addr(family)
|
||||
|
||||
tables = [
|
||||
b'table <forward_subnets> {%s}' % b','.join(includes)
|
||||
]
|
||||
tables = []
|
||||
translating_rules = [
|
||||
b'pass in on lo0 %s proto tcp to <forward_subnets> '
|
||||
b'divert-to %s port %r' % (inet_version, lo_addr, port)
|
||||
b'pass in on lo0 %s proto tcp to %s '
|
||||
b'divert-to %s port %r' % (inet_version, subnet, lo_addr, port)
|
||||
for exclude, subnet in includes if not exclude
|
||||
]
|
||||
filtering_rules = [
|
||||
b'pass out %s proto tcp to <forward_subnets> '
|
||||
b'route-to lo0 keep state' % inet_version
|
||||
b'pass out %s proto tcp to %s '
|
||||
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 nslist:
|
||||
tables.append(
|
||||
b'table <dns_servers> {%s}' %
|
||||
b','.join([ns[1].encode("ASCII") for ns in nslist]))
|
||||
@ -333,16 +361,26 @@ class Darwin(FreeBsd):
|
||||
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':
|
||||
pf = Darwin()
|
||||
elif sys.platform.startswith('openbsd'):
|
||||
pf = OpenBsd()
|
||||
elif platform.version().endswith('pfSense'):
|
||||
pf = PfSense()
|
||||
else:
|
||||
pf = FreeBsd()
|
||||
|
||||
|
||||
def pfctl(args, stdin=None):
|
||||
argv = ['pfctl'] + list(args.split(" "))
|
||||
argv = ['pfctl'] + shlex.split(args)
|
||||
debug1('>> %s\n' % ' '.join(argv))
|
||||
|
||||
env = {
|
||||
@ -399,11 +437,8 @@ class Method(BaseMethod):
|
||||
|
||||
return sock.getsockname()
|
||||
|
||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
|
||||
tables = []
|
||||
translating_rules = []
|
||||
filtering_rules = []
|
||||
|
||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
||||
user):
|
||||
if family not in [socket.AF_INET, socket.AF_INET6]:
|
||||
raise Exception(
|
||||
'Address family "%s" unsupported by pf method_name'
|
||||
@ -411,24 +446,24 @@ class Method(BaseMethod):
|
||||
if udp:
|
||||
raise Exception("UDP not supported by pf method_name")
|
||||
|
||||
if len(subnets) > 0:
|
||||
if subnets:
|
||||
includes = []
|
||||
# If a given subnet is both included and excluded, list the
|
||||
# exclusion first; the table will ignore the second, opposite
|
||||
# definition
|
||||
for f, swidth, sexclude, snet in sorted(
|
||||
subnets, key=lambda s: (s[1], s[2]), reverse=True):
|
||||
includes.append(b"%s%s/%d" %
|
||||
(b"!" if sexclude else b"",
|
||||
snet.encode("ASCII"),
|
||||
swidth))
|
||||
for _, swidth, sexclude, snet, fport, lport \
|
||||
in sorted(subnets, key=subnet_weight, reverse=True):
|
||||
includes.append((sexclude, b"%s/%d%s" % (
|
||||
snet.encode("ASCII"),
|
||||
swidth,
|
||||
b" port %d:%d" % (fport, lport) if fport else b"")))
|
||||
|
||||
anchor = pf_get_anchor(family, port)
|
||||
pf.add_anchors(anchor)
|
||||
pf.add_rules(anchor, includes, port, dnsport, nslist, family)
|
||||
pf.enable()
|
||||
|
||||
def restore_firewall(self, port, family, udp):
|
||||
def restore_firewall(self, port, family, udp, user):
|
||||
if family not in [socket.AF_INET, socket.AF_INET6]:
|
||||
raise Exception(
|
||||
'Address family "%s" unsupported by pf method_name'
|
||||
|
@ -1,4 +1,5 @@
|
||||
import struct
|
||||
from sshuttle.firewall import subnet_weight
|
||||
from sshuttle.helpers import family_to_string
|
||||
from sshuttle.linux import ipt, ipt_ttl, ipt_chain_exists
|
||||
from sshuttle.methods import BaseMethod
|
||||
@ -32,7 +33,7 @@ IPV6_RECVORIGDSTADDR = IPV6_ORIGDSTADDR
|
||||
if recvmsg == "python":
|
||||
def recv_udp(listener, bufsize):
|
||||
debug3('Accept UDP python using recvmsg.\n')
|
||||
data, ancdata, msg_flags, srcip = listener.recvmsg(
|
||||
data, ancdata, _, srcip = listener.recvmsg(
|
||||
4096, socket.CMSG_SPACE(24))
|
||||
dstip = None
|
||||
family = None
|
||||
@ -63,7 +64,7 @@ if recvmsg == "python":
|
||||
elif recvmsg == "socket_ext":
|
||||
def recv_udp(listener, bufsize):
|
||||
debug3('Accept UDP using socket_ext recvmsg.\n')
|
||||
srcip, data, adata, flags = listener.recvmsg(
|
||||
srcip, data, adata, _ = listener.recvmsg(
|
||||
(bufsize,), socket.CMSG_SPACE(24))
|
||||
dstip = None
|
||||
family = None
|
||||
@ -149,7 +150,8 @@ class Method(BaseMethod):
|
||||
if udp_listener.v6 is not None:
|
||||
udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
|
||||
|
||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
|
||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
||||
user):
|
||||
if family not in [socket.AF_INET, socket.AF_INET6]:
|
||||
raise Exception(
|
||||
'Address family "%s" unsupported by tproxy method'
|
||||
@ -163,12 +165,17 @@ class Method(BaseMethod):
|
||||
def _ipt_ttl(*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
|
||||
tproxy_chain = 'sshuttle-t-%s' % port
|
||||
divert_chain = 'sshuttle-d-%s' % port
|
||||
|
||||
# basic cleanup/setup of chains
|
||||
self.restore_firewall(port, family, udp)
|
||||
self.restore_firewall(port, family, udp, user)
|
||||
|
||||
_ipt('-N', mark_chain)
|
||||
_ipt('-F', mark_chain)
|
||||
@ -187,7 +194,7 @@ class Method(BaseMethod):
|
||||
_ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain,
|
||||
'-m', 'udp', '-p', 'udp')
|
||||
|
||||
for f, ip in [i for i in nslist if i[0] == family]:
|
||||
for _, ip in [i for i in nslist if i[0] == family]:
|
||||
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
|
||||
'--dest', '%s/32' % ip,
|
||||
'-m', 'udp', '-p', 'udp', '--dport', '53')
|
||||
@ -197,33 +204,44 @@ class Method(BaseMethod):
|
||||
'-m', 'udp', '-p', 'udp', '--dport', '53',
|
||||
'--on-port', str(dnsport))
|
||||
|
||||
for f, swidth, sexclude, snet \
|
||||
in sorted(subnets, key=lambda s: s[1], reverse=True):
|
||||
for _, swidth, sexclude, snet, fport, lport \
|
||||
in sorted(subnets, key=subnet_weight, reverse=True):
|
||||
tcp_ports = ('-p', 'tcp')
|
||||
tcp_ports = _ipt_proto_ports(tcp_ports, fport, lport)
|
||||
|
||||
if sexclude:
|
||||
_ipt('-A', mark_chain, '-j', 'RETURN',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'tcp', '-p', 'tcp')
|
||||
'-m', 'tcp',
|
||||
*tcp_ports)
|
||||
_ipt('-A', tproxy_chain, '-j', 'RETURN',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'tcp', '-p', 'tcp')
|
||||
'-m', 'tcp',
|
||||
*tcp_ports)
|
||||
else:
|
||||
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'tcp', '-p', 'tcp')
|
||||
'-m', 'tcp',
|
||||
*tcp_ports)
|
||||
_ipt('-A', tproxy_chain, '-j', 'TPROXY',
|
||||
'--tproxy-mark', '0x1/0x1',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'tcp', '-p', 'tcp',
|
||||
'--on-port', str(port))
|
||||
'-m', 'tcp',
|
||||
*(tcp_ports + ('--on-port', str(port))))
|
||||
|
||||
if udp:
|
||||
udp_ports = ('-p', 'udp')
|
||||
udp_ports = _ipt_proto_ports(udp_ports, fport, lport)
|
||||
|
||||
if sexclude:
|
||||
_ipt('-A', mark_chain, '-j', 'RETURN',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'udp', '-p', 'udp')
|
||||
'-m', 'udp',
|
||||
*udp_ports)
|
||||
_ipt('-A', tproxy_chain, '-j', 'RETURN',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'udp', '-p', 'udp')
|
||||
'-m', 'udp',
|
||||
*udp_ports)
|
||||
else:
|
||||
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
@ -231,10 +249,10 @@ class Method(BaseMethod):
|
||||
_ipt('-A', tproxy_chain, '-j', 'TPROXY',
|
||||
'--tproxy-mark', '0x1/0x1',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'udp', '-p', 'udp',
|
||||
'--on-port', str(port))
|
||||
'-m', 'udp',
|
||||
*(udp_ports + ('--on-port', str(port))))
|
||||
|
||||
def restore_firewall(self, port, family, udp):
|
||||
def restore_firewall(self, port, family, udp, user):
|
||||
if family not in [socket.AF_INET, socket.AF_INET6]:
|
||||
raise Exception(
|
||||
'Address family "%s" unsupported by tproxy method'
|
||||
|
@ -4,41 +4,8 @@ from argparse import ArgumentParser, Action, ArgumentTypeError as Fatal
|
||||
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
|
||||
def parse_subnet_file(s):
|
||||
def parse_subnetport_file(s):
|
||||
try:
|
||||
handle = open(s, 'r')
|
||||
except OSError:
|
||||
@ -46,57 +13,76 @@ def parse_subnet_file(s):
|
||||
|
||||
raw_config_lines = handle.readlines()
|
||||
subnets = []
|
||||
for line_no, line in enumerate(raw_config_lines):
|
||||
for _, line in enumerate(raw_config_lines):
|
||||
line = line.strip()
|
||||
if len(line) == 0:
|
||||
if not line:
|
||||
continue
|
||||
if line[0] == '#':
|
||||
continue
|
||||
subnets.append(parse_subnet(line))
|
||||
subnets.append(parse_subnetport(line))
|
||||
|
||||
return subnets
|
||||
|
||||
|
||||
# 1.2.3.4/5 or just 1.2.3.4
|
||||
# 1:2::3/64 or just 1:2::3
|
||||
def parse_subnet(subnet_str):
|
||||
if ':' in subnet_str:
|
||||
return parse_subnet6(subnet_str)
|
||||
# 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]:456, [1:2::3]:456, 1:2::3/64 or just 1:2::3
|
||||
# example.com:123 or just example.com
|
||||
def parse_subnetport(s):
|
||||
if s.count(':') > 1:
|
||||
rx = r'(?:\[?([\w\:]+)(?:/(\d+))?]?)(?::(\d+)(?:-(\d+))?)?$'
|
||||
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
|
||||
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)
|
||||
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:
|
||||
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)
|
||||
|
||||
try:
|
||||
addrinfo = socket.getaddrinfo(ip, port, 0, socket.SOCK_STREAM)
|
||||
except socket.gaierror:
|
||||
raise Fatal('%r is not a valid IP:port format' % s)
|
||||
|
||||
family, _, _, _, addr = min(addrinfo)
|
||||
return (family,) + addr[:2]
|
||||
|
||||
|
||||
# [1:2::3]:456 or [1:2::3] or 456
|
||||
def parse_ipport6(s):
|
||||
s = str(s)
|
||||
m = re.match(r'(?:\[([^]]*)])?(?::)?(?:(\d+))?$', s)
|
||||
if not m:
|
||||
raise Fatal('%s is not a valid IP:port format' % s)
|
||||
(ip, port) = m.groups()
|
||||
(ip, port) = (ip or '::', int(port or 0))
|
||||
return (ip, port)
|
||||
|
||||
|
||||
def parse_list(list):
|
||||
return re.split(r'[\s,]+', list.strip()) if list else []
|
||||
def parse_list(lst):
|
||||
return re.split(r'[\s,]+', lst.strip()) if lst else []
|
||||
|
||||
|
||||
class Concat(Action):
|
||||
@ -106,19 +92,20 @@ class Concat(Action):
|
||||
super(Concat, self).__init__(option_strings, dest, **kwargs)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
parser = ArgumentParser(
|
||||
prog="sshuttle",
|
||||
usage="%(prog)s [-l [ip:]port] [-r [user@]sshserver[:port]] <subnets...>"
|
||||
usage="%(prog)s [-l [ip:]port] [-r [user@]sshserver[:port]] <subnets...>",
|
||||
fromfile_prefix_chars="@"
|
||||
)
|
||||
parser.add_argument(
|
||||
"subnets",
|
||||
metavar="IP/MASK [IP/MASK...]",
|
||||
metavar="IP/MASK[:PORT[-PORT]]...",
|
||||
nargs="*",
|
||||
type=parse_subnet,
|
||||
type=parse_subnetport,
|
||||
help="""
|
||||
capture and forward traffic to these subnets (whitespace separated)
|
||||
"""
|
||||
@ -134,7 +121,8 @@ parser.add_argument(
|
||||
"-H", "--auto-hosts",
|
||||
action="store_true",
|
||||
help="""
|
||||
continuously scan for remote hostnames and update local /etc/hosts as they are found
|
||||
continuously scan for remote hostnames and update local /etc/hosts as
|
||||
they are found
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
@ -160,9 +148,19 @@ parser.add_argument(
|
||||
capture and forward DNS requests made to the following servers
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
"--to-ns",
|
||||
metavar="IP[:PORT]",
|
||||
type=parse_ipport,
|
||||
help="""
|
||||
the DNS server to forward requests to; defaults to servers in
|
||||
/etc/resolv.conf on remote side if not given.
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--method",
|
||||
choices=["auto", "nat", "tproxy", "pf"],
|
||||
choices=["auto", "nat", "nft", "tproxy", "pf", "ipfw"],
|
||||
metavar="TYPE",
|
||||
default="auto",
|
||||
help="""
|
||||
@ -185,10 +183,10 @@ parser.add_argument(
|
||||
)
|
||||
parser.add_argument(
|
||||
"-x", "--exclude",
|
||||
metavar="IP/MASK",
|
||||
metavar="IP/MASK[:PORT[-PORT]]",
|
||||
action="append",
|
||||
default=[],
|
||||
type=parse_subnet,
|
||||
type=parse_subnetport,
|
||||
help="""
|
||||
exclude this subnet (can be used more than once)
|
||||
"""
|
||||
@ -198,7 +196,7 @@ parser.add_argument(
|
||||
metavar="PATH",
|
||||
action=Concat,
|
||||
dest="exclude",
|
||||
type=parse_subnet_file,
|
||||
type=parse_subnetport_file,
|
||||
help="""
|
||||
exclude the subnets in a file (whitespace separated)
|
||||
"""
|
||||
@ -232,7 +230,8 @@ parser.add_argument(
|
||||
metavar="HOSTNAME[,HOSTNAME]",
|
||||
default=[],
|
||||
help="""
|
||||
comma-separated list of hostnames for initial scan (may be used with or without --auto-hosts)
|
||||
comma-separated list of hostnames for initial scan (may be used with
|
||||
or without --auto-hosts)
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
@ -269,8 +268,9 @@ parser.add_argument(
|
||||
"-s", "--subnets",
|
||||
metavar="PATH",
|
||||
action=Concat,
|
||||
dest="subnets",
|
||||
type=parse_subnet_file,
|
||||
dest="subnets_file",
|
||||
default=[],
|
||||
type=parse_subnetport_file,
|
||||
help="""
|
||||
file where the subnets are stored, instead of on the command line
|
||||
"""
|
||||
@ -290,6 +290,12 @@ parser.add_argument(
|
||||
pidfile name (only if using --daemon) [%(default)s]
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
"--user",
|
||||
help="""
|
||||
apply all the rules only to this linux user
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
"--firewall",
|
||||
action="store_true",
|
||||
|
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, \
|
||||
resolvconf_random_nameserver
|
||||
|
||||
try:
|
||||
from shutil import which
|
||||
except ImportError:
|
||||
from distutils.spawn import find_executable as which
|
||||
|
||||
|
||||
def _ipmatch(ipstr):
|
||||
# FIXME: IPv4 only
|
||||
@ -60,9 +65,27 @@ def _shl(n, 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
|
||||
argv = ['netstat', '-rn']
|
||||
env = {
|
||||
'PATH': os.environ['PATH'],
|
||||
'LC_ALL': "C",
|
||||
@ -70,12 +93,11 @@ def _list_routes():
|
||||
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=env)
|
||||
routes = []
|
||||
for line in p.stdout:
|
||||
cols = re.split(r'\s+', line.decode("ASCII"))
|
||||
ipw = _ipmatch(cols[0])
|
||||
if not line.strip():
|
||||
continue
|
||||
ipw, mask = extract_route(line.decode("ASCII"))
|
||||
if not ipw:
|
||||
continue # some lines won't be parseable; never mind
|
||||
maskw = _ipmatch(cols[2]) # linux only
|
||||
mask = _maskbits(maskw) # returns 32 if maskw is null
|
||||
continue
|
||||
width = min(ipw[1], mask)
|
||||
ip = ipw[0] & _shl(_shl(1, width) - 1, 32 - width)
|
||||
routes.append(
|
||||
@ -84,11 +106,20 @@ def _list_routes():
|
||||
if rv != 0:
|
||||
log('WARNING: %r returned %d\n' % (argv, rv))
|
||||
log('WARNING: That prevents --auto-nets from working.\n')
|
||||
|
||||
return 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.'):
|
||||
yield (family, ip, width)
|
||||
|
||||
@ -129,7 +160,7 @@ class Hostwatch:
|
||||
|
||||
class DnsProxy(Handler):
|
||||
|
||||
def __init__(self, mux, chan, request):
|
||||
def __init__(self, mux, chan, request, to_nameserver):
|
||||
Handler.__init__(self, [])
|
||||
self.timeout = time.time() + 30
|
||||
self.mux = mux
|
||||
@ -137,22 +168,43 @@ class DnsProxy(Handler):
|
||||
self.tries = 0
|
||||
self.request = request
|
||||
self.peers = {}
|
||||
self.to_ns_peer = None
|
||||
self.to_ns_port = None
|
||||
if to_nameserver is None:
|
||||
self.to_nameserver = None
|
||||
else:
|
||||
self.to_ns_peer, self.to_ns_port = to_nameserver.split("@")
|
||||
self.to_nameserver = self._addrinfo(self.to_ns_peer,
|
||||
self.to_ns_port)
|
||||
self.try_send()
|
||||
|
||||
@staticmethod
|
||||
def _addrinfo(peer, port):
|
||||
if int(port) == 0:
|
||||
port = 53
|
||||
family, _, _, _, sockaddr = socket.getaddrinfo(peer, port)[0]
|
||||
return (family, sockaddr)
|
||||
|
||||
def try_send(self):
|
||||
if self.tries >= 3:
|
||||
return
|
||||
self.tries += 1
|
||||
|
||||
family, peer = resolvconf_random_nameserver()
|
||||
if self.to_nameserver is None:
|
||||
_, peer = resolvconf_random_nameserver()
|
||||
port = 53
|
||||
else:
|
||||
peer = self.to_ns_peer
|
||||
port = int(self.to_ns_port)
|
||||
|
||||
family, sockaddr = self._addrinfo(peer, port)
|
||||
sock = socket.socket(family, socket.SOCK_DGRAM)
|
||||
sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
|
||||
sock.connect((peer, 53))
|
||||
sock.connect(sockaddr)
|
||||
|
||||
self.peers[sock] = peer
|
||||
|
||||
debug2('DNS: sending to %r (try %d)\n' % (peer, self.tries))
|
||||
debug2('DNS: sending to %r:%d (try %d)\n' % (peer, port, self.tries))
|
||||
try:
|
||||
sock.send(self.request)
|
||||
self.socks.append(sock)
|
||||
@ -227,7 +279,7 @@ class UdpProxy(Handler):
|
||||
self.mux.send(self.chan, ssnet.CMD_UDP_DATA, hdr + data)
|
||||
|
||||
|
||||
def main(latency_control, auto_hosts):
|
||||
def main(latency_control, auto_hosts, to_nameserver):
|
||||
debug1('Starting server with Python version %s\n'
|
||||
% platform.python_version())
|
||||
|
||||
@ -278,7 +330,7 @@ def main(latency_control, auto_hosts):
|
||||
def got_host_req(data):
|
||||
if not hw.pid:
|
||||
(hw.pid, hw.sock) = start_hostwatch(
|
||||
data.strip().split(), auto_hosts)
|
||||
data.decode("ASCII").strip().split(), auto_hosts)
|
||||
handlers.append(Handler(socks=[hw.sock],
|
||||
callback=hostwatch_ready))
|
||||
mux.got_host_req = got_host_req
|
||||
@ -301,7 +353,7 @@ def main(latency_control, auto_hosts):
|
||||
|
||||
def dns_req(channel, data):
|
||||
debug2('Incoming DNS request channel=%d.\n' % channel)
|
||||
h = DnsProxy(mux, channel, data)
|
||||
h = DnsProxy(mux, channel, data, to_nameserver)
|
||||
handlers.append(h)
|
||||
dnshandlers[channel] = h
|
||||
mux.got_dns_req = dns_req
|
||||
|
@ -5,6 +5,7 @@ import socket
|
||||
import zlib
|
||||
import imp
|
||||
import subprocess as ssubprocess
|
||||
import shlex
|
||||
import sshuttle.helpers as helpers
|
||||
from sshuttle.helpers import debug2
|
||||
|
||||
@ -62,20 +63,20 @@ def empackage(z, name, data=None):
|
||||
def connect(ssh_cmd, rhostport, python, stderr, options):
|
||||
portl = []
|
||||
|
||||
if (rhostport or '').count(':') > 1:
|
||||
if re.sub(r'.*@', '', rhostport or '').count(':') > 1:
|
||||
if rhostport.count(']') or rhostport.count('['):
|
||||
result = rhostport.split(']')
|
||||
rhost = result[0].strip('[')
|
||||
if len(result) > 1:
|
||||
result[1] = result[1].strip(':')
|
||||
if result[1] is not '':
|
||||
if result[1] != '':
|
||||
portl = ['-p', str(int(result[1]))]
|
||||
# can't disambiguate IPv6 colons and a port number. pass the hostname
|
||||
# through.
|
||||
else:
|
||||
rhost = rhostport
|
||||
else: # IPv4
|
||||
l = (rhostport or '').split(':', 1)
|
||||
l = (rhostport or '').rsplit(':', 1)
|
||||
rhost = l[0]
|
||||
if len(l) > 1:
|
||||
portl = ['-p', str(int(l[1]))]
|
||||
@ -109,13 +110,13 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
|
||||
argv = [sys.executable, '-c', pyscript]
|
||||
else:
|
||||
if ssh_cmd:
|
||||
sshl = ssh_cmd.split(' ')
|
||||
sshl = shlex.split(ssh_cmd)
|
||||
else:
|
||||
sshl = ['ssh']
|
||||
if python:
|
||||
pycmd = "'%s' -c '%s'" % (python, pyscript)
|
||||
else:
|
||||
pycmd = ("P=python3.5; $P -V 2>/dev/null || P=python; "
|
||||
pycmd = ("P=python3; $P -V 2>/dev/null || P=python; "
|
||||
"exec \"$P\" -c %s") % quote(pyscript)
|
||||
pycmd = ("exec /bin/sh -c %s" % quote(pycmd))
|
||||
argv = (sshl +
|
||||
|
@ -200,7 +200,8 @@ class SockWrapper:
|
||||
_, e = sys.exc_info()[:2]
|
||||
self.seterr('nowrite: %s' % e)
|
||||
|
||||
def too_full(self):
|
||||
@staticmethod
|
||||
def too_full():
|
||||
return False # fullness is determined by the socket's select() state
|
||||
|
||||
def uwrite(self, buf):
|
||||
@ -270,7 +271,7 @@ class Handler:
|
||||
|
||||
def callback(self, sock):
|
||||
log('--no callback defined-- %r\n' % self)
|
||||
(r, w, x) = select.select(self.socks, [], [], 0)
|
||||
(r, _, _) = select.select(self.socks, [], [], 0)
|
||||
for s in r:
|
||||
v = s.recv(4096)
|
||||
if not v:
|
||||
@ -349,7 +350,7 @@ class Mux(Handler):
|
||||
|
||||
def next_channel(self):
|
||||
# channel 0 is special, so we never allocate it
|
||||
for timeout in range(1024):
|
||||
for _ in range(1024):
|
||||
self.chani += 1
|
||||
if self.chani > MAX_CHANNEL:
|
||||
self.chani = 1
|
||||
@ -478,7 +479,7 @@ class Mux(Handler):
|
||||
_add(w, self.wsock)
|
||||
|
||||
def callback(self, sock):
|
||||
(r, w, x) = select.select([self.rsock], [self.wsock], [], 0)
|
||||
(r, w, _) = select.select([self.rsock], [self.wsock], [], 0)
|
||||
if self.rsock in r:
|
||||
self.handle()
|
||||
if self.outbuf and self.wsock in w:
|
||||
@ -503,17 +504,25 @@ class MuxWrapper(SockWrapper):
|
||||
return 'SW%r:Mux#%d' % (self.peername, self.channel)
|
||||
|
||||
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:
|
||||
debug2('%r: done reading\n' % self)
|
||||
self.shut_read = True
|
||||
self.mux.send(self.channel, CMD_TCP_STOP_SENDING, b(''))
|
||||
self.maybe_close()
|
||||
|
||||
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:
|
||||
debug2('%r: done writing\n' % self)
|
||||
self.shut_write = True
|
||||
self.mux.send(self.channel, CMD_TCP_EOF, b(''))
|
||||
self.maybe_close()
|
||||
|
||||
def maybe_close(self):
|
||||
@ -542,9 +551,11 @@ class MuxWrapper(SockWrapper):
|
||||
|
||||
def got_packet(self, cmd, data):
|
||||
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:
|
||||
self.nowrite()
|
||||
# Remote side already knows the status - set flag but don't notify
|
||||
self.setnowrite()
|
||||
elif cmd == CMD_TCP_DATA:
|
||||
self.buf.append(data)
|
||||
else:
|
||||
|
@ -1,22 +1,23 @@
|
||||
from mock import Mock, patch, call
|
||||
import io
|
||||
from socket import AF_INET, AF_INET6
|
||||
|
||||
import sshuttle.firewall
|
||||
|
||||
|
||||
def setup_daemon():
|
||||
stdin = io.StringIO(u"""ROUTES
|
||||
2,24,0,1.2.3.0
|
||||
2,32,1,1.2.3.66
|
||||
10,64,0,2404:6800:4004:80c::
|
||||
10,128,1,2404:6800:4004:80c::101f
|
||||
{inet},24,0,1.2.3.0,8000,9000
|
||||
{inet},32,1,1.2.3.66,8080,8080
|
||||
{inet6},64,0,2404:6800:4004:80c::,0,0
|
||||
{inet6},128,1,2404:6800:4004:80c::101f,80,80
|
||||
NSLIST
|
||||
2,1.2.3.33
|
||||
10,2404:6800:4004:80c::33
|
||||
{inet},1.2.3.33
|
||||
{inet6},2404:6800:4004:80c::33
|
||||
PORTS 1024,1025,1026,1027
|
||||
GO 1
|
||||
GO 1 -
|
||||
HOST 1.2.3.3,existing
|
||||
""")
|
||||
""".format(inet=AF_INET, inet6=AF_INET6))
|
||||
stdout = Mock()
|
||||
return stdin, stdout
|
||||
|
||||
@ -58,6 +59,36 @@ def test_rewrite_etc_hosts(tmpdir):
|
||||
assert orig_hosts.computehash() == new_hosts.computehash()
|
||||
|
||||
|
||||
def test_subnet_weight():
|
||||
subnets = [
|
||||
(AF_INET, 16, 0, '192.168.0.0', 0, 0),
|
||||
(AF_INET, 24, 0, '192.168.69.0', 0, 0),
|
||||
(AF_INET, 32, 0, '192.168.69.70', 0, 0),
|
||||
(AF_INET, 32, 1, '192.168.69.70', 0, 0),
|
||||
(AF_INET, 32, 1, '192.168.69.70', 80, 80),
|
||||
(AF_INET, 0, 1, '0.0.0.0', 0, 0),
|
||||
(AF_INET, 0, 1, '0.0.0.0', 8000, 9000),
|
||||
(AF_INET, 0, 1, '0.0.0.0', 8000, 8500),
|
||||
(AF_INET, 0, 1, '0.0.0.0', 8000, 8000),
|
||||
(AF_INET, 0, 1, '0.0.0.0', 400, 450)
|
||||
]
|
||||
subnets_sorted = [
|
||||
(AF_INET, 32, 1, '192.168.69.70', 80, 80),
|
||||
(AF_INET, 0, 1, '0.0.0.0', 8000, 8000),
|
||||
(AF_INET, 0, 1, '0.0.0.0', 400, 450),
|
||||
(AF_INET, 0, 1, '0.0.0.0', 8000, 8500),
|
||||
(AF_INET, 0, 1, '0.0.0.0', 8000, 9000),
|
||||
(AF_INET, 32, 1, '192.168.69.70', 0, 0),
|
||||
(AF_INET, 32, 0, '192.168.69.70', 0, 0),
|
||||
(AF_INET, 24, 0, '192.168.69.0', 0, 0),
|
||||
(AF_INET, 16, 0, '192.168.0.0', 0, 0),
|
||||
(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.setup_daemon')
|
||||
@patch('sshuttle.firewall.get_method')
|
||||
@ -86,17 +117,20 @@ def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts):
|
||||
call('not_auto'),
|
||||
call().setup_firewall(
|
||||
1024, 1026,
|
||||
[(10, u'2404:6800:4004:80c::33')],
|
||||
10,
|
||||
[(10, 64, False, u'2404:6800:4004:80c::'),
|
||||
(10, 128, True, u'2404:6800:4004:80c::101f')],
|
||||
True),
|
||||
[(AF_INET6, u'2404:6800:4004:80c::33')],
|
||||
AF_INET6,
|
||||
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 0, 0),
|
||||
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
|
||||
True,
|
||||
None),
|
||||
call().setup_firewall(
|
||||
1025, 1027,
|
||||
[(2, u'1.2.3.33')],
|
||||
2,
|
||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
||||
True),
|
||||
call().restore_firewall(1024, 10, True),
|
||||
call().restore_firewall(1025, 2, True),
|
||||
[(AF_INET, u'1.2.3.33')],
|
||||
AF_INET,
|
||||
[(AF_INET, 24, False, u'1.2.3.0', 8000, 9000),
|
||||
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
|
||||
True,
|
||||
None),
|
||||
call().restore_firewall(1024, AF_INET6, True, None),
|
||||
call().restore_firewall(1025, AF_INET, True, None),
|
||||
]
|
||||
|
@ -2,6 +2,8 @@ from mock import patch, call
|
||||
import sys
|
||||
import io
|
||||
import socket
|
||||
from socket import AF_INET, AF_INET6
|
||||
import errno
|
||||
|
||||
import sshuttle.helpers
|
||||
|
||||
@ -132,10 +134,12 @@ nameserver 2404:6800:4004:80c::4
|
||||
|
||||
ns = sshuttle.helpers.resolvconf_nameservers()
|
||||
assert ns == [
|
||||
(2, u'192.168.1.1'), (2, u'192.168.2.1'),
|
||||
(2, u'192.168.3.1'), (2, u'192.168.4.1'),
|
||||
(10, u'2404:6800:4004:80c::1'), (10, u'2404:6800:4004:80c::2'),
|
||||
(10, u'2404:6800:4004:80c::3'), (10, u'2404:6800:4004:80c::4')
|
||||
(AF_INET, u'192.168.1.1'), (AF_INET, u'192.168.2.1'),
|
||||
(AF_INET, u'192.168.3.1'), (AF_INET, u'192.168.4.1'),
|
||||
(AF_INET6, u'2404:6800:4004:80c::1'),
|
||||
(AF_INET6, u'2404:6800:4004:80c::2'),
|
||||
(AF_INET6, u'2404:6800:4004:80c::3'),
|
||||
(AF_INET6, u'2404:6800:4004:80c::4')
|
||||
]
|
||||
|
||||
|
||||
@ -155,34 +159,40 @@ nameserver 2404:6800:4004:80c::4
|
||||
""")
|
||||
ns = sshuttle.helpers.resolvconf_random_nameserver()
|
||||
assert ns in [
|
||||
(2, u'192.168.1.1'), (2, u'192.168.2.1'),
|
||||
(2, u'192.168.3.1'), (2, u'192.168.4.1'),
|
||||
(10, u'2404:6800:4004:80c::1'), (10, u'2404:6800:4004:80c::2'),
|
||||
(10, u'2404:6800:4004:80c::3'), (10, u'2404:6800:4004:80c::4')
|
||||
(AF_INET, u'192.168.1.1'), (AF_INET, u'192.168.2.1'),
|
||||
(AF_INET, u'192.168.3.1'), (AF_INET, u'192.168.4.1'),
|
||||
(AF_INET6, u'2404:6800:4004:80c::1'),
|
||||
(AF_INET6, u'2404:6800:4004:80c::2'),
|
||||
(AF_INET6, u'2404:6800:4004:80c::3'),
|
||||
(AF_INET6, u'2404:6800:4004:80c::4')
|
||||
]
|
||||
|
||||
|
||||
def test_islocal():
|
||||
assert sshuttle.helpers.islocal("127.0.0.1", socket.AF_INET)
|
||||
assert not sshuttle.helpers.islocal("192.0.2.1", socket.AF_INET)
|
||||
assert sshuttle.helpers.islocal("::1", socket.AF_INET6)
|
||||
assert not sshuttle.helpers.islocal("2001:db8::1", socket.AF_INET6)
|
||||
@patch('sshuttle.helpers.socket.socket.bind')
|
||||
def test_islocal(mock_bind):
|
||||
bind_error = socket.error(errno.EADDRNOTAVAIL)
|
||||
mock_bind.side_effect = [None, bind_error, None, bind_error]
|
||||
|
||||
assert sshuttle.helpers.islocal("127.0.0.1", AF_INET)
|
||||
assert not sshuttle.helpers.islocal("192.0.2.1", AF_INET)
|
||||
assert sshuttle.helpers.islocal("::1", AF_INET6)
|
||||
assert not sshuttle.helpers.islocal("2001:db8::1", AF_INET6)
|
||||
|
||||
|
||||
def test_family_ip_tuple():
|
||||
assert sshuttle.helpers.family_ip_tuple("127.0.0.1") \
|
||||
== (socket.AF_INET, "127.0.0.1")
|
||||
== (AF_INET, "127.0.0.1")
|
||||
assert sshuttle.helpers.family_ip_tuple("192.168.2.6") \
|
||||
== (socket.AF_INET, "192.168.2.6")
|
||||
== (AF_INET, "192.168.2.6")
|
||||
assert sshuttle.helpers.family_ip_tuple("::1") \
|
||||
== (socket.AF_INET6, "::1")
|
||||
== (AF_INET6, "::1")
|
||||
assert sshuttle.helpers.family_ip_tuple("2404:6800:4004:80c::1") \
|
||||
== (socket.AF_INET6, "2404:6800:4004:80c::1")
|
||||
== (AF_INET6, "2404:6800:4004:80c::1")
|
||||
|
||||
|
||||
def test_family_to_string():
|
||||
assert sshuttle.helpers.family_to_string(socket.AF_INET) == "AF_INET"
|
||||
assert sshuttle.helpers.family_to_string(socket.AF_INET6) == "AF_INET6"
|
||||
assert sshuttle.helpers.family_to_string(AF_INET) == "AF_INET"
|
||||
assert sshuttle.helpers.family_to_string(AF_INET6) == "AF_INET6"
|
||||
if sys.version_info < (3, 0):
|
||||
expected = "1"
|
||||
assert sshuttle.helpers.family_to_string(socket.AF_UNIX) == "1"
|
||||
|
@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
from mock import Mock, patch, call
|
||||
import socket
|
||||
from socket import AF_INET, AF_INET6
|
||||
import struct
|
||||
|
||||
from sshuttle.helpers import Fatal
|
||||
@ -18,7 +19,7 @@ def test_get_supported_features():
|
||||
def test_get_tcp_dstip():
|
||||
sock = Mock()
|
||||
sock.getsockopt.return_value = struct.pack(
|
||||
'!HHBBBB', socket.ntohs(socket.AF_INET), 1024, 127, 0, 0, 1)
|
||||
'!HHBBBB', socket.ntohs(AF_INET), 1024, 127, 0, 0, 1)
|
||||
method = get_method('nat')
|
||||
assert method.get_tcp_dstip(sock) == ('127.0.0.1', 1024)
|
||||
assert sock.mock_calls == [call.getsockopt(0, 80, 16)]
|
||||
@ -84,11 +85,12 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
method.setup_firewall(
|
||||
1024, 1026,
|
||||
[(10, u'2404:6800:4004:80c::33')],
|
||||
10,
|
||||
[(10, 64, False, u'2404:6800:4004:80c::'),
|
||||
(10, 128, True, u'2404:6800:4004:80c::101f')],
|
||||
True)
|
||||
[(AF_INET6, u'2404:6800:4004:80c::33')],
|
||||
AF_INET6,
|
||||
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 0, 0),
|
||||
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
|
||||
True,
|
||||
None)
|
||||
assert str(excinfo.value) \
|
||||
== 'Address family "AF_INET6" unsupported by nat method_name'
|
||||
assert mock_ipt_chain_exists.mock_calls == []
|
||||
@ -98,10 +100,12 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
method.setup_firewall(
|
||||
1025, 1027,
|
||||
[(2, u'1.2.3.33')],
|
||||
2,
|
||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
||||
True)
|
||||
[(AF_INET, u'1.2.3.33')],
|
||||
AF_INET,
|
||||
[(AF_INET, 24, False, u'1.2.3.0', 8000, 9000),
|
||||
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
|
||||
True,
|
||||
None)
|
||||
assert str(excinfo.value) == 'UDP not supported by nat method_name'
|
||||
assert mock_ipt_chain_exists.mock_calls == []
|
||||
assert mock_ipt_ttl.mock_calls == []
|
||||
@ -109,46 +113,49 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
||||
|
||||
method.setup_firewall(
|
||||
1025, 1027,
|
||||
[(2, u'1.2.3.33')],
|
||||
2,
|
||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
||||
False)
|
||||
[(AF_INET, u'1.2.3.33')],
|
||||
AF_INET,
|
||||
[(AF_INET, 24, False, u'1.2.3.0', 8000, 9000),
|
||||
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
|
||||
False,
|
||||
None)
|
||||
assert mock_ipt_chain_exists.mock_calls == [
|
||||
call(2, 'nat', 'sshuttle-1025')
|
||||
call(AF_INET, 'nat', 'sshuttle-1025')
|
||||
]
|
||||
assert mock_ipt_ttl.mock_calls == [
|
||||
call(2, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
|
||||
'--dest', u'1.2.3.0/24', '-p', 'tcp', '--to-ports', '1025'),
|
||||
call(2, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
|
||||
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
|
||||
'--dest', u'1.2.3.0/24', '-p', 'tcp', '--dport', '8000:9000',
|
||||
'--to-ports', '1025'),
|
||||
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
|
||||
'--dest', u'1.2.3.33/32', '-p', 'udp',
|
||||
'--dport', '53', '--to-ports', '1027')
|
||||
]
|
||||
assert mock_ipt.mock_calls == [
|
||||
call(2, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
|
||||
call(2, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'),
|
||||
call(2, 'nat', '-F', 'sshuttle-1025'),
|
||||
call(2, 'nat', '-X', 'sshuttle-1025'),
|
||||
call(2, 'nat', '-N', 'sshuttle-1025'),
|
||||
call(2, 'nat', '-F', 'sshuttle-1025'),
|
||||
call(2, 'nat', '-I', 'OUTPUT', '1', '-j', 'sshuttle-1025'),
|
||||
call(2, 'nat', '-I', 'PREROUTING', '1', '-j', 'sshuttle-1025'),
|
||||
call(2, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
|
||||
'--dest', u'1.2.3.66/32', '-p', 'tcp')
|
||||
call(AF_INET, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
|
||||
call(AF_INET, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'),
|
||||
call(AF_INET, 'nat', '-F', 'sshuttle-1025'),
|
||||
call(AF_INET, 'nat', '-X', 'sshuttle-1025'),
|
||||
call(AF_INET, 'nat', '-N', 'sshuttle-1025'),
|
||||
call(AF_INET, 'nat', '-F', 'sshuttle-1025'),
|
||||
call(AF_INET, 'nat', '-I', 'OUTPUT', '1', '-j', 'sshuttle-1025'),
|
||||
call(AF_INET, 'nat', '-I', 'PREROUTING', '1', '-j', 'sshuttle-1025'),
|
||||
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
|
||||
'--dest', u'1.2.3.66/32', '-p', 'tcp', '--dport', '8080:8080')
|
||||
]
|
||||
mock_ipt_chain_exists.reset_mock()
|
||||
mock_ipt_ttl.reset_mock()
|
||||
mock_ipt.reset_mock()
|
||||
|
||||
method.restore_firewall(1025, 2, False)
|
||||
method.restore_firewall(1025, AF_INET, False, None)
|
||||
assert mock_ipt_chain_exists.mock_calls == [
|
||||
call(2, 'nat', 'sshuttle-1025')
|
||||
call(AF_INET, 'nat', 'sshuttle-1025')
|
||||
]
|
||||
assert mock_ipt_ttl.mock_calls == []
|
||||
assert mock_ipt.mock_calls == [
|
||||
call(2, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
|
||||
call(2, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'),
|
||||
call(2, 'nat', '-F', 'sshuttle-1025'),
|
||||
call(2, 'nat', '-X', 'sshuttle-1025')
|
||||
call(AF_INET, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
|
||||
call(AF_INET, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'),
|
||||
call(AF_INET, 'nat', '-F', 'sshuttle-1025'),
|
||||
call(AF_INET, 'nat', '-X', 'sshuttle-1025')
|
||||
]
|
||||
mock_ipt_chain_exists.reset_mock()
|
||||
mock_ipt_ttl.reset_mock()
|
||||
|
@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
from mock import Mock, patch, call, ANY
|
||||
import socket
|
||||
from socket import AF_INET, AF_INET6
|
||||
|
||||
from sshuttle.methods import get_method
|
||||
from sshuttle.helpers import Fatal
|
||||
@ -20,7 +21,7 @@ def test_get_tcp_dstip():
|
||||
sock = Mock()
|
||||
sock.getpeername.return_value = ("127.0.0.1", 1024)
|
||||
sock.getsockname.return_value = ("127.0.0.2", 1025)
|
||||
sock.family = socket.AF_INET
|
||||
sock.family = AF_INET
|
||||
|
||||
firewall = Mock()
|
||||
firewall.pfile.readline.return_value = \
|
||||
@ -94,7 +95,7 @@ def test_firewall_command_darwin(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
||||
assert not method.firewall_command("somthing")
|
||||
|
||||
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
|
||||
socket.AF_INET, socket.IPPROTO_TCP,
|
||||
AF_INET, socket.IPPROTO_TCP,
|
||||
"127.0.0.1", 1025, "127.0.0.2", 1024)
|
||||
assert method.firewall_command(command)
|
||||
|
||||
@ -117,7 +118,7 @@ def test_firewall_command_freebsd(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
||||
assert not method.firewall_command("somthing")
|
||||
|
||||
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
|
||||
socket.AF_INET, socket.IPPROTO_TCP,
|
||||
AF_INET, socket.IPPROTO_TCP,
|
||||
"127.0.0.1", 1025, "127.0.0.2", 1024)
|
||||
assert method.firewall_command(command)
|
||||
|
||||
@ -140,7 +141,7 @@ def test_firewall_command_openbsd(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
||||
assert not method.firewall_command("somthing")
|
||||
|
||||
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
|
||||
socket.AF_INET, socket.IPPROTO_TCP,
|
||||
AF_INET, socket.IPPROTO_TCP,
|
||||
"127.0.0.1", 1025, "127.0.0.2", 1024)
|
||||
assert method.firewall_command(command)
|
||||
|
||||
@ -180,11 +181,12 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
|
||||
method.setup_firewall(
|
||||
1024, 1026,
|
||||
[(10, u'2404:6800:4004:80c::33')],
|
||||
10,
|
||||
[(10, 64, False, u'2404:6800:4004:80c::'),
|
||||
(10, 128, True, u'2404:6800:4004:80c::101f')],
|
||||
False)
|
||||
[(AF_INET6, u'2404:6800:4004:80c::33')],
|
||||
AF_INET6,
|
||||
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
|
||||
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||
False,
|
||||
None)
|
||||
assert mock_ioctl.mock_calls == [
|
||||
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||
call(mock_pf_get_dev(), 0xCC20441A, ANY),
|
||||
@ -198,16 +200,15 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
call('-f /dev/stdin', b'pass on lo\n'),
|
||||
call('-s all'),
|
||||
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'rdr pass on lo0 inet6 proto tcp '
|
||||
b'to <forward_subnets> -> ::1 port 1024\n'
|
||||
b'rdr pass on lo0 inet6 proto tcp from ! ::1 to '
|
||||
b'2404:6800:4004:80c::/64 port 8000:9000 -> ::1 port 1024\n'
|
||||
b'rdr pass on lo0 inet6 proto udp '
|
||||
b'to <dns_servers> port 53 -> ::1 port 1026\n'
|
||||
b'pass out route-to lo0 inet6 proto tcp '
|
||||
b'to <forward_subnets> keep state\n'
|
||||
b'pass out quick inet6 proto tcp to '
|
||||
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'to <dns_servers> port 53 keep state\n'),
|
||||
call('-E'),
|
||||
@ -219,10 +220,12 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
method.setup_firewall(
|
||||
1025, 1027,
|
||||
[(2, u'1.2.3.33')],
|
||||
2,
|
||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
||||
True)
|
||||
[(AF_INET, u'1.2.3.33')],
|
||||
AF_INET,
|
||||
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
|
||||
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||
True,
|
||||
None)
|
||||
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
||||
assert mock_pf_get_dev.mock_calls == []
|
||||
assert mock_ioctl.mock_calls == []
|
||||
@ -230,10 +233,12 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
|
||||
method.setup_firewall(
|
||||
1025, 1027,
|
||||
[(2, u'1.2.3.33')],
|
||||
2,
|
||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
||||
False)
|
||||
[(AF_INET, u'1.2.3.33')],
|
||||
AF_INET,
|
||||
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
|
||||
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||
False,
|
||||
None)
|
||||
assert mock_ioctl.mock_calls == [
|
||||
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||
call(mock_pf_get_dev(), 0xCC20441A, ANY),
|
||||
@ -247,14 +252,13 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
call('-f /dev/stdin', b'pass on lo\n'),
|
||||
call('-s all'),
|
||||
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'rdr pass on lo0 inet proto tcp '
|
||||
b'to <forward_subnets> -> 127.0.0.1 port 1025\n'
|
||||
b'rdr pass on lo0 inet proto tcp from ! 127.0.0.1 to 1.2.3.0/24 '
|
||||
b'-> 127.0.0.1 port 1025\n'
|
||||
b'rdr pass on lo0 inet proto udp '
|
||||
b'to <dns_servers> port 53 -> 127.0.0.1 port 1027\n'
|
||||
b'pass out route-to lo0 inet proto tcp '
|
||||
b'to <forward_subnets> keep state\n'
|
||||
b'pass out quick inet proto tcp to 1.2.3.66/32 port 80:80\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'to <dns_servers> port 53 keep state\n'),
|
||||
call('-E'),
|
||||
@ -263,7 +267,7 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
mock_ioctl.reset_mock()
|
||||
mock_pfctl.reset_mock()
|
||||
|
||||
method.restore_firewall(1025, 2, False)
|
||||
method.restore_firewall(1025, AF_INET, False, None)
|
||||
assert mock_ioctl.mock_calls == []
|
||||
assert mock_pfctl.mock_calls == [
|
||||
call('-a sshuttle-1025 -F all'),
|
||||
@ -276,10 +280,12 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
|
||||
@patch('sshuttle.helpers.verbose', new=3)
|
||||
@patch('sshuttle.methods.pf.pf', FreeBsd())
|
||||
@patch('subprocess.call')
|
||||
@patch('sshuttle.methods.pf.pfctl')
|
||||
@patch('sshuttle.methods.pf.ioctl')
|
||||
@patch('sshuttle.methods.pf.pf_get_dev')
|
||||
def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl,
|
||||
mock_subprocess_call):
|
||||
mock_pfctl.side_effect = pfctl
|
||||
|
||||
method = get_method('pf')
|
||||
@ -287,29 +293,30 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
|
||||
method.setup_firewall(
|
||||
1024, 1026,
|
||||
[(10, u'2404:6800:4004:80c::33')],
|
||||
10,
|
||||
[(10, 64, False, u'2404:6800:4004:80c::'),
|
||||
(10, 128, True, u'2404:6800:4004:80c::101f')],
|
||||
False)
|
||||
[(AF_INET6, u'2404:6800:4004:80c::33')],
|
||||
AF_INET6,
|
||||
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
|
||||
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||
False,
|
||||
None)
|
||||
|
||||
assert mock_pfctl.mock_calls == [
|
||||
call('-s all'),
|
||||
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'rdr pass on lo0 inet6 proto tcp '
|
||||
b'to <forward_subnets> -> ::1 port 1024\n'
|
||||
b'rdr pass on lo0 inet6 proto tcp from ! ::1 to '
|
||||
b'2404:6800:4004:80c::/64 port 8000:9000 -> ::1 port 1024\n'
|
||||
b'rdr pass on lo0 inet6 proto udp '
|
||||
b'to <dns_servers> port 53 -> ::1 port 1026\n'
|
||||
b'pass out route-to lo0 inet6 proto tcp '
|
||||
b'to <forward_subnets> keep state\n'
|
||||
b'pass out quick inet6 proto tcp to '
|
||||
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'to <dns_servers> port 53 keep state\n'),
|
||||
call('-e'),
|
||||
]
|
||||
assert call(['kldload', 'pf']) in mock_subprocess_call.mock_calls
|
||||
mock_pf_get_dev.reset_mock()
|
||||
mock_ioctl.reset_mock()
|
||||
mock_pfctl.reset_mock()
|
||||
@ -317,10 +324,12 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
method.setup_firewall(
|
||||
1025, 1027,
|
||||
[(2, u'1.2.3.33')],
|
||||
2,
|
||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
||||
True)
|
||||
[(AF_INET, u'1.2.3.33')],
|
||||
AF_INET,
|
||||
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
|
||||
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||
True,
|
||||
None)
|
||||
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
||||
assert mock_pf_get_dev.mock_calls == []
|
||||
assert mock_ioctl.mock_calls == []
|
||||
@ -328,10 +337,12 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
|
||||
method.setup_firewall(
|
||||
1025, 1027,
|
||||
[(2, u'1.2.3.33')],
|
||||
2,
|
||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
||||
False)
|
||||
[(AF_INET, u'1.2.3.33')],
|
||||
AF_INET,
|
||||
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
|
||||
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||
False,
|
||||
None)
|
||||
assert mock_ioctl.mock_calls == [
|
||||
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||
call(mock_pf_get_dev(), 0xCBE0441A, ANY),
|
||||
@ -343,14 +354,13 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
assert mock_pfctl.mock_calls == [
|
||||
call('-s all'),
|
||||
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'rdr pass on lo0 inet proto tcp '
|
||||
b'to <forward_subnets> -> 127.0.0.1 port 1025\n'
|
||||
b'rdr pass on lo0 inet proto tcp from ! 127.0.0.1 '
|
||||
b'to 1.2.3.0/24 -> 127.0.0.1 port 1025\n'
|
||||
b'rdr pass on lo0 inet proto udp '
|
||||
b'to <dns_servers> port 53 -> 127.0.0.1 port 1027\n'
|
||||
b'pass out route-to lo0 inet proto tcp '
|
||||
b'to <forward_subnets> keep state\n'
|
||||
b'pass out quick inet proto tcp to 1.2.3.66/32 port 80:80\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'to <dns_servers> port 53 keep state\n'),
|
||||
call('-e'),
|
||||
@ -359,10 +369,12 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
mock_ioctl.reset_mock()
|
||||
mock_pfctl.reset_mock()
|
||||
|
||||
method.restore_firewall(1025, 2, False)
|
||||
method.restore_firewall(1025, AF_INET, False, None)
|
||||
method.restore_firewall(1024, AF_INET6, False, None)
|
||||
assert mock_ioctl.mock_calls == []
|
||||
assert mock_pfctl.mock_calls == [
|
||||
call('-a sshuttle-1025 -F all'),
|
||||
call('-a sshuttle6-1024 -F all'),
|
||||
call("-d"),
|
||||
]
|
||||
mock_pf_get_dev.reset_mock()
|
||||
@ -383,11 +395,12 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
|
||||
method.setup_firewall(
|
||||
1024, 1026,
|
||||
[(10, u'2404:6800:4004:80c::33')],
|
||||
10,
|
||||
[(10, 64, False, u'2404:6800:4004:80c::'),
|
||||
(10, 128, True, u'2404:6800:4004:80c::101f')],
|
||||
False)
|
||||
[(AF_INET6, u'2404:6800:4004:80c::33')],
|
||||
AF_INET6,
|
||||
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
|
||||
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||
False,
|
||||
None)
|
||||
|
||||
assert mock_ioctl.mock_calls == [
|
||||
call(mock_pf_get_dev(), 0xcd48441a, ANY),
|
||||
@ -398,16 +411,15 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
call('-f /dev/stdin', b'match on lo\n'),
|
||||
call('-s all'),
|
||||
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'pass in on lo0 inet6 proto tcp to '
|
||||
b'<forward_subnets> divert-to ::1 port 1024\n'
|
||||
b'pass in on lo0 inet6 proto tcp to 2404:6800:4004:80c::/64 '
|
||||
b'port 8000:9000 divert-to ::1 port 1024\n'
|
||||
b'pass in on lo0 inet6 proto udp '
|
||||
b'to <dns_servers> port 53 rdr-to ::1 port 1026\n'
|
||||
b'pass out inet6 proto tcp to '
|
||||
b'<forward_subnets> route-to lo0 keep state\n'
|
||||
b'pass out quick inet6 proto tcp to '
|
||||
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'<dns_servers> port 53 route-to lo0 keep state\n'),
|
||||
call('-e'),
|
||||
@ -415,14 +427,16 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
mock_pf_get_dev.reset_mock()
|
||||
mock_ioctl.reset_mock()
|
||||
mock_pfctl.reset_mock()
|
||||
|
||||
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
method.setup_firewall(
|
||||
1025, 1027,
|
||||
[(2, u'1.2.3.33')],
|
||||
2,
|
||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
||||
True)
|
||||
[(AF_INET, u'1.2.3.33')],
|
||||
AF_INET,
|
||||
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
|
||||
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||
True,
|
||||
None)
|
||||
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
||||
assert mock_pf_get_dev.mock_calls == []
|
||||
assert mock_ioctl.mock_calls == []
|
||||
@ -430,10 +444,12 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
|
||||
method.setup_firewall(
|
||||
1025, 1027,
|
||||
[(2, u'1.2.3.33')],
|
||||
2,
|
||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
||||
False)
|
||||
[(AF_INET, u'1.2.3.33')],
|
||||
AF_INET,
|
||||
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
|
||||
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||
False,
|
||||
None)
|
||||
assert mock_ioctl.mock_calls == [
|
||||
call(mock_pf_get_dev(), 0xcd48441a, ANY),
|
||||
call(mock_pf_get_dev(), 0xcd48441a, ANY),
|
||||
@ -443,13 +459,13 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
call('-f /dev/stdin', b'match on lo\n'),
|
||||
call('-s all'),
|
||||
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'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'<dns_servers> port 53 rdr-to 127.0.0.1 port 1027\n'
|
||||
b'pass out inet proto tcp to '
|
||||
b'<forward_subnets> route-to lo0 keep state\n'
|
||||
b'pass out quick inet proto tcp to 1.2.3.66/32 port 80:80\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'<dns_servers> port 53 route-to lo0 keep state\n'),
|
||||
call('-e'),
|
||||
@ -458,11 +474,13 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
mock_ioctl.reset_mock()
|
||||
mock_pfctl.reset_mock()
|
||||
|
||||
method.restore_firewall(1025, 2, False)
|
||||
method.restore_firewall(1025, AF_INET, False, None)
|
||||
method.restore_firewall(1024, AF_INET6, False, None)
|
||||
assert mock_ioctl.mock_calls == []
|
||||
assert mock_pfctl.mock_calls == [
|
||||
call('-a sshuttle-1025 -F all'),
|
||||
call("-d"),
|
||||
call('-a sshuttle6-1024 -F all'),
|
||||
call('-d'),
|
||||
]
|
||||
mock_pf_get_dev.reset_mock()
|
||||
mock_pfctl.reset_mock()
|
||||
|
@ -1,3 +1,6 @@
|
||||
import socket
|
||||
from socket import AF_INET, AF_INET6
|
||||
|
||||
from mock import Mock, patch, call
|
||||
|
||||
from sshuttle.methods import get_method
|
||||
@ -49,7 +52,7 @@ def test_send_udp(mock_socket):
|
||||
assert sock.mock_calls == []
|
||||
assert mock_socket.mock_calls == [
|
||||
call(sock.family, 2),
|
||||
call().setsockopt(1, 2, 1),
|
||||
call().setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1),
|
||||
call().setsockopt(0, 19, 1),
|
||||
call().bind('127.0.0.2'),
|
||||
call().sendto("2222222", '127.0.0.1'),
|
||||
@ -100,93 +103,97 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
||||
|
||||
method.setup_firewall(
|
||||
1024, 1026,
|
||||
[(10, u'2404:6800:4004:80c::33')],
|
||||
10,
|
||||
[(10, 64, False, u'2404:6800:4004:80c::'),
|
||||
(10, 128, True, u'2404:6800:4004:80c::101f')],
|
||||
True)
|
||||
[(AF_INET6, u'2404:6800:4004:80c::33')],
|
||||
AF_INET6,
|
||||
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
|
||||
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||
True,
|
||||
None)
|
||||
assert mock_ipt_chain_exists.mock_calls == [
|
||||
call(10, 'mangle', 'sshuttle-m-1024'),
|
||||
call(10, 'mangle', 'sshuttle-t-1024'),
|
||||
call(10, 'mangle', 'sshuttle-d-1024')
|
||||
call(AF_INET6, 'mangle', 'sshuttle-m-1024'),
|
||||
call(AF_INET6, 'mangle', 'sshuttle-t-1024'),
|
||||
call(AF_INET6, 'mangle', 'sshuttle-d-1024')
|
||||
]
|
||||
assert mock_ipt_ttl.mock_calls == []
|
||||
assert mock_ipt.mock_calls == [
|
||||
call(10, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1024'),
|
||||
call(10, 'mangle', '-F', 'sshuttle-m-1024'),
|
||||
call(10, 'mangle', '-X', 'sshuttle-m-1024'),
|
||||
call(10, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1024'),
|
||||
call(10, 'mangle', '-F', 'sshuttle-t-1024'),
|
||||
call(10, 'mangle', '-X', 'sshuttle-t-1024'),
|
||||
call(10, 'mangle', '-F', 'sshuttle-d-1024'),
|
||||
call(10, 'mangle', '-X', 'sshuttle-d-1024'),
|
||||
call(10, 'mangle', '-N', 'sshuttle-m-1024'),
|
||||
call(10, 'mangle', '-F', 'sshuttle-m-1024'),
|
||||
call(10, 'mangle', '-N', 'sshuttle-d-1024'),
|
||||
call(10, 'mangle', '-F', 'sshuttle-d-1024'),
|
||||
call(10, 'mangle', '-N', 'sshuttle-t-1024'),
|
||||
call(10, 'mangle', '-F', 'sshuttle-t-1024'),
|
||||
call(10, 'mangle', '-I', 'OUTPUT', '1', '-j', 'sshuttle-m-1024'),
|
||||
call(10, 'mangle', '-I', 'PREROUTING', '1', '-j', 'sshuttle-t-1024'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-d-1024', '-j', 'MARK',
|
||||
call(AF_INET6, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1024'),
|
||||
call(AF_INET6, 'mangle', '-F', 'sshuttle-m-1024'),
|
||||
call(AF_INET6, 'mangle', '-X', 'sshuttle-m-1024'),
|
||||
call(AF_INET6, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1024'),
|
||||
call(AF_INET6, 'mangle', '-F', 'sshuttle-t-1024'),
|
||||
call(AF_INET6, 'mangle', '-X', 'sshuttle-t-1024'),
|
||||
call(AF_INET6, 'mangle', '-F', 'sshuttle-d-1024'),
|
||||
call(AF_INET6, 'mangle', '-X', 'sshuttle-d-1024'),
|
||||
call(AF_INET6, 'mangle', '-N', 'sshuttle-m-1024'),
|
||||
call(AF_INET6, 'mangle', '-F', 'sshuttle-m-1024'),
|
||||
call(AF_INET6, 'mangle', '-N', 'sshuttle-d-1024'),
|
||||
call(AF_INET6, 'mangle', '-F', 'sshuttle-d-1024'),
|
||||
call(AF_INET6, 'mangle', '-N', 'sshuttle-t-1024'),
|
||||
call(AF_INET6, 'mangle', '-F', 'sshuttle-t-1024'),
|
||||
call(AF_INET6, 'mangle', '-I', 'OUTPUT', '1', '-j', 'sshuttle-m-1024'),
|
||||
call(AF_INET6, 'mangle', '-I', 'PREROUTING', '1', '-j',
|
||||
'sshuttle-t-1024'),
|
||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-d-1024', '-j', 'MARK',
|
||||
'--set-mark', '1'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-d-1024', '-j', 'ACCEPT'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket',
|
||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-d-1024', '-j', 'ACCEPT'),
|
||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket',
|
||||
'-j', 'sshuttle-d-1024', '-m', 'tcp', '-p', 'tcp'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket',
|
||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket',
|
||||
'-j', 'sshuttle-d-1024', '-m', 'udp', '-p', 'udp'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
|
||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
|
||||
'--set-mark', '1', '--dest', u'2404:6800:4004:80c::33/32',
|
||||
'-m', 'udp', '-p', 'udp', '--dport', '53'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
|
||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
|
||||
'--tproxy-mark', '0x1/0x1',
|
||||
'--dest', u'2404:6800:4004:80c::33/32',
|
||||
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1026'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
|
||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
|
||||
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||
'-m', 'tcp', '-p', 'tcp'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'RETURN',
|
||||
'-m', 'tcp', '-p', 'tcp', '--dport', '8080:8080'),
|
||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'RETURN',
|
||||
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||
'-m', 'tcp', '-p', 'tcp'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
|
||||
'-m', 'tcp', '-p', 'tcp', '--dport', '8080:8080'),
|
||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
|
||||
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||
'-m', 'udp', '-p', 'udp'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'RETURN',
|
||||
'-m', 'udp', '-p', 'udp', '--dport', '8080:8080'),
|
||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'RETURN',
|
||||
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||
'-m', 'udp', '-p', 'udp'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
|
||||
'-m', 'udp', '-p', 'udp', '--dport', '8080:8080'),
|
||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
|
||||
'--set-mark', '1', '--dest', u'2404:6800:4004:80c::/64',
|
||||
'-m', 'tcp', '-p', 'tcp'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
|
||||
'-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000'),
|
||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
|
||||
'--tproxy-mark', '0x1/0x1', '--dest', u'2404:6800:4004:80c::/64',
|
||||
'-m', 'tcp', '-p', 'tcp', '--on-port', '1024'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
|
||||
'-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000',
|
||||
'--on-port', '1024'),
|
||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
|
||||
'--set-mark', '1', '--dest', u'2404:6800:4004:80c::/64',
|
||||
'-m', 'udp', '-p', 'udp'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
|
||||
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
|
||||
'--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_ttl.reset_mock()
|
||||
mock_ipt.reset_mock()
|
||||
|
||||
method.restore_firewall(1025, 10, True)
|
||||
method.restore_firewall(1025, AF_INET6, True, None)
|
||||
assert mock_ipt_chain_exists.mock_calls == [
|
||||
call(10, 'mangle', 'sshuttle-m-1025'),
|
||||
call(10, 'mangle', 'sshuttle-t-1025'),
|
||||
call(10, 'mangle', 'sshuttle-d-1025')
|
||||
call(AF_INET6, 'mangle', 'sshuttle-m-1025'),
|
||||
call(AF_INET6, 'mangle', 'sshuttle-t-1025'),
|
||||
call(AF_INET6, 'mangle', 'sshuttle-d-1025')
|
||||
]
|
||||
assert mock_ipt_ttl.mock_calls == []
|
||||
assert mock_ipt.mock_calls == [
|
||||
call(10, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
|
||||
call(10, 'mangle', '-F', 'sshuttle-m-1025'),
|
||||
call(10, 'mangle', '-X', 'sshuttle-m-1025'),
|
||||
call(10, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
|
||||
call(10, 'mangle', '-F', 'sshuttle-t-1025'),
|
||||
call(10, 'mangle', '-X', 'sshuttle-t-1025'),
|
||||
call(10, 'mangle', '-F', 'sshuttle-d-1025'),
|
||||
call(10, 'mangle', '-X', 'sshuttle-d-1025')
|
||||
call(AF_INET6, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
|
||||
call(AF_INET6, 'mangle', '-F', 'sshuttle-m-1025'),
|
||||
call(AF_INET6, 'mangle', '-X', 'sshuttle-m-1025'),
|
||||
call(AF_INET6, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
|
||||
call(AF_INET6, 'mangle', '-F', 'sshuttle-t-1025'),
|
||||
call(AF_INET6, 'mangle', '-X', 'sshuttle-t-1025'),
|
||||
call(AF_INET6, 'mangle', '-F', 'sshuttle-d-1025'),
|
||||
call(AF_INET6, 'mangle', '-X', 'sshuttle-d-1025')
|
||||
]
|
||||
mock_ipt_chain_exists.reset_mock()
|
||||
mock_ipt_ttl.reset_mock()
|
||||
@ -196,64 +203,71 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
||||
|
||||
method.setup_firewall(
|
||||
1025, 1027,
|
||||
[(2, u'1.2.3.33')],
|
||||
2,
|
||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
||||
True)
|
||||
[(AF_INET, u'1.2.3.33')],
|
||||
AF_INET,
|
||||
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
|
||||
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||
True,
|
||||
None)
|
||||
assert mock_ipt_chain_exists.mock_calls == [
|
||||
call(2, 'mangle', 'sshuttle-m-1025'),
|
||||
call(2, 'mangle', 'sshuttle-t-1025'),
|
||||
call(2, 'mangle', 'sshuttle-d-1025')
|
||||
call(AF_INET, 'mangle', 'sshuttle-m-1025'),
|
||||
call(AF_INET, 'mangle', 'sshuttle-t-1025'),
|
||||
call(AF_INET, 'mangle', 'sshuttle-d-1025')
|
||||
]
|
||||
assert mock_ipt_ttl.mock_calls == []
|
||||
assert mock_ipt.mock_calls == [
|
||||
call(2, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
|
||||
call(2, 'mangle', '-F', 'sshuttle-m-1025'),
|
||||
call(2, 'mangle', '-X', 'sshuttle-m-1025'),
|
||||
call(2, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
|
||||
call(2, 'mangle', '-F', 'sshuttle-t-1025'),
|
||||
call(2, 'mangle', '-X', 'sshuttle-t-1025'),
|
||||
call(2, 'mangle', '-F', 'sshuttle-d-1025'),
|
||||
call(2, 'mangle', '-X', 'sshuttle-d-1025'),
|
||||
call(2, 'mangle', '-N', 'sshuttle-m-1025'),
|
||||
call(2, 'mangle', '-F', 'sshuttle-m-1025'),
|
||||
call(2, 'mangle', '-N', 'sshuttle-d-1025'),
|
||||
call(2, 'mangle', '-F', 'sshuttle-d-1025'),
|
||||
call(2, 'mangle', '-N', 'sshuttle-t-1025'),
|
||||
call(2, 'mangle', '-F', 'sshuttle-t-1025'),
|
||||
call(2, 'mangle', '-I', 'OUTPUT', '1', '-j', 'sshuttle-m-1025'),
|
||||
call(2, 'mangle', '-I', 'PREROUTING', '1', '-j', 'sshuttle-t-1025'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-d-1025',
|
||||
call(AF_INET, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
|
||||
call(AF_INET, 'mangle', '-F', 'sshuttle-m-1025'),
|
||||
call(AF_INET, 'mangle', '-X', 'sshuttle-m-1025'),
|
||||
call(AF_INET, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
|
||||
call(AF_INET, 'mangle', '-F', 'sshuttle-t-1025'),
|
||||
call(AF_INET, 'mangle', '-X', 'sshuttle-t-1025'),
|
||||
call(AF_INET, 'mangle', '-F', 'sshuttle-d-1025'),
|
||||
call(AF_INET, 'mangle', '-X', 'sshuttle-d-1025'),
|
||||
call(AF_INET, 'mangle', '-N', 'sshuttle-m-1025'),
|
||||
call(AF_INET, 'mangle', '-F', 'sshuttle-m-1025'),
|
||||
call(AF_INET, 'mangle', '-N', 'sshuttle-d-1025'),
|
||||
call(AF_INET, 'mangle', '-F', 'sshuttle-d-1025'),
|
||||
call(AF_INET, 'mangle', '-N', 'sshuttle-t-1025'),
|
||||
call(AF_INET, 'mangle', '-F', 'sshuttle-t-1025'),
|
||||
call(AF_INET, 'mangle', '-I', 'OUTPUT', '1', '-j', 'sshuttle-m-1025'),
|
||||
call(AF_INET, 'mangle', '-I', 'PREROUTING', '1', '-j',
|
||||
'sshuttle-t-1025'),
|
||||
call(AF_INET, 'mangle', '-A', 'sshuttle-d-1025',
|
||||
'-j', 'MARK', '--set-mark', '1'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-d-1025', '-j', 'ACCEPT'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket',
|
||||
call(AF_INET, 'mangle', '-A', 'sshuttle-d-1025', '-j', 'ACCEPT'),
|
||||
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket',
|
||||
'-j', 'sshuttle-d-1025', '-m', 'tcp', '-p', 'tcp'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket',
|
||||
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket',
|
||||
'-j', 'sshuttle-d-1025', '-m', 'udp', '-p', 'udp'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
|
||||
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
|
||||
'--set-mark', '1', '--dest', u'1.2.3.33/32',
|
||||
'-m', 'udp', '-p', 'udp', '--dport', '53'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
|
||||
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
|
||||
'--tproxy-mark', '0x1/0x1', '--dest', u'1.2.3.33/32',
|
||||
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1027'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
||||
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'RETURN',
|
||||
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
||||
'--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'RETURN',
|
||||
'--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
|
||||
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
||||
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp',
|
||||
'--dport', '80:80'),
|
||||
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'RETURN',
|
||||
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp',
|
||||
'--dport', '80:80'),
|
||||
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
||||
'--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp',
|
||||
'--dport', '80:80'),
|
||||
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'RETURN',
|
||||
'--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp',
|
||||
'--dport', '80:80'),
|
||||
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
|
||||
'--set-mark', '1', '--dest', u'1.2.3.0/24',
|
||||
'-m', 'tcp', '-p', 'tcp'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
|
||||
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
|
||||
'--tproxy-mark', '0x1/0x1', '--dest', u'1.2.3.0/24',
|
||||
'-m', 'tcp', '-p', 'tcp', '--on-port', '1025'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
|
||||
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
|
||||
'--set-mark', '1', '--dest', u'1.2.3.0/24',
|
||||
'-m', 'udp', '-p', 'udp'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
|
||||
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
|
||||
'--tproxy-mark', '0x1/0x1', '--dest', u'1.2.3.0/24',
|
||||
'-m', 'udp', '-p', 'udp', '--on-port', '1025')
|
||||
]
|
||||
@ -261,22 +275,22 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
||||
mock_ipt_ttl.reset_mock()
|
||||
mock_ipt.reset_mock()
|
||||
|
||||
method.restore_firewall(1025, 2, True)
|
||||
method.restore_firewall(1025, AF_INET, True, None)
|
||||
assert mock_ipt_chain_exists.mock_calls == [
|
||||
call(2, 'mangle', 'sshuttle-m-1025'),
|
||||
call(2, 'mangle', 'sshuttle-t-1025'),
|
||||
call(2, 'mangle', 'sshuttle-d-1025')
|
||||
call(AF_INET, 'mangle', 'sshuttle-m-1025'),
|
||||
call(AF_INET, 'mangle', 'sshuttle-t-1025'),
|
||||
call(AF_INET, 'mangle', 'sshuttle-d-1025')
|
||||
]
|
||||
assert mock_ipt_ttl.mock_calls == []
|
||||
assert mock_ipt.mock_calls == [
|
||||
call(2, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
|
||||
call(2, 'mangle', '-F', 'sshuttle-m-1025'),
|
||||
call(2, 'mangle', '-X', 'sshuttle-m-1025'),
|
||||
call(2, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
|
||||
call(2, 'mangle', '-F', 'sshuttle-t-1025'),
|
||||
call(2, 'mangle', '-X', 'sshuttle-t-1025'),
|
||||
call(2, 'mangle', '-F', 'sshuttle-d-1025'),
|
||||
call(2, 'mangle', '-X', 'sshuttle-d-1025')
|
||||
call(AF_INET, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
|
||||
call(AF_INET, 'mangle', '-F', 'sshuttle-m-1025'),
|
||||
call(AF_INET, 'mangle', '-X', 'sshuttle-m-1025'),
|
||||
call(AF_INET, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
|
||||
call(AF_INET, 'mangle', '-F', 'sshuttle-t-1025'),
|
||||
call(AF_INET, 'mangle', '-X', 'sshuttle-t-1025'),
|
||||
call(AF_INET, 'mangle', '-F', 'sshuttle-d-1025'),
|
||||
call(AF_INET, 'mangle', '-X', 'sshuttle-d-1025')
|
||||
]
|
||||
mock_ipt_chain_exists.reset_mock()
|
||||
mock_ipt_ttl.reset_mock()
|
||||
|
98
sshuttle/tests/client/test_options.py
Normal file
98
sshuttle/tests/client/test_options.py
Normal file
@ -0,0 +1,98 @@
|
||||
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)
|
||||
|
||||
|
||||
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)
|
64
sshuttle/tests/client/test_sdnotify.py
Normal file
64
sshuttle/tests/client/test_sdnotify.py
Normal file
@ -0,0 +1,64 @@
|
||||
from mock import Mock, patch, call
|
||||
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),
|
||||
]
|
@ -1,8 +1,7 @@
|
||||
import os
|
||||
import io
|
||||
import socket
|
||||
import sshuttle.server
|
||||
from mock import patch, Mock, call
|
||||
from mock import patch, Mock
|
||||
|
||||
|
||||
def test__ipmatch():
|
||||
@ -21,36 +20,9 @@ def test__maskbits():
|
||||
sshuttle.server._maskbits(netmask)
|
||||
|
||||
|
||||
@patch('sshuttle.server.which', side_effect=lambda x: x == 'netstat')
|
||||
@patch('sshuttle.server.ssubprocess.Popen')
|
||||
def test__listroutes(mock_popen):
|
||||
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):
|
||||
def test_listroutes_netstat(mock_popen, mock_which):
|
||||
mock_pobj = Mock()
|
||||
mock_pobj.stdout = io.BytesIO(b"""
|
||||
Kernel IP routing table
|
||||
@ -66,3 +38,21 @@ Destination Gateway Genmask Flags MSS Window irtt Iface
|
||||
assert list(routes) == [
|
||||
(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)
|
||||
]
|
||||
|
9
tox.ini
9
tox.ini
@ -1,10 +1,10 @@
|
||||
[tox]
|
||||
downloadcache = {toxworkdir}/cache/
|
||||
envlist =
|
||||
py26,
|
||||
py27,
|
||||
py34,
|
||||
py35,
|
||||
py36,
|
||||
|
||||
[testenv]
|
||||
basepython =
|
||||
@ -12,9 +12,10 @@ basepython =
|
||||
py27: python2.7
|
||||
py34: python3.4
|
||||
py35: python3.5
|
||||
py36: python3.6
|
||||
commands =
|
||||
flake8 sshuttle --count --select=E901,E999,F821,F822,F823 --show-source --statistics
|
||||
flake8 sshuttle --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
py.test
|
||||
deps =
|
||||
pytest
|
||||
mock
|
||||
setuptools>=17.1
|
||||
-rrequirements-tests.txt
|
||||
|
Reference in New Issue
Block a user