mirror of
https://github.com/sshuttle/sshuttle.git
synced 2025-07-03 16:21:17 +02:00
Compare commits
81 Commits
Author | SHA1 | Date | |
---|---|---|---|
752a953101 | |||
61f4cd9de5 | |||
8e35f049e2 | |||
0e99adc5d1 | |||
04849df7e3 | |||
531a17c151 | |||
d43db80dec | |||
0b1a260436 | |||
efc854c33e | |||
ca41026c89 | |||
b473b91633 | |||
7a54d12f80 | |||
d4bbf3b68d | |||
41f5b3e9c1 | |||
c780597de3 | |||
d085a419b2 | |||
842768f9cf | |||
97ed2030f3 | |||
6dc368bde8 | |||
f528bb9846 | |||
561b648e4b | |||
0dba8a8beb | |||
7b6f082454 | |||
1ec17e1b1b | |||
cecccc2efd | |||
db69ba6d8d | |||
2bb92cd6d4 | |||
ae5bd28dcf | |||
55bd78fd43 | |||
1f5ed9c66e | |||
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 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -13,3 +13,5 @@
|
|||||||
/.do_built
|
/.do_built
|
||||||
/.do_built.dir
|
/.do_built.dir
|
||||||
/.redo
|
/.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
|
language: python
|
||||||
python:
|
python:
|
||||||
- 2.6
|
|
||||||
- 2.7
|
- 2.7
|
||||||
- 3.4
|
- 3.4
|
||||||
- 3.5
|
- 3.5
|
||||||
|
- 3.6
|
||||||
- pypy
|
- pypy
|
||||||
|
|
||||||
install:
|
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:
|
script:
|
||||||
- PYTHONPATH=. py.test
|
- PYTHONPATH=. py.test
|
||||||
|
80
CHANGES.rst
80
CHANGES.rst
@ -9,6 +9,86 @@ adheres to `Semantic Versioning`_.
|
|||||||
.. _`Semantic Versioning`: http://semver.org/
|
.. _`Semantic Versioning`: http://semver.org/
|
||||||
|
|
||||||
|
|
||||||
|
0.78.5 - 2019-01-28
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Added
|
||||||
|
-----
|
||||||
|
* doas support as replacmeent for sudo on OpenBSD.
|
||||||
|
* Added ChromeOS section to documentation (#262)
|
||||||
|
* Add --no-sudo-pythonpath option
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
~~~~~
|
||||||
|
* Fix forwarding to a single port.
|
||||||
|
* Various updates to documentation.
|
||||||
|
* Don't crash if we can't look up peername
|
||||||
|
* Fix missing string formatting argument
|
||||||
|
* Moved sshuttle/tests into tests.
|
||||||
|
* Updated bandit config.
|
||||||
|
* Replace path /dev/null by os.devnull.
|
||||||
|
* Added coverage report to tests.
|
||||||
|
* Fixes support for OpenBSD (6.1+) (#282).
|
||||||
|
* Close stdin, stdout, and stderr when using syslog or forking to daemon (#283).
|
||||||
|
* Changes pf exclusion rules precedence.
|
||||||
|
* Fix deadlock with iptables with large ruleset.
|
||||||
|
* docs: document --ns-hosts --to-ns and update --dns.
|
||||||
|
* Use subprocess.check_output instead of run.
|
||||||
|
* Fix potential deadlock condition in nft_get_handle.
|
||||||
|
* auto-nets: retrieve routes only if using auto-nets.
|
||||||
|
|
||||||
|
|
||||||
|
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
|
0.78.3 - 2017-07-09
|
||||||
-------------------
|
-------------------
|
||||||
The "I should have done a git pull" first release.
|
The "I should have done a git pull" first release.
|
||||||
|
11
README.rst
11
README.rst
@ -40,6 +40,7 @@ Obtaining sshuttle
|
|||||||
- Clone::
|
- Clone::
|
||||||
|
|
||||||
git clone https://github.com/sshuttle/sshuttle.git
|
git clone https://github.com/sshuttle/sshuttle.git
|
||||||
|
cd sshuttle
|
||||||
sudo ./setup.py install
|
sudo ./setup.py install
|
||||||
|
|
||||||
It is also possible to install into a virtualenv as a non-root user.
|
It is also possible to install into a virtualenv as a non-root user.
|
||||||
@ -55,12 +56,18 @@ It is also possible to install into a virtualenv as a non-root user.
|
|||||||
virtualenv -p python3 /tmp/sshuttle
|
virtualenv -p python3 /tmp/sshuttle
|
||||||
. /tmp/sshuttle/bin/activate
|
. /tmp/sshuttle/bin/activate
|
||||||
git clone https://github.com/sshuttle/sshuttle.git
|
git clone https://github.com/sshuttle/sshuttle.git
|
||||||
|
cd sshuttle
|
||||||
./setup.py install
|
./setup.py install
|
||||||
|
|
||||||
|
- Homebrew::
|
||||||
|
|
||||||
|
brew install sshuttle
|
||||||
|
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
The documentation for the stable version is available at:
|
The documentation for the stable version is available at:
|
||||||
http://sshuttle.readthedocs.org/
|
https://sshuttle.readthedocs.org/
|
||||||
|
|
||||||
The documentation for the latest development version is available at:
|
The documentation for the latest development version is available at:
|
||||||
http://sshuttle.readthedocs.org/en/latest/
|
https://sshuttle.readthedocs.org/en/latest/
|
||||||
|
9
bandit.yml
Normal file
9
bandit.yml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
exclude_dirs:
|
||||||
|
- tests
|
||||||
|
skips:
|
||||||
|
- B101
|
||||||
|
- B104
|
||||||
|
- B404
|
||||||
|
- B603
|
||||||
|
- B606
|
||||||
|
- B607
|
12
docs/chromeos.rst
Normal file
12
docs/chromeos.rst
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Google ChromeOS
|
||||||
|
===============
|
||||||
|
|
||||||
|
Currently there is no built in support for running sshuttle directly on
|
||||||
|
Google ChromeOS/Chromebooks.
|
||||||
|
|
||||||
|
What we can really do is to create a Linux VM with Crostini. In the default
|
||||||
|
stretch/Debian 9 VM, you can then install sshuttle as on any Linux box and
|
||||||
|
it just works, as do xterms and ssvncviewer etc.
|
||||||
|
|
||||||
|
https://www.reddit.com/r/Crostini/wiki/getstarted/crostini-setup-guide
|
||||||
|
|
@ -8,4 +8,5 @@ Installation
|
|||||||
- Clone::
|
- Clone::
|
||||||
|
|
||||||
git clone https://github.com/sshuttle/sshuttle.git
|
git clone https://github.com/sshuttle/sshuttle.git
|
||||||
|
cd sshuttle
|
||||||
./setup.py install
|
./setup.py install
|
||||||
|
@ -94,7 +94,30 @@ Options
|
|||||||
.. option:: --dns
|
.. option:: --dns
|
||||||
|
|
||||||
Capture local DNS requests and forward to the remote DNS
|
Capture local DNS requests and forward to the remote DNS
|
||||||
server.
|
server. All queries to any of the local system's DNS
|
||||||
|
servers (/etc/resolv.conf) will be intercepted and
|
||||||
|
resolved on the remote side of the tunnel instead, there
|
||||||
|
using the DNS specified via the :option:`--to-ns=` option,
|
||||||
|
if specified.
|
||||||
|
|
||||||
|
.. option:: --ns-hosts=server1[,server2[,server3[...]]]
|
||||||
|
|
||||||
|
Capture local DNS requests to the specified server(s)
|
||||||
|
and forward to the remote DNS server. Contrary to the
|
||||||
|
:option:`--dns` option, this flag allows to specify the
|
||||||
|
DNS server(s) the queries to which to intercept,
|
||||||
|
instead of intercepting all DNS traffic on the local
|
||||||
|
machine. This can be useful when only certain DNS
|
||||||
|
requests should be resolved on the remote side of the
|
||||||
|
tunnel, e.g. in combination with dnsmasq.
|
||||||
|
|
||||||
|
.. option:: --to-ns=server
|
||||||
|
|
||||||
|
The DNS to forward requests to when remote DNS
|
||||||
|
resolution is enabled. If not given, sshuttle will
|
||||||
|
simply resolve using the system configured resolver on
|
||||||
|
the remote side (via /etc/resolv.conf on the remote
|
||||||
|
side).
|
||||||
|
|
||||||
.. option:: --python
|
.. option:: --python
|
||||||
|
|
||||||
@ -204,6 +227,33 @@ Options
|
|||||||
makes it a lot easier to debug and test the :option:`--auto-hosts`
|
makes it a lot easier to debug and test the :option:`--auto-hosts`
|
||||||
feature.
|
feature.
|
||||||
|
|
||||||
|
.. option:: --version
|
||||||
|
|
||||||
|
Print program version.
|
||||||
|
|
||||||
|
|
||||||
|
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/sshuttle.conf`
|
||||||
|
just pass the path to the file preceded by the `@` character, e.g.
|
||||||
|
:option:`@/etc/sshuttle.conf`.
|
||||||
|
|
||||||
|
When running :program:`sshuttle` with options defined in a configuration 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
|
Examples
|
||||||
--------
|
--------
|
||||||
@ -253,6 +303,24 @@ and subnet guessing::
|
|||||||
c : Keyboard interrupt: exiting.
|
c : Keyboard interrupt: exiting.
|
||||||
c : SW#6:192.168.42.121:60554: deleting
|
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
|
Discussion
|
||||||
----------
|
----------
|
||||||
|
@ -6,5 +6,6 @@ Contents:
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
chromeos
|
||||||
tproxy
|
tproxy
|
||||||
windows
|
windows
|
||||||
|
@ -73,8 +73,6 @@ possible as support for older versions might be dropped in the future.
|
|||||||
Additional Suggested Software
|
Additional Suggested Software
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
- You may want to use autossh, available in various package management
|
|
||||||
systems.
|
|
||||||
- If you are using systemd, sshuttle can notify it when the connection to
|
- 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
|
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
|
this feature to work you must configure the process start-up type for the
|
||||||
@ -89,6 +87,7 @@ Additional Suggested Software
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=notify
|
Type=notify
|
||||||
|
NotifyAccess=all
|
||||||
ExecStart=/usr/bin/sshuttle --dns --remote <user>@<server> <subnets...>
|
ExecStart=/usr/bin/sshuttle --dns --remote <user>@<server> <subnets...>
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
5
requirements-tests.txt
Normal file
5
requirements-tests.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
-r requirements.txt
|
||||||
|
pytest==3.4.2
|
||||||
|
pytest-cov==2.6.0
|
||||||
|
mock==2.0.0
|
||||||
|
flake8==3.5.0
|
27
run
27
run
@ -1,11 +1,16 @@
|
|||||||
#!/bin/sh
|
#!/usr/bin/env sh
|
||||||
source_dir="$(dirname $0)"
|
set -e
|
||||||
(cd "$source_dir" && "$source_dir/setup.py" --version > /dev/null)
|
export PYTHONPATH="$(dirname $0):$PYTHONPATH"
|
||||||
export PYTHONPATH="$source_dir:$PYTHONPATH"
|
|
||||||
if python3.5 -V >/dev/null 2>&1; then
|
python_best_version() {
|
||||||
exec python3.5 -m "sshuttle" "$@"
|
if [ -x "$(command -v python3)" ] &&
|
||||||
elif python2.7 -V >/dev/null 2>&1; then
|
python3 -c "import sys; sys.exit(not sys.version_info > (3, 5))"; then
|
||||||
exec python2.7 -m "sshuttle" "$@"
|
exec python3 "$@"
|
||||||
else
|
elif [ -x "$(command -v python2.7)" ]; then
|
||||||
exec python -m "sshuttle" "$@"
|
exec python2.7 "$@"
|
||||||
fi
|
else
|
||||||
|
exec python "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
python_best_version -m "sshuttle" "$@"
|
||||||
|
@ -7,3 +7,6 @@ universal = 1
|
|||||||
[upload]
|
[upload]
|
||||||
sign=true
|
sign=true
|
||||||
identity=0x1784577F811F6EAC
|
identity=0x1784577F811F6EAC
|
||||||
|
|
||||||
|
[tool:pytest]
|
||||||
|
addopts = --cov=sshuttle --cov-branch --cov-report=term-missing
|
||||||
|
30
setup.py
30
setup.py
@ -2,20 +2,20 @@
|
|||||||
|
|
||||||
# Copyright 2012-2014 Brian May
|
# 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
|
# sshuttle is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU Lesser General Public License as
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
# published by the Free Software Foundation; either version 2.1 of
|
||||||
# (at your option) any later version.
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# 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
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with python-tldap If not, see <http://www.gnu.org/licenses/>.
|
# along with sshuttle; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
@ -39,14 +39,14 @@ setup(
|
|||||||
author_email='brian@linuxpenguins.xyz',
|
author_email='brian@linuxpenguins.xyz',
|
||||||
description='Full-featured" VPN over an SSH tunnel',
|
description='Full-featured" VPN over an SSH tunnel',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
license="GPL2+",
|
license="LGPL2.1+",
|
||||||
long_description=open('README.rst').read(),
|
long_description=open('README.rst').read(),
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"Intended Audience :: End Users/Desktop",
|
"Intended Audience :: End Users/Desktop",
|
||||||
"License :: OSI Approved :: "
|
"License :: OSI Approved :: "
|
||||||
"GNU General Public License v2 or later (GPLv2+)",
|
"GNU Lesser General Public License v2 or later (LGPLv2+)",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python :: 2.7",
|
"Programming Language :: Python :: 2.7",
|
||||||
"Programming Language :: Python :: 3.5",
|
"Programming Language :: Python :: 3.5",
|
||||||
@ -57,6 +57,12 @@ setup(
|
|||||||
'sshuttle = sshuttle.cmdline:main',
|
'sshuttle = sshuttle.cmdline:main',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
tests_require=['pytest', 'pytest-runner', 'mock'],
|
tests_require=[
|
||||||
|
'pytest',
|
||||||
|
'pytest-cov',
|
||||||
|
'pytest-runner',
|
||||||
|
'mock',
|
||||||
|
'flake8',
|
||||||
|
],
|
||||||
keywords="ssh vpn",
|
keywords="ssh vpn",
|
||||||
)
|
)
|
||||||
|
@ -2,6 +2,7 @@ import sys
|
|||||||
import zlib
|
import zlib
|
||||||
import imp
|
import imp
|
||||||
|
|
||||||
|
verbosity = verbosity # noqa: F821 must be a previously defined global
|
||||||
z = zlib.decompressobj()
|
z = zlib.decompressobj()
|
||||||
while 1:
|
while 1:
|
||||||
name = sys.stdin.readline().strip()
|
name = sys.stdin.readline().strip()
|
||||||
@ -21,7 +22,7 @@ while 1:
|
|||||||
setattr(sys.modules[parent], parent_name, module)
|
setattr(sys.modules[parent], parent_name, module)
|
||||||
|
|
||||||
code = compile(content, name, "exec")
|
code = compile(content, name, "exec")
|
||||||
exec(code, module.__dict__)
|
exec(code, module.__dict__) # nosec
|
||||||
sys.modules[name] = module
|
sys.modules[name] = module
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
@ -34,4 +35,5 @@ sshuttle.helpers.verbose = verbosity
|
|||||||
|
|
||||||
import sshuttle.cmdline_options as options
|
import sshuttle.cmdline_options as options
|
||||||
from sshuttle.server import main
|
from sshuttle.server import main
|
||||||
main(options.latency_control, options.auto_hosts)
|
main(options.latency_control, options.auto_hosts, options.to_nameserver,
|
||||||
|
options.auto_nets)
|
||||||
|
@ -8,13 +8,16 @@ import os
|
|||||||
import sshuttle.ssnet as ssnet
|
import sshuttle.ssnet as ssnet
|
||||||
import sshuttle.ssh as ssh
|
import sshuttle.ssh as ssh
|
||||||
import sshuttle.ssyslog as ssyslog
|
import sshuttle.ssyslog as ssyslog
|
||||||
import sshuttle.sdnotify as sdnotify
|
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
from sshuttle.ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
|
from sshuttle.ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
|
||||||
from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, islocal, \
|
from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, islocal, \
|
||||||
resolvconf_nameservers
|
resolvconf_nameservers
|
||||||
from sshuttle.methods import get_method, Features
|
from sshuttle.methods import get_method, Features
|
||||||
|
try:
|
||||||
|
from pwd import getpwnam
|
||||||
|
except ImportError:
|
||||||
|
getpwnam = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# try getting recvmsg from python
|
# try getting recvmsg from python
|
||||||
@ -30,7 +33,7 @@ except AttributeError:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
_extra_fd = os.open('/dev/null', os.O_RDONLY)
|
_extra_fd = os.open(os.devnull, os.O_RDONLY)
|
||||||
|
|
||||||
|
|
||||||
def got_signal(signum, frame):
|
def got_signal(signum, frame):
|
||||||
@ -90,7 +93,7 @@ def daemonize():
|
|||||||
# be deleted.
|
# be deleted.
|
||||||
signal.signal(signal.SIGTERM, got_signal)
|
signal.signal(signal.SIGTERM, got_signal)
|
||||||
|
|
||||||
si = open('/dev/null', 'r+')
|
si = open(os.devnull, 'r+')
|
||||||
os.dup2(si.fileno(), 0)
|
os.dup2(si.fileno(), 0)
|
||||||
os.dup2(si.fileno(), 1)
|
os.dup2(si.fileno(), 1)
|
||||||
si.close()
|
si.close()
|
||||||
@ -108,8 +111,8 @@ def daemon_cleanup():
|
|||||||
|
|
||||||
class MultiListener:
|
class MultiListener:
|
||||||
|
|
||||||
def __init__(self, type=socket.SOCK_STREAM, proto=0):
|
def __init__(self, kind=socket.SOCK_STREAM, proto=0):
|
||||||
self.type = type
|
self.type = kind
|
||||||
self.proto = proto
|
self.proto = proto
|
||||||
self.v6 = None
|
self.v6 = None
|
||||||
self.v4 = None
|
self.v4 = None
|
||||||
@ -157,13 +160,11 @@ class MultiListener:
|
|||||||
self.bind_called = True
|
self.bind_called = True
|
||||||
if address_v6 is not None:
|
if address_v6 is not None:
|
||||||
self.v6 = socket.socket(socket.AF_INET6, self.type, self.proto)
|
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)
|
self.v6.bind(address_v6)
|
||||||
else:
|
else:
|
||||||
self.v6 = None
|
self.v6 = None
|
||||||
if address_v4 is not None:
|
if address_v4 is not None:
|
||||||
self.v4 = socket.socket(socket.AF_INET, self.type, self.proto)
|
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)
|
self.v4.bind(address_v4)
|
||||||
else:
|
else:
|
||||||
self.v4 = None
|
self.v4 = None
|
||||||
@ -182,7 +183,13 @@ class MultiListener:
|
|||||||
|
|
||||||
class FirewallClient:
|
class FirewallClient:
|
||||||
|
|
||||||
def __init__(self, method_name):
|
def __init__(self, method_name, sudo_pythonpath):
|
||||||
|
|
||||||
|
# Default to sudo unless on OpenBSD in which case use built in `doas`
|
||||||
|
elevbin = 'sudo'
|
||||||
|
if platform.platform().startswith('OpenBSD'):
|
||||||
|
elevbin = 'doas'
|
||||||
|
|
||||||
self.auto_nets = []
|
self.auto_nets = []
|
||||||
python_path = os.path.dirname(os.path.dirname(__file__))
|
python_path = os.path.dirname(os.path.dirname(__file__))
|
||||||
argvbase = ([sys.executable, sys.argv[0]] +
|
argvbase = ([sys.executable, sys.argv[0]] +
|
||||||
@ -191,11 +198,13 @@ class FirewallClient:
|
|||||||
['--firewall'])
|
['--firewall'])
|
||||||
if ssyslog._p:
|
if ssyslog._p:
|
||||||
argvbase += ['--syslog']
|
argvbase += ['--syslog']
|
||||||
argv_tries = [
|
elev_prefix = [part % {'eb': elevbin}
|
||||||
['sudo', '-p', '[local sudo] Password: ',
|
for part in ['%(eb)s', '-p',
|
||||||
('PYTHONPATH=%s' % python_path), '--'] + argvbase,
|
'[local %(eb)s] Password: ']]
|
||||||
argvbase
|
if sudo_pythonpath:
|
||||||
]
|
elev_prefix += ['/usr/bin/env',
|
||||||
|
'PYTHONPATH=%s' % python_path]
|
||||||
|
argv_tries = [elev_prefix + argvbase, argvbase]
|
||||||
|
|
||||||
# we can't use stdin/stdout=subprocess.PIPE here, as we normally would,
|
# we can't use stdin/stdout=subprocess.PIPE here, as we normally would,
|
||||||
# because stupid Linux 'su' requires that stdin be attached to a tty.
|
# because stupid Linux 'su' requires that stdin be attached to a tty.
|
||||||
@ -238,7 +247,8 @@ class FirewallClient:
|
|||||||
self.method.set_firewall(self)
|
self.method.set_firewall(self)
|
||||||
|
|
||||||
def setup(self, subnets_include, subnets_exclude, nslist,
|
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_include = subnets_include
|
||||||
self.subnets_exclude = subnets_exclude
|
self.subnets_exclude = subnets_exclude
|
||||||
self.nslist = nslist
|
self.nslist = nslist
|
||||||
@ -247,6 +257,7 @@ class FirewallClient:
|
|||||||
self.dnsport_v6 = dnsport_v6
|
self.dnsport_v6 = dnsport_v6
|
||||||
self.dnsport_v4 = dnsport_v4
|
self.dnsport_v4 = dnsport_v4
|
||||||
self.udp = udp
|
self.udp = udp
|
||||||
|
self.user = user
|
||||||
|
|
||||||
def check(self):
|
def check(self):
|
||||||
rv = self.p.poll()
|
rv = self.p.poll()
|
||||||
@ -276,8 +287,14 @@ class FirewallClient:
|
|||||||
udp = 0
|
udp = 0
|
||||||
if self.udp:
|
if self.udp:
|
||||||
udp = 1
|
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()
|
self.pfile.flush()
|
||||||
|
|
||||||
line = self.pfile.readline()
|
line = self.pfile.readline()
|
||||||
@ -286,7 +303,7 @@ class FirewallClient:
|
|||||||
raise Fatal('%r expected STARTED, got %r' % (self.argv, line))
|
raise Fatal('%r expected STARTED, got %r' % (self.argv, line))
|
||||||
|
|
||||||
def sethostip(self, hostname, ip):
|
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))
|
assert(not re.search(b'[^0-9.]', ip))
|
||||||
self.pfile.write(b'HOST %s,%s\n' % (hostname, ip))
|
self.pfile.write(b'HOST %s,%s\n' % (hostname, ip))
|
||||||
self.pfile.flush()
|
self.pfile.flush()
|
||||||
@ -338,7 +355,7 @@ def onaccept_tcp(listener, method, mux, handlers):
|
|||||||
sock, srcip = listener.accept()
|
sock, srcip = listener.accept()
|
||||||
sock.close()
|
sock.close()
|
||||||
finally:
|
finally:
|
||||||
_extra_fd = os.open('/dev/null', os.O_RDONLY)
|
_extra_fd = os.open(os.devnull, os.O_RDONLY)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
@ -377,7 +394,7 @@ def onaccept_udp(listener, method, mux, handlers):
|
|||||||
srcip, dstip, data = t
|
srcip, dstip, data = t
|
||||||
debug1('Accept UDP: %r -> %r.\n' % (srcip, dstip,))
|
debug1('Accept UDP: %r -> %r.\n' % (srcip, dstip,))
|
||||||
if srcip in udp_by_src:
|
if srcip in udp_by_src:
|
||||||
chan, timeout = udp_by_src[srcip]
|
chan, _ = udp_by_src[srcip]
|
||||||
else:
|
else:
|
||||||
chan = mux.next_channel()
|
chan = mux.next_channel()
|
||||||
mux.channels[chan] = lambda cmd, data: udp_done(
|
mux.channels[chan] = lambda cmd, data: udp_done(
|
||||||
@ -415,7 +432,8 @@ def ondns(listener, method, mux, handlers):
|
|||||||
|
|
||||||
def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
||||||
python, latency_control,
|
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'
|
debug1('Starting client with Python version %s\n'
|
||||||
% platform.python_version())
|
% platform.python_version())
|
||||||
@ -434,7 +452,9 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
|||||||
ssh_cmd, remotename, python,
|
ssh_cmd, remotename, python,
|
||||||
stderr=ssyslog._p and ssyslog._p.stdin,
|
stderr=ssyslog._p and ssyslog._p.stdin,
|
||||||
options=dict(latency_control=latency_control,
|
options=dict(latency_control=latency_control,
|
||||||
auto_hosts=auto_hosts))
|
auto_hosts=auto_hosts,
|
||||||
|
to_nameserver=to_nameserver,
|
||||||
|
auto_nets=auto_nets))
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
if e.args[0] == errno.EPIPE:
|
if e.args[0] == errno.EPIPE:
|
||||||
raise Fatal("failed to establish ssh session (1)")
|
raise Fatal("failed to establish ssh session (1)")
|
||||||
@ -475,6 +495,7 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
|||||||
def onroutes(routestr):
|
def onroutes(routestr):
|
||||||
if auto_nets:
|
if auto_nets:
|
||||||
for line in routestr.strip().split(b'\n'):
|
for line in routestr.strip().split(b'\n'):
|
||||||
|
if not line: continue
|
||||||
(family, ip, width) = line.split(b',', 2)
|
(family, ip, width) = line.split(b',', 2)
|
||||||
family = int(family)
|
family = int(family)
|
||||||
width = int(width)
|
width = int(width)
|
||||||
@ -518,9 +539,6 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
|||||||
debug1('seed_hosts: %r\n' % seed_hosts)
|
debug1('seed_hosts: %r\n' % seed_hosts)
|
||||||
mux.send(0, ssnet.CMD_HOST_REQ, str.encode('\n'.join(seed_hosts)))
|
mux.send(0, ssnet.CMD_HOST_REQ, str.encode('\n'.join(seed_hosts)))
|
||||||
|
|
||||||
sdnotify.send(sdnotify.ready(),
|
|
||||||
sdnotify.status('Connected to %s.' % remotename))
|
|
||||||
|
|
||||||
while 1:
|
while 1:
|
||||||
rv = serverproc.poll()
|
rv = serverproc.poll()
|
||||||
if rv:
|
if rv:
|
||||||
@ -534,7 +552,8 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
|||||||
def main(listenip_v6, listenip_v4,
|
def main(listenip_v6, listenip_v4,
|
||||||
ssh_cmd, remotename, python, latency_control, dns, nslist,
|
ssh_cmd, remotename, python, latency_control, dns, nslist,
|
||||||
method_name, seed_hosts, auto_hosts, auto_nets,
|
method_name, seed_hosts, auto_hosts, auto_nets,
|
||||||
subnets_include, subnets_exclude, daemon, pidfile):
|
subnets_include, subnets_exclude, daemon, to_nameserver, pidfile,
|
||||||
|
user, sudo_pythonpath):
|
||||||
|
|
||||||
if daemon:
|
if daemon:
|
||||||
try:
|
try:
|
||||||
@ -544,11 +563,16 @@ def main(listenip_v6, listenip_v4,
|
|||||||
return 5
|
return 5
|
||||||
debug1('Starting sshuttle proxy.\n')
|
debug1('Starting sshuttle proxy.\n')
|
||||||
|
|
||||||
fw = FirewallClient(method_name)
|
fw = FirewallClient(method_name, sudo_pythonpath)
|
||||||
|
|
||||||
# Get family specific subnet lists
|
# Get family specific subnet lists
|
||||||
if dns:
|
if dns:
|
||||||
nslist += resolvconf_nameservers()
|
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 = subnets_include + subnets_exclude # we don't care here
|
||||||
subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6]
|
subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6]
|
||||||
@ -566,10 +590,19 @@ def main(listenip_v6, listenip_v4,
|
|||||||
else:
|
else:
|
||||||
listenip_v6 = None
|
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.ipv6 = len(subnets_v6) > 0 or listenip_v6 is not None
|
||||||
required.ipv4 = len(subnets_v4) > 0 or listenip_v4 is not None
|
required.ipv4 = len(subnets_v4) > 0 or listenip_v4 is not None
|
||||||
required.udp = avail.udp
|
required.udp = avail.udp
|
||||||
required.dns = len(nslist) > 0
|
required.dns = len(nslist) > 0
|
||||||
|
required.user = False if user is None else True
|
||||||
|
|
||||||
# if IPv6 not supported, ignore IPv6 DNS servers
|
# if IPv6 not supported, ignore IPv6 DNS servers
|
||||||
if not required.ipv6:
|
if not required.ipv6:
|
||||||
@ -585,6 +618,7 @@ def main(listenip_v6, listenip_v4,
|
|||||||
debug1("IPv6 enabled: %r\n" % required.ipv6)
|
debug1("IPv6 enabled: %r\n" % required.ipv6)
|
||||||
debug1("UDP enabled: %r\n" % required.udp)
|
debug1("UDP enabled: %r\n" % required.udp)
|
||||||
debug1("DNS enabled: %r\n" % required.dns)
|
debug1("DNS enabled: %r\n" % required.dns)
|
||||||
|
debug1("User enabled: %r\n" % required.user)
|
||||||
|
|
||||||
# bind to required ports
|
# bind to required ports
|
||||||
if listenip_v4 == "auto":
|
if listenip_v4 == "auto":
|
||||||
@ -604,6 +638,9 @@ def main(listenip_v6, listenip_v4,
|
|||||||
else:
|
else:
|
||||||
# if at least one port missing, we have to search
|
# if at least one port missing, we have to search
|
||||||
ports = range(12300, 9000, -1)
|
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
|
# search for free ports and try to bind
|
||||||
last_e = None
|
last_e = None
|
||||||
@ -645,10 +682,12 @@ def main(listenip_v6, listenip_v4,
|
|||||||
if udp_listener:
|
if udp_listener:
|
||||||
udp_listener.bind(lv6, lv4)
|
udp_listener.bind(lv6, lv4)
|
||||||
bound = True
|
bound = True
|
||||||
|
used_ports.append(port)
|
||||||
break
|
break
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
if e.errno == errno.EADDRINUSE:
|
if e.errno == errno.EADDRINUSE:
|
||||||
last_e = e
|
last_e = e
|
||||||
|
used_ports.append(port)
|
||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
@ -668,6 +707,8 @@ def main(listenip_v6, listenip_v4,
|
|||||||
ports = range(12300, 9000, -1)
|
ports = range(12300, 9000, -1)
|
||||||
for port in ports:
|
for port in ports:
|
||||||
debug2(' %d' % port)
|
debug2(' %d' % port)
|
||||||
|
if port in used_ports: continue
|
||||||
|
|
||||||
dns_listener = MultiListener(socket.SOCK_DGRAM)
|
dns_listener = MultiListener(socket.SOCK_DGRAM)
|
||||||
|
|
||||||
if listenip_v6:
|
if listenip_v6:
|
||||||
@ -687,10 +728,12 @@ def main(listenip_v6, listenip_v4,
|
|||||||
try:
|
try:
|
||||||
dns_listener.bind(lv6, lv4)
|
dns_listener.bind(lv6, lv4)
|
||||||
bound = True
|
bound = True
|
||||||
|
used_ports.append(port)
|
||||||
break
|
break
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
if e.errno == errno.EADDRINUSE:
|
if e.errno == errno.EADDRINUSE:
|
||||||
last_e = e
|
last_e = e
|
||||||
|
used_ports.append(port)
|
||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
debug2('\n')
|
debug2('\n')
|
||||||
@ -706,22 +749,22 @@ def main(listenip_v6, listenip_v4,
|
|||||||
# Last minute sanity checks.
|
# Last minute sanity checks.
|
||||||
# These should never fail.
|
# These should never fail.
|
||||||
# If these do fail, something is broken above.
|
# If these do fail, something is broken above.
|
||||||
if len(subnets_v6) > 0:
|
if subnets_v6:
|
||||||
assert required.ipv6
|
assert required.ipv6
|
||||||
if redirectport_v6 == 0:
|
if redirectport_v6 == 0:
|
||||||
raise Fatal("IPv6 subnets defined but not listening")
|
raise Fatal("IPv6 subnets defined but not listening")
|
||||||
|
|
||||||
if len(nslist_v6) > 0:
|
if nslist_v6:
|
||||||
assert required.dns
|
assert required.dns
|
||||||
assert required.ipv6
|
assert required.ipv6
|
||||||
if dnsport_v6 == 0:
|
if dnsport_v6 == 0:
|
||||||
raise Fatal("IPv6 ns servers defined but not listening")
|
raise Fatal("IPv6 ns servers defined but not listening")
|
||||||
|
|
||||||
if len(subnets_v4) > 0:
|
if subnets_v4:
|
||||||
if redirectport_v4 == 0:
|
if redirectport_v4 == 0:
|
||||||
raise Fatal("IPv4 subnets defined but not listening")
|
raise Fatal("IPv4 subnets defined but not listening")
|
||||||
|
|
||||||
if len(nslist_v4) > 0:
|
if nslist_v4:
|
||||||
if dnsport_v4 == 0:
|
if dnsport_v4 == 0:
|
||||||
raise Fatal("IPv4 ns servers defined but not listening")
|
raise Fatal("IPv4 ns servers defined but not listening")
|
||||||
|
|
||||||
@ -735,13 +778,13 @@ def main(listenip_v6, listenip_v4,
|
|||||||
# start the firewall
|
# start the firewall
|
||||||
fw.setup(subnets_include, subnets_exclude, nslist,
|
fw.setup(subnets_include, subnets_exclude, nslist,
|
||||||
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4,
|
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4,
|
||||||
required.udp)
|
required.udp, user)
|
||||||
|
|
||||||
# start the client process
|
# start the client process
|
||||||
try:
|
try:
|
||||||
return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
||||||
python, latency_control, dns_listener,
|
python, latency_control, dns_listener,
|
||||||
seed_hosts, auto_hosts, auto_nets, daemon)
|
seed_hosts, auto_hosts, auto_nets, daemon, to_nameserver)
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
if daemon:
|
if daemon:
|
||||||
|
@ -25,7 +25,7 @@ def main():
|
|||||||
parser.error('exactly zero arguments expected')
|
parser.error('exactly zero arguments expected')
|
||||||
return firewall.main(opt.method, opt.syslog)
|
return firewall.main(opt.method, opt.syslog)
|
||||||
elif opt.hostwatch:
|
elif opt.hostwatch:
|
||||||
return hostwatch.hw_main(opt.subnets)
|
return hostwatch.hw_main(opt.subnets, opt.auto_hosts)
|
||||||
else:
|
else:
|
||||||
includes = opt.subnets + opt.subnets_file
|
includes = opt.subnets + opt.subnets_file
|
||||||
excludes = opt.exclude
|
excludes = opt.exclude
|
||||||
@ -45,8 +45,8 @@ def main():
|
|||||||
if opt.listen:
|
if opt.listen:
|
||||||
ipport_v6 = None
|
ipport_v6 = None
|
||||||
ipport_v4 = None
|
ipport_v4 = None
|
||||||
list = opt.listen.split(",")
|
lst = opt.listen.split(",")
|
||||||
for ip in list:
|
for ip in lst:
|
||||||
family, ip, port = parse_ipport(ip)
|
family, ip, port = parse_ipport(ip)
|
||||||
if family == socket.AF_INET6:
|
if family == socket.AF_INET6:
|
||||||
ipport_v6 = (ip, port)
|
ipport_v6 = (ip, port)
|
||||||
@ -59,6 +59,8 @@ def main():
|
|||||||
ipport_v6 = "auto" if not opt.disable_ipv6 else None
|
ipport_v6 = "auto" if not opt.disable_ipv6 else None
|
||||||
if opt.syslog:
|
if opt.syslog:
|
||||||
ssyslog.start_syslog()
|
ssyslog.start_syslog()
|
||||||
|
ssyslog.close_stdin()
|
||||||
|
ssyslog.stdout_to_syslog()
|
||||||
ssyslog.stderr_to_syslog()
|
ssyslog.stderr_to_syslog()
|
||||||
return_code = client.main(ipport_v6, ipport_v4,
|
return_code = client.main(ipport_v6, ipport_v4,
|
||||||
opt.ssh_cmd,
|
opt.ssh_cmd,
|
||||||
@ -73,12 +75,16 @@ def main():
|
|||||||
opt.auto_nets,
|
opt.auto_nets,
|
||||||
includes,
|
includes,
|
||||||
excludes,
|
excludes,
|
||||||
opt.daemon, opt.pidfile)
|
opt.daemon,
|
||||||
|
opt.to_ns,
|
||||||
|
opt.pidfile,
|
||||||
|
opt.user,
|
||||||
|
opt.sudo_pythonpath)
|
||||||
|
|
||||||
if return_code == 0:
|
if return_code == 0:
|
||||||
log('Normal exit code, exiting...')
|
log('Normal exit code, exiting...')
|
||||||
else:
|
else:
|
||||||
log('Abnormal exit code detected, failing...' % return_code)
|
log('Abnormal exit code %d detected, failing...' % return_code)
|
||||||
return return_code
|
return return_code
|
||||||
|
|
||||||
except Fatal as e:
|
except Fatal as e:
|
||||||
|
@ -2,6 +2,7 @@ import errno
|
|||||||
import socket
|
import socket
|
||||||
import signal
|
import signal
|
||||||
import sshuttle.ssyslog as ssyslog
|
import sshuttle.ssyslog as ssyslog
|
||||||
|
import sshuttle.sdnotify as sdnotify
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
@ -164,7 +165,7 @@ def main(method_name, syslog):
|
|||||||
_, _, ports = line.partition(" ")
|
_, _, ports = line.partition(" ")
|
||||||
ports = ports.split(",")
|
ports = ports.split(",")
|
||||||
if len(ports) != 4:
|
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_v6 = int(ports[0])
|
||||||
port_v4 = int(ports[1])
|
port_v4 = int(ports[1])
|
||||||
dnsport_v6 = int(ports[2])
|
dnsport_v6 = int(ports[2])
|
||||||
@ -188,9 +189,12 @@ def main(method_name, syslog):
|
|||||||
elif not line.startswith("GO "):
|
elif not line.startswith("GO "):
|
||||||
raise Fatal('firewall: expected GO but got %r' % line)
|
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))
|
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]
|
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]
|
nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6]
|
||||||
@ -200,19 +204,23 @@ def main(method_name, syslog):
|
|||||||
try:
|
try:
|
||||||
debug1('firewall manager: setting up.\n')
|
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')
|
debug2('firewall manager: setting up IPv6.\n')
|
||||||
method.setup_firewall(
|
method.setup_firewall(
|
||||||
port_v6, dnsport_v6, nslist_v6,
|
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')
|
debug2('firewall manager: setting up IPv4.\n')
|
||||||
method.setup_firewall(
|
method.setup_firewall(
|
||||||
port_v4, dnsport_v4, nslist_v4,
|
port_v4, dnsport_v4, nslist_v4,
|
||||||
socket.AF_INET, subnets_v4, udp)
|
socket.AF_INET, subnets_v4, udp,
|
||||||
|
user)
|
||||||
|
|
||||||
stdout.write('STARTED\n')
|
stdout.write('STARTED\n')
|
||||||
|
sdnotify.send(sdnotify.ready(),
|
||||||
|
sdnotify.status('Connected'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stdout.flush()
|
stdout.flush()
|
||||||
@ -244,9 +252,9 @@ def main(method_name, syslog):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if len(subnets_v6) > 0 or len(nslist_v6) > 0:
|
if subnets_v6 or nslist_v6:
|
||||||
debug2('firewall manager: undoing IPv6 changes.\n')
|
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:
|
except:
|
||||||
try:
|
try:
|
||||||
debug1("firewall manager: "
|
debug1("firewall manager: "
|
||||||
@ -257,9 +265,9 @@ def main(method_name, syslog):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if len(subnets_v4) > 0 or len(nslist_v4) > 0:
|
if subnets_v4 or nslist_v4:
|
||||||
debug2('firewall manager: undoing IPv4 changes.\n')
|
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:
|
except:
|
||||||
try:
|
try:
|
||||||
debug1("firewall manager: "
|
debug1("firewall manager: "
|
||||||
|
@ -21,7 +21,7 @@ _smb_ok = True
|
|||||||
hostnames = {}
|
hostnames = {}
|
||||||
queue = {}
|
queue = {}
|
||||||
try:
|
try:
|
||||||
null = open('/dev/null', 'wb')
|
null = open(os.devnull, 'wb')
|
||||||
except IOError:
|
except IOError:
|
||||||
_, e = sys.exc_info()[:2]
|
_, e = sys.exc_info()[:2]
|
||||||
log('warning: %s\n' % e)
|
log('warning: %s\n' % e)
|
||||||
@ -61,23 +61,27 @@ def read_host_cache():
|
|||||||
words = line.strip().split(',')
|
words = line.strip().split(',')
|
||||||
if len(words) == 2:
|
if len(words) == 2:
|
||||||
(name, ip) = words
|
(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()
|
ip = re.sub(r'[^0-9.]', '', ip).strip()
|
||||||
if name and ip:
|
if name and ip:
|
||||||
found_host(name, ip)
|
found_host(name, ip)
|
||||||
|
|
||||||
|
|
||||||
def found_host(hostname, ip):
|
def found_host(name, ip):
|
||||||
hostname = re.sub(r'\..*', '', hostname)
|
hostname = re.sub(r'\..*', '', name)
|
||||||
hostname = re.sub(r'[^-\w]', '_', hostname)
|
hostname = re.sub(r'[^-\w\.]', '_', hostname)
|
||||||
if (ip.startswith('127.') or ip.startswith('255.') or
|
if (ip.startswith('127.') or ip.startswith('255.') or
|
||||||
hostname == 'localhost'):
|
hostname == 'localhost'):
|
||||||
return
|
return
|
||||||
oldip = hostnames.get(hostname)
|
|
||||||
|
if hostname != name:
|
||||||
|
found_host(hostname, ip)
|
||||||
|
|
||||||
|
oldip = hostnames.get(name)
|
||||||
if oldip != ip:
|
if oldip != ip:
|
||||||
hostnames[hostname] = ip
|
hostnames[name] = ip
|
||||||
debug1('Found: %s: %s\n' % (hostname, ip))
|
debug1('Found: %s: %s\n' % (name, ip))
|
||||||
sys.stdout.write('%s,%s\n' % (hostname, ip))
|
sys.stdout.write('%s,%s\n' % (name, ip))
|
||||||
write_host_cache()
|
write_host_cache()
|
||||||
|
|
||||||
|
|
||||||
@ -247,7 +251,7 @@ def _enqueue(op, *args):
|
|||||||
|
|
||||||
|
|
||||||
def _stdin_still_ok(timeout):
|
def _stdin_still_ok(timeout):
|
||||||
r, w, x = select.select([sys.stdin.fileno()], [], [], timeout)
|
r, _, _ = select.select([sys.stdin.fileno()], [], [], timeout)
|
||||||
if r:
|
if r:
|
||||||
b = os.read(sys.stdin.fileno(), 4096)
|
b = os.read(sys.stdin.fileno(), 4096)
|
||||||
if not b:
|
if not b:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import re
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import subprocess as ssubprocess
|
import subprocess as ssubprocess
|
||||||
@ -23,13 +24,13 @@ def ipt_chain_exists(family, table, name):
|
|||||||
'PATH': os.environ['PATH'],
|
'PATH': os.environ['PATH'],
|
||||||
'LC_ALL': "C",
|
'LC_ALL': "C",
|
||||||
}
|
}
|
||||||
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=env)
|
try:
|
||||||
for line in p.stdout:
|
output = ssubprocess.check_output(argv, env=env)
|
||||||
if line.startswith(b'Chain %s ' % name.encode("ASCII")):
|
for line in output.decode('ASCII').split('\n'):
|
||||||
return True
|
if line.startswith('Chain %s ' % name):
|
||||||
rv = p.wait()
|
return True
|
||||||
if rv:
|
except ssubprocess.CalledProcessError as e:
|
||||||
raise Fatal('%r returned %d' % (argv, rv))
|
raise Fatal('%r returned %d' % (argv, e.returncode))
|
||||||
|
|
||||||
|
|
||||||
def ipt(family, table, *args):
|
def ipt(family, table, *args):
|
||||||
@ -49,6 +50,39 @@ def ipt(family, table, *args):
|
|||||||
raise Fatal('%r returned %d' % (argv, rv))
|
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",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
output = ssubprocess.check_output(argv, env=env)
|
||||||
|
for line in output.decode('utf-8').split('\n'):
|
||||||
|
if ('jump %s' % chain) in line:
|
||||||
|
return re.sub('.*# ', '', line)
|
||||||
|
except ssubprocess.CalledProcessError as e:
|
||||||
|
raise Fatal('%r returned %d' % (argv, e.returncode))
|
||||||
|
|
||||||
|
|
||||||
_no_ttl_module = False
|
_no_ttl_module = False
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,17 +35,21 @@ class BaseMethod(object):
|
|||||||
def set_firewall(self, firewall):
|
def set_firewall(self, firewall):
|
||||||
self.firewall = firewall
|
self.firewall = firewall
|
||||||
|
|
||||||
def get_supported_features(self):
|
@staticmethod
|
||||||
|
def get_supported_features():
|
||||||
result = Features()
|
result = Features()
|
||||||
result.ipv6 = False
|
result.ipv6 = False
|
||||||
result.udp = False
|
result.udp = False
|
||||||
result.dns = True
|
result.dns = True
|
||||||
|
result.user = False
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_tcp_dstip(self, sock):
|
@staticmethod
|
||||||
|
def get_tcp_dstip(sock):
|
||||||
return original_dst(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')
|
debug3('Accept UDP using recvfrom.\n')
|
||||||
data, srcip = udp_listener.recvfrom(bufsize)
|
data, srcip = udp_listener.recvfrom(bufsize)
|
||||||
return (srcip, None, data)
|
return (srcip, None, data)
|
||||||
@ -64,19 +68,21 @@ class BaseMethod(object):
|
|||||||
|
|
||||||
def assert_features(self, features):
|
def assert_features(self, features):
|
||||||
avail = self.get_supported_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):
|
if getattr(features, key) and not getattr(avail, key):
|
||||||
raise Fatal(
|
raise Fatal(
|
||||||
"Feature %s not supported with method %s.\n" %
|
"Feature %s not supported with method %s.\n" %
|
||||||
(key, self.name))
|
(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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def restore_firewall(self, port, family, udp):
|
def restore_firewall(self, port, family, udp, user):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def firewall_command(self, line):
|
@staticmethod
|
||||||
|
def firewall_command(line):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@ -96,12 +102,14 @@ def get_method(method_name):
|
|||||||
def get_auto_method():
|
def get_auto_method():
|
||||||
if _program_exists('iptables'):
|
if _program_exists('iptables'):
|
||||||
method_name = "nat"
|
method_name = "nat"
|
||||||
|
elif _program_exists('nft'):
|
||||||
|
method_name = "nft"
|
||||||
elif _program_exists('pfctl'):
|
elif _program_exists('pfctl'):
|
||||||
method_name = "pf"
|
method_name = "pf"
|
||||||
elif _program_exists('ipfw'):
|
elif _program_exists('ipfw'):
|
||||||
method_name = "ipfw"
|
method_name = "ipfw"
|
||||||
else:
|
else:
|
||||||
raise Fatal(
|
raise Fatal(
|
||||||
"can't find either iptables or pfctl; check your PATH")
|
"can't find either iptables, nft or pfctl; check your PATH")
|
||||||
|
|
||||||
return get_method(method_name)
|
return get_method(method_name)
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import struct
|
|
||||||
import subprocess as ssubprocess
|
import subprocess as ssubprocess
|
||||||
from sshuttle.methods import BaseMethod
|
from sshuttle.methods import BaseMethod
|
||||||
from sshuttle.helpers import log, debug1, debug3, \
|
from sshuttle.helpers import log, debug1, debug3, \
|
||||||
@ -31,9 +29,9 @@ IPV6_RECVDSTADDR = 74
|
|||||||
if recvmsg == "python":
|
if recvmsg == "python":
|
||||||
def recv_udp(listener, bufsize):
|
def recv_udp(listener, bufsize):
|
||||||
debug3('Accept UDP python using recvmsg.\n')
|
debug3('Accept UDP python using recvmsg.\n')
|
||||||
data, ancdata, msg_flags, srcip = listener.recvmsg(4096, socket.CMSG_SPACE(4))
|
data, ancdata, _, srcip = \
|
||||||
|
listener.recvmsg(4096, socket.CMSG_SPACE(4))
|
||||||
dstip = None
|
dstip = None
|
||||||
family = None
|
|
||||||
for cmsg_level, cmsg_type, cmsg_data in ancdata:
|
for cmsg_level, cmsg_type, cmsg_data in ancdata:
|
||||||
if cmsg_level == socket.SOL_IP and cmsg_type == IP_RECVDSTADDR:
|
if cmsg_level == socket.SOL_IP and cmsg_type == IP_RECVDSTADDR:
|
||||||
port = 53
|
port = 53
|
||||||
@ -44,13 +42,13 @@ if recvmsg == "python":
|
|||||||
elif recvmsg == "socket_ext":
|
elif recvmsg == "socket_ext":
|
||||||
def recv_udp(listener, bufsize):
|
def recv_udp(listener, bufsize):
|
||||||
debug3('Accept UDP using socket_ext recvmsg.\n')
|
debug3('Accept UDP using socket_ext recvmsg.\n')
|
||||||
srcip, data, adata, flags = listener.recvmsg((bufsize,), socket.CMSG_SPACE(4))
|
srcip, data, adata, _ = \
|
||||||
|
listener.recvmsg((bufsize,), socket.CMSG_SPACE(4))
|
||||||
dstip = None
|
dstip = None
|
||||||
family = None
|
|
||||||
for a in adata:
|
for a in adata:
|
||||||
if a.cmsg_level == socket.SOL_IP and a.cmsg_type == IP_RECVDSTADDR:
|
if a.cmsg_level == socket.SOL_IP and a.cmsg_type == IP_RECVDSTADDR:
|
||||||
port = 53
|
port = 53
|
||||||
ip = socket.inet_ntop(socket.AF_INET, cmsg_data[0:4])
|
ip = socket.inet_ntop(socket.AF_INET, a.cmsg_data[0:4])
|
||||||
dstip = (ip, port)
|
dstip = (ip, port)
|
||||||
break
|
break
|
||||||
return (srcip, dstip, data[0])
|
return (srcip, dstip, data[0])
|
||||||
@ -75,7 +73,7 @@ def ipfw_rule_exists(n):
|
|||||||
if not ('ipttl 42' in line or 'check-state' in line):
|
if not ('ipttl 42' in line or 'check-state' in line):
|
||||||
log('non-sshuttle ipfw rule: %r\n' % line.strip())
|
log('non-sshuttle ipfw rule: %r\n' % line.strip())
|
||||||
raise Fatal('non-sshuttle ipfw rule #%d already exists!' % n)
|
raise Fatal('non-sshuttle ipfw rule #%d already exists!' % n)
|
||||||
found = True
|
found = True
|
||||||
rv = p.wait()
|
rv = p.wait()
|
||||||
if rv:
|
if rv:
|
||||||
raise Fatal('%r returned %d' % (argv, rv))
|
raise Fatal('%r returned %d' % (argv, rv))
|
||||||
@ -107,7 +105,7 @@ def _fill_oldctls(prefix):
|
|||||||
def _sysctl_set(name, val):
|
def _sysctl_set(name, val):
|
||||||
argv = ['sysctl', '-w', '%s=%s' % (name, val)]
|
argv = ['sysctl', '-w', '%s=%s' % (name, val)]
|
||||||
debug1('>> %s\n' % ' '.join(argv))
|
debug1('>> %s\n' % ' '.join(argv))
|
||||||
return ssubprocess.call(argv, stdout=open('/dev/null', 'w'))
|
return ssubprocess.call(argv, stdout=open(os.devnull, 'w'))
|
||||||
|
|
||||||
|
|
||||||
_changedctls = []
|
_changedctls = []
|
||||||
@ -150,17 +148,17 @@ def ipfw_noexit(*args):
|
|||||||
ssubprocess.call(argv)
|
ssubprocess.call(argv)
|
||||||
|
|
||||||
class Method(BaseMethod):
|
class Method(BaseMethod):
|
||||||
|
|
||||||
def get_supported_features(self):
|
def get_supported_features(self):
|
||||||
result = super(Method, self).get_supported_features()
|
result = super(Method, self).get_supported_features()
|
||||||
result.ipv6 = False
|
result.ipv6 = False
|
||||||
result.udp = False #NOTE: Almost there, kernel patch needed
|
result.udp = False #NOTE: Almost there, kernel patch needed
|
||||||
result.dns = True
|
result.dns = True
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_tcp_dstip(self, sock):
|
def get_tcp_dstip(self, sock):
|
||||||
return sock.getsockname()
|
return sock.getsockname()
|
||||||
|
|
||||||
def recv_udp(self, udp_listener, bufsize):
|
def recv_udp(self, udp_listener, bufsize):
|
||||||
srcip, dstip, data = recv_udp(udp_listener, bufsize)
|
srcip, dstip, data = recv_udp(udp_listener, bufsize)
|
||||||
if not dstip:
|
if not dstip:
|
||||||
@ -193,13 +191,14 @@ class Method(BaseMethod):
|
|||||||
#if udp_listener.v6 is not None:
|
#if udp_listener.v6 is not None:
|
||||||
# udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVDSTADDR, 1)
|
# udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVDSTADDR, 1)
|
||||||
|
|
||||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
|
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
||||||
|
user):
|
||||||
# IPv6 not supported
|
# IPv6 not supported
|
||||||
if family not in [socket.AF_INET ]:
|
if family not in [socket.AF_INET]:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Address family "%s" unsupported by ipfw method_name'
|
'Address family "%s" unsupported by ipfw method_name'
|
||||||
% family_to_string(family))
|
% family_to_string(family))
|
||||||
|
|
||||||
#XXX: Any risk from this?
|
#XXX: Any risk from this?
|
||||||
ipfw_noexit('delete', '1')
|
ipfw_noexit('delete', '1')
|
||||||
|
|
||||||
@ -207,13 +206,13 @@ class Method(BaseMethod):
|
|||||||
name = _changedctls.pop()
|
name = _changedctls.pop()
|
||||||
oldval = _oldctls[name]
|
oldval = _oldctls[name]
|
||||||
_sysctl_set(name, oldval)
|
_sysctl_set(name, oldval)
|
||||||
|
|
||||||
if subnets or dnsport:
|
if subnets or dnsport:
|
||||||
sysctl_set('net.inet.ip.fw.enable', 1)
|
sysctl_set('net.inet.ip.fw.enable', 1)
|
||||||
|
|
||||||
ipfw('add', '1', 'check-state', 'ip',
|
ipfw('add', '1', 'check-state', 'ip',
|
||||||
'from', 'any', 'to', 'any')
|
'from', 'any', 'to', 'any')
|
||||||
|
|
||||||
ipfw('add', '1', 'skipto', '2',
|
ipfw('add', '1', 'skipto', '2',
|
||||||
'tcp',
|
'tcp',
|
||||||
'from', 'any', 'to', 'table(125)')
|
'from', 'any', 'to', 'table(125)')
|
||||||
@ -224,7 +223,7 @@ class Method(BaseMethod):
|
|||||||
|
|
||||||
ipfw_noexit('table', '124', 'flush')
|
ipfw_noexit('table', '124', 'flush')
|
||||||
dnscount = 0
|
dnscount = 0
|
||||||
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]:
|
||||||
ipfw('table', '124', 'add', '%s' % (ip))
|
ipfw('table', '124', 'add', '%s' % (ip))
|
||||||
dnscount += 1
|
dnscount += 1
|
||||||
if dnscount > 0:
|
if dnscount > 0:
|
||||||
@ -232,30 +231,21 @@ class Method(BaseMethod):
|
|||||||
'udp',
|
'udp',
|
||||||
'from', 'any', 'to', 'table(124)',
|
'from', 'any', 'to', 'table(124)',
|
||||||
'not', 'ipttl', '42')
|
'not', 'ipttl', '42')
|
||||||
"""if udp:
|
ipfw('add', '1', 'allow',
|
||||||
ipfw('add', '1', 'skipto', '2',
|
|
||||||
'udp',
|
|
||||||
'from', 'any', 'to', 'table(125)')
|
|
||||||
ipfw('add', '1', 'fwd', '127.0.0.1,%d' % port,
|
|
||||||
'udp',
|
|
||||||
'from', 'any', 'to', 'table(126)',
|
|
||||||
'not', 'ipttl', '42')
|
|
||||||
"""
|
|
||||||
ipfw('add', '1', 'allow',
|
|
||||||
'udp',
|
'udp',
|
||||||
'from', 'any', 'to', 'any',
|
'from', 'any', 'to', 'any',
|
||||||
'ipttl', '42')
|
'ipttl', '42')
|
||||||
|
|
||||||
if subnets:
|
if subnets:
|
||||||
# create new subnet entries
|
# create new subnet entries
|
||||||
for f, swidth, sexclude, snet \
|
for _, swidth, sexclude, snet \
|
||||||
in sorted(subnets, key=lambda s: s[1], reverse=True):
|
in sorted(subnets, key=lambda s: s[1], reverse=True):
|
||||||
if sexclude:
|
if sexclude:
|
||||||
ipfw('table', '125', 'add', '%s/%s' % (snet, swidth))
|
ipfw('table', '125', 'add', '%s/%s' % (snet, swidth))
|
||||||
else:
|
else:
|
||||||
ipfw('table', '126', 'add', '%s/%s' % (snet, swidth))
|
ipfw('table', '126', 'add', '%s/%s' % (snet, swidth))
|
||||||
|
|
||||||
def restore_firewall(self, port, family, udp):
|
def restore_firewall(self, port, family, udp, user):
|
||||||
if family not in [socket.AF_INET]:
|
if family not in [socket.AF_INET]:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Address family "%s" unsupported by tproxy method'
|
'Address family "%s" unsupported by tproxy method'
|
||||||
@ -265,4 +255,3 @@ class Method(BaseMethod):
|
|||||||
ipfw_noexit('table', '124', 'flush')
|
ipfw_noexit('table', '124', 'flush')
|
||||||
ipfw_noexit('table', '125', 'flush')
|
ipfw_noexit('table', '125', 'flush')
|
||||||
ipfw_noexit('table', '126', 'flush')
|
ipfw_noexit('table', '126', 'flush')
|
||||||
|
|
||||||
|
@ -12,7 +12,8 @@ class Method(BaseMethod):
|
|||||||
# the multiple copies shouldn't have overlapping subnets, or only the most-
|
# 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
|
# recently-started one will win (because we use "-I OUTPUT 1" instead of
|
||||||
# "-A OUTPUT").
|
# "-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
|
# only ipv4 supported with NAT
|
||||||
if family != socket.AF_INET:
|
if family != socket.AF_INET:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
@ -29,18 +30,28 @@ class Method(BaseMethod):
|
|||||||
def _ipt_ttl(*args):
|
def _ipt_ttl(*args):
|
||||||
return ipt_ttl(family, table, *args)
|
return ipt_ttl(family, table, *args)
|
||||||
|
|
||||||
|
def _ipm(*args):
|
||||||
|
return ipt(family, "mangle", *args)
|
||||||
|
|
||||||
chain = 'sshuttle-%s' % port
|
chain = 'sshuttle-%s' % port
|
||||||
|
|
||||||
# basic cleanup/setup of chains
|
# basic cleanup/setup of chains
|
||||||
self.restore_firewall(port, family, udp)
|
self.restore_firewall(port, family, udp, user)
|
||||||
|
|
||||||
_ipt('-N', chain)
|
_ipt('-N', chain)
|
||||||
_ipt('-F', chain)
|
_ipt('-F', chain)
|
||||||
_ipt('-I', 'OUTPUT', '1', '-j', chain)
|
if user is not None:
|
||||||
_ipt('-I', 'PREROUTING', '1', '-j', chain)
|
_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.
|
# create new subnet entries.
|
||||||
for f, swidth, sexclude, snet, fport, lport \
|
for _, swidth, sexclude, snet, fport, lport \
|
||||||
in sorted(subnets, key=subnet_weight, reverse=True):
|
in sorted(subnets, key=subnet_weight, reverse=True):
|
||||||
tcp_ports = ('-p', 'tcp')
|
tcp_ports = ('-p', 'tcp')
|
||||||
if fport:
|
if fport:
|
||||||
@ -55,14 +66,14 @@ class Method(BaseMethod):
|
|||||||
'--dest', '%s/%s' % (snet, swidth),
|
'--dest', '%s/%s' % (snet, swidth),
|
||||||
*(tcp_ports + ('--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',
|
_ipt_ttl('-A', chain, '-j', 'REDIRECT',
|
||||||
'--dest', '%s/32' % ip,
|
'--dest', '%s/32' % ip,
|
||||||
'-p', 'udp',
|
'-p', 'udp',
|
||||||
'--dport', '53',
|
'--dport', '53',
|
||||||
'--to-ports', str(dnsport))
|
'--to-ports', str(dnsport))
|
||||||
|
|
||||||
def restore_firewall(self, port, family, udp):
|
def restore_firewall(self, port, family, udp, user):
|
||||||
# only ipv4 supported with NAT
|
# only ipv4 supported with NAT
|
||||||
if family != socket.AF_INET:
|
if family != socket.AF_INET:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
@ -79,11 +90,25 @@ class Method(BaseMethod):
|
|||||||
def _ipt_ttl(*args):
|
def _ipt_ttl(*args):
|
||||||
return ipt_ttl(family, table, *args)
|
return ipt_ttl(family, table, *args)
|
||||||
|
|
||||||
|
def _ipm(*args):
|
||||||
|
return ipt(family, "mangle", *args)
|
||||||
|
|
||||||
chain = 'sshuttle-%s' % port
|
chain = 'sshuttle-%s' % port
|
||||||
|
|
||||||
# basic cleanup/setup of chains
|
# basic cleanup/setup of chains
|
||||||
if ipt_chain_exists(family, table, chain):
|
if ipt_chain_exists(family, table, chain):
|
||||||
nonfatal(_ipt, '-D', 'OUTPUT', '-j', chain)
|
if user is not None:
|
||||||
nonfatal(_ipt, '-D', 'PREROUTING', '-j', chain)
|
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)
|
nonfatal(_ipt, '-F', chain)
|
||||||
_ipt('-X', chain)
|
_ipt('-X', chain)
|
||||||
|
|
||||||
|
def get_supported_features(self):
|
||||||
|
result = super(Method, self).get_supported_features()
|
||||||
|
result.user = True
|
||||||
|
return result
|
||||||
|
84
sshuttle/methods/nft.py
Normal file
84
sshuttle/methods/nft.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
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 and fport != lport:
|
||||||
|
tcp_ports = \
|
||||||
|
tcp_ports + \
|
||||||
|
('tcp', 'dport', '{ %d-%d }' % (fport, lport))
|
||||||
|
elif fport and fport == lport:
|
||||||
|
tcp_ports = tcp_ports + ('tcp', 'dport', '%d' % (fport))
|
||||||
|
|
||||||
|
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)
|
@ -14,7 +14,11 @@ from sshuttle.helpers import debug1, debug2, debug3, Fatal, family_to_string
|
|||||||
from sshuttle.methods import BaseMethod
|
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
|
_pf_fd = None
|
||||||
|
|
||||||
|
|
||||||
@ -31,11 +35,11 @@ class Generic(object):
|
|||||||
|
|
||||||
class pf_addr(Structure):
|
class pf_addr(Structure):
|
||||||
class _pfa(Union):
|
class _pfa(Union):
|
||||||
_fields_ = [("v4", c_uint32), # struct in_addr
|
_fields_ = [("v4", c_uint32), # struct in_addr
|
||||||
("v6", c_uint32 * 4), # struct in6_addr
|
("v6", c_uint32 * 4), # struct in6_addr
|
||||||
("addr8", c_uint8 * 16),
|
("addr8", c_uint8 * 16),
|
||||||
("addr16", c_uint16 * 8),
|
("addr16", c_uint16 * 8),
|
||||||
("addr32", c_uint32 * 4)]
|
("addr32", c_uint32 * 4)]
|
||||||
|
|
||||||
_fields_ = [("pfa", _pfa)]
|
_fields_ = [("pfa", _pfa)]
|
||||||
_anonymous_ = ("pfa",)
|
_anonymous_ = ("pfa",)
|
||||||
@ -60,13 +64,14 @@ class Generic(object):
|
|||||||
def enable(self):
|
def enable(self):
|
||||||
if b'INFO:\nStatus: Disabled' in self.status:
|
if b'INFO:\nStatus: Disabled' in self.status:
|
||||||
pfctl('-e')
|
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)
|
pfctl('-a %s -F all' % anchor)
|
||||||
if _pf_context['started_by_sshuttle']:
|
if _pf_context['started_by_sshuttle'] == 1:
|
||||||
pfctl('-d')
|
pfctl('-d')
|
||||||
_pf_context['started_by_sshuttle'] = False
|
_pf_context['started_by_sshuttle'] -= 1
|
||||||
|
|
||||||
def query_nat(self, family, proto, src_ip, src_port, dst_ip, dst_port):
|
def query_nat(self, family, proto, src_ip, src_port, dst_ip, dst_port):
|
||||||
[proto, family, src_port, dst_port] = [
|
[proto, family, src_port, dst_port] = [
|
||||||
@ -94,11 +99,13 @@ class Generic(object):
|
|||||||
port = socket.ntohs(self._get_natlook_port(pnl.rdxport))
|
port = socket.ntohs(self._get_natlook_port(pnl.rdxport))
|
||||||
return (ip, port)
|
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.sxport = socket.htons(src_port)
|
||||||
pnl.dxport = socket.htons(dst_port)
|
pnl.dxport = socket.htons(dst_port)
|
||||||
|
|
||||||
def _get_natlook_port(self, xport):
|
@staticmethod
|
||||||
|
def _get_natlook_port(xport):
|
||||||
return xport
|
return xport
|
||||||
|
|
||||||
def add_anchors(self, anchor, status=None):
|
def add_anchors(self, anchor, status=None):
|
||||||
@ -108,35 +115,39 @@ class Generic(object):
|
|||||||
if ('\nanchor "%s"' % anchor).encode('ASCII') not in status:
|
if ('\nanchor "%s"' % anchor).encode('ASCII') not in status:
|
||||||
self._add_anchor_rule(self.PF_PASS, anchor.encode('ASCII'))
|
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:
|
if pr is None:
|
||||||
pr = self.pfioc_rule()
|
pr = self.pfioc_rule()
|
||||||
|
|
||||||
memmove(addressof(pr) + self.ANCHOR_CALL_OFFSET, name,
|
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,
|
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(
|
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)
|
ioctl(pf_get_dev(), pf.DIOCCHANGERULE, pr)
|
||||||
|
|
||||||
memmove(addressof(pr) + self.ACTION_OFFSET, struct.pack(
|
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)
|
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'
|
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'
|
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)
|
assert isinstance(rules, bytes)
|
||||||
debug3("rules:\n" + rules.decode("ASCII"))
|
debug3("rules:\n" + rules.decode("ASCII"))
|
||||||
pfctl('-a %s -f /dev/stdin' % anchor, rules)
|
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]
|
return b'skip' in pfctl('-s Interfaces -i lo -v')[0]
|
||||||
|
|
||||||
|
|
||||||
@ -165,8 +176,17 @@ class FreeBsd(Generic):
|
|||||||
freebsd.pfioc_natlook = pfioc_natlook
|
freebsd.pfioc_natlook = pfioc_natlook
|
||||||
return freebsd
|
return freebsd
|
||||||
|
|
||||||
def __init__(self):
|
def enable(self):
|
||||||
super(FreeBsd, self).__init__()
|
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):
|
def add_anchors(self, anchor):
|
||||||
status = pfctl('-s all')[0]
|
status = pfctl('-s all')[0]
|
||||||
@ -174,14 +194,14 @@ class FreeBsd(Generic):
|
|||||||
self._add_anchor_rule(self.PF_RDR, anchor.encode('ASCII'))
|
self._add_anchor_rule(self.PF_RDR, anchor.encode('ASCII'))
|
||||||
super(FreeBsd, self).add_anchors(anchor, status=status)
|
super(FreeBsd, self).add_anchors(anchor, status=status)
|
||||||
|
|
||||||
def _add_anchor_rule(self, type, name):
|
def _add_anchor_rule(self, kind, name, pr=None):
|
||||||
pr = self.pfioc_rule()
|
pr = pr or self.pfioc_rule()
|
||||||
ppa = self.pfioc_pooladdr()
|
ppa = self.pfioc_pooladdr()
|
||||||
|
|
||||||
ioctl(pf_get_dev(), self.DIOCBEGINADDRS, ppa)
|
ioctl(pf_get_dev(), self.DIOCBEGINADDRS, ppa)
|
||||||
# pool ticket
|
# pool ticket
|
||||||
memmove(addressof(pr) + self.POOL_TICKET_OFFSET, ppa[4:8], 4)
|
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):
|
def add_rules(self, anchor, includes, port, dnsport, nslist, family):
|
||||||
inet_version = self._inet_version(family)
|
inet_version = self._inet_version(family)
|
||||||
@ -189,19 +209,19 @@ class FreeBsd(Generic):
|
|||||||
|
|
||||||
tables = []
|
tables = []
|
||||||
translating_rules = [
|
translating_rules = [
|
||||||
b'rdr pass on lo0 %s proto tcp to %s '
|
b'rdr pass on lo0 %s proto tcp from ! %s to %s '
|
||||||
b'-> %s port %r' % (inet_version, subnet, lo_addr, port)
|
b'-> %s port %r' % (inet_version, lo_addr, subnet, lo_addr, port)
|
||||||
for exclude, subnet in includes if not exclude
|
for exclude, subnet in includes if not exclude
|
||||||
]
|
]
|
||||||
filtering_rules = [
|
filtering_rules = [
|
||||||
b'pass out route-to lo0 %s proto tcp '
|
b'pass out route-to lo0 %s proto tcp '
|
||||||
b'to %s keep state' % (inet_version, subnet)
|
b'to %s keep state' % (inet_version, subnet)
|
||||||
if not exclude else
|
if not exclude else
|
||||||
b'pass out quick %s proto tcp to %s' % (inet_version, subnet)
|
b'pass out %s proto tcp to %s' % (inet_version, subnet)
|
||||||
for exclude, subnet in includes
|
for exclude, subnet in includes
|
||||||
]
|
]
|
||||||
|
|
||||||
if len(nslist) > 0:
|
if nslist:
|
||||||
tables.append(
|
tables.append(
|
||||||
b'table <dns_servers> {%s}' %
|
b'table <dns_servers> {%s}' %
|
||||||
b','.join([ns[1].encode("ASCII") for ns in nslist]))
|
b','.join([ns[1].encode("ASCII") for ns in nslist]))
|
||||||
@ -241,7 +261,7 @@ class OpenBsd(Generic):
|
|||||||
("proto_variant", c_uint8),
|
("proto_variant", c_uint8),
|
||||||
("direction", c_uint8)]
|
("direction", c_uint8)]
|
||||||
|
|
||||||
self.pfioc_rule = c_char * 3400
|
self.pfioc_rule = c_char * 3416
|
||||||
self.pfioc_natlook = pfioc_natlook
|
self.pfioc_natlook = pfioc_natlook
|
||||||
super(OpenBsd, self).__init__()
|
super(OpenBsd, self).__init__()
|
||||||
|
|
||||||
@ -267,11 +287,11 @@ class OpenBsd(Generic):
|
|||||||
b'pass out %s proto tcp to %s '
|
b'pass out %s proto tcp to %s '
|
||||||
b'route-to lo0 keep state' % (inet_version, subnet)
|
b'route-to lo0 keep state' % (inet_version, subnet)
|
||||||
if not exclude else
|
if not exclude else
|
||||||
b'pass out quick %s proto tcp to %s' % (inet_version, subnet)
|
b'pass out %s proto tcp to %s' % (inet_version, subnet)
|
||||||
for exclude, subnet in includes
|
for exclude, subnet in includes
|
||||||
]
|
]
|
||||||
|
|
||||||
if len(nslist) > 0:
|
if nslist:
|
||||||
tables.append(
|
tables.append(
|
||||||
b'table <dns_servers> {%s}' %
|
b'table <dns_servers> {%s}' %
|
||||||
b','.join([ns[1].encode("ASCII") for ns in nslist]))
|
b','.join([ns[1].encode("ASCII") for ns in nslist]))
|
||||||
@ -417,11 +437,8 @@ class Method(BaseMethod):
|
|||||||
|
|
||||||
return sock.getsockname()
|
return sock.getsockname()
|
||||||
|
|
||||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
|
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
|
||||||
tables = []
|
user):
|
||||||
translating_rules = []
|
|
||||||
filtering_rules = []
|
|
||||||
|
|
||||||
if family not in [socket.AF_INET, socket.AF_INET6]:
|
if family not in [socket.AF_INET, socket.AF_INET6]:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Address family "%s" unsupported by pf method_name'
|
'Address family "%s" unsupported by pf method_name'
|
||||||
@ -429,13 +446,13 @@ class Method(BaseMethod):
|
|||||||
if udp:
|
if udp:
|
||||||
raise Exception("UDP not supported by pf method_name")
|
raise Exception("UDP not supported by pf method_name")
|
||||||
|
|
||||||
if len(subnets) > 0:
|
if subnets:
|
||||||
includes = []
|
includes = []
|
||||||
# If a given subnet is both included and excluded, list the
|
# If a given subnet is both included and excluded, list the
|
||||||
# exclusion first; the table will ignore the second, opposite
|
# exclusion first; the table will ignore the second, opposite
|
||||||
# definition
|
# definition
|
||||||
for f, swidth, sexclude, snet, fport, lport \
|
for _, swidth, sexclude, snet, fport, lport \
|
||||||
in sorted(subnets, key=subnet_weight, reverse=True):
|
in sorted(subnets, key=subnet_weight):
|
||||||
includes.append((sexclude, b"%s/%d%s" % (
|
includes.append((sexclude, b"%s/%d%s" % (
|
||||||
snet.encode("ASCII"),
|
snet.encode("ASCII"),
|
||||||
swidth,
|
swidth,
|
||||||
@ -446,7 +463,7 @@ class Method(BaseMethod):
|
|||||||
pf.add_rules(anchor, includes, port, dnsport, nslist, family)
|
pf.add_rules(anchor, includes, port, dnsport, nslist, family)
|
||||||
pf.enable()
|
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]:
|
if family not in [socket.AF_INET, socket.AF_INET6]:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Address family "%s" unsupported by pf method_name'
|
'Address family "%s" unsupported by pf method_name'
|
||||||
|
@ -33,7 +33,7 @@ IPV6_RECVORIGDSTADDR = IPV6_ORIGDSTADDR
|
|||||||
if recvmsg == "python":
|
if recvmsg == "python":
|
||||||
def recv_udp(listener, bufsize):
|
def recv_udp(listener, bufsize):
|
||||||
debug3('Accept UDP python using recvmsg.\n')
|
debug3('Accept UDP python using recvmsg.\n')
|
||||||
data, ancdata, msg_flags, srcip = listener.recvmsg(
|
data, ancdata, _, srcip = listener.recvmsg(
|
||||||
4096, socket.CMSG_SPACE(24))
|
4096, socket.CMSG_SPACE(24))
|
||||||
dstip = None
|
dstip = None
|
||||||
family = None
|
family = None
|
||||||
@ -64,7 +64,7 @@ if recvmsg == "python":
|
|||||||
elif recvmsg == "socket_ext":
|
elif recvmsg == "socket_ext":
|
||||||
def recv_udp(listener, bufsize):
|
def recv_udp(listener, bufsize):
|
||||||
debug3('Accept UDP using socket_ext recvmsg.\n')
|
debug3('Accept UDP using socket_ext recvmsg.\n')
|
||||||
srcip, data, adata, flags = listener.recvmsg(
|
srcip, data, adata, _ = listener.recvmsg(
|
||||||
(bufsize,), socket.CMSG_SPACE(24))
|
(bufsize,), socket.CMSG_SPACE(24))
|
||||||
dstip = None
|
dstip = None
|
||||||
family = None
|
family = None
|
||||||
@ -150,7 +150,8 @@ class Method(BaseMethod):
|
|||||||
if udp_listener.v6 is not None:
|
if udp_listener.v6 is not None:
|
||||||
udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
|
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]:
|
if family not in [socket.AF_INET, socket.AF_INET6]:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Address family "%s" unsupported by tproxy method'
|
'Address family "%s" unsupported by tproxy method'
|
||||||
@ -174,7 +175,7 @@ class Method(BaseMethod):
|
|||||||
divert_chain = 'sshuttle-d-%s' % port
|
divert_chain = 'sshuttle-d-%s' % port
|
||||||
|
|
||||||
# basic cleanup/setup of chains
|
# basic cleanup/setup of chains
|
||||||
self.restore_firewall(port, family, udp)
|
self.restore_firewall(port, family, udp, user)
|
||||||
|
|
||||||
_ipt('-N', mark_chain)
|
_ipt('-N', mark_chain)
|
||||||
_ipt('-F', mark_chain)
|
_ipt('-F', mark_chain)
|
||||||
@ -193,7 +194,7 @@ class Method(BaseMethod):
|
|||||||
_ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain,
|
_ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain,
|
||||||
'-m', 'udp', '-p', 'udp')
|
'-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',
|
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
|
||||||
'--dest', '%s/32' % ip,
|
'--dest', '%s/32' % ip,
|
||||||
'-m', 'udp', '-p', 'udp', '--dport', '53')
|
'-m', 'udp', '-p', 'udp', '--dport', '53')
|
||||||
@ -203,7 +204,7 @@ class Method(BaseMethod):
|
|||||||
'-m', 'udp', '-p', 'udp', '--dport', '53',
|
'-m', 'udp', '-p', 'udp', '--dport', '53',
|
||||||
'--on-port', str(dnsport))
|
'--on-port', str(dnsport))
|
||||||
|
|
||||||
for f, swidth, sexclude, snet, fport, lport \
|
for _, swidth, sexclude, snet, fport, lport \
|
||||||
in sorted(subnets, key=subnet_weight, reverse=True):
|
in sorted(subnets, key=subnet_weight, reverse=True):
|
||||||
tcp_ports = ('-p', 'tcp')
|
tcp_ports = ('-p', 'tcp')
|
||||||
tcp_ports = _ipt_proto_ports(tcp_ports, fport, lport)
|
tcp_ports = _ipt_proto_ports(tcp_ports, fport, lport)
|
||||||
@ -251,7 +252,7 @@ class Method(BaseMethod):
|
|||||||
'-m', 'udp',
|
'-m', 'udp',
|
||||||
*(udp_ports + ('--on-port', str(port))))
|
*(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]:
|
if family not in [socket.AF_INET, socket.AF_INET6]:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Address family "%s" unsupported by tproxy method'
|
'Address family "%s" unsupported by tproxy method'
|
||||||
|
@ -13,9 +13,9 @@ def parse_subnetport_file(s):
|
|||||||
|
|
||||||
raw_config_lines = handle.readlines()
|
raw_config_lines = handle.readlines()
|
||||||
subnets = []
|
subnets = []
|
||||||
for line_no, line in enumerate(raw_config_lines):
|
for _, line in enumerate(raw_config_lines):
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if len(line) == 0:
|
if not line:
|
||||||
continue
|
continue
|
||||||
if line[0] == '#':
|
if line[0] == '#':
|
||||||
continue
|
continue
|
||||||
@ -81,8 +81,8 @@ def parse_ipport(s):
|
|||||||
return (family,) + addr[:2]
|
return (family,) + addr[:2]
|
||||||
|
|
||||||
|
|
||||||
def parse_list(list):
|
def parse_list(lst):
|
||||||
return re.split(r'[\s,]+', list.strip()) if list else []
|
return re.split(r'[\s,]+', lst.strip()) if lst else []
|
||||||
|
|
||||||
|
|
||||||
class Concat(Action):
|
class Concat(Action):
|
||||||
@ -98,7 +98,8 @@ class Concat(Action):
|
|||||||
|
|
||||||
parser = ArgumentParser(
|
parser = ArgumentParser(
|
||||||
prog="sshuttle",
|
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(
|
parser.add_argument(
|
||||||
"subnets",
|
"subnets",
|
||||||
@ -120,7 +121,8 @@ parser.add_argument(
|
|||||||
"-H", "--auto-hosts",
|
"-H", "--auto-hosts",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="""
|
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(
|
parser.add_argument(
|
||||||
@ -146,9 +148,19 @@ parser.add_argument(
|
|||||||
capture and forward DNS requests made to the following servers
|
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(
|
parser.add_argument(
|
||||||
"--method",
|
"--method",
|
||||||
choices=["auto", "nat", "tproxy", "pf", "ipfw"],
|
choices=["auto", "nat", "nft", "tproxy", "pf", "ipfw"],
|
||||||
metavar="TYPE",
|
metavar="TYPE",
|
||||||
default="auto",
|
default="auto",
|
||||||
help="""
|
help="""
|
||||||
@ -218,7 +230,8 @@ parser.add_argument(
|
|||||||
metavar="HOSTNAME[,HOSTNAME]",
|
metavar="HOSTNAME[,HOSTNAME]",
|
||||||
default=[],
|
default=[],
|
||||||
help="""
|
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(
|
parser.add_argument(
|
||||||
@ -277,6 +290,12 @@ parser.add_argument(
|
|||||||
pidfile name (only if using --daemon) [%(default)s]
|
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(
|
parser.add_argument(
|
||||||
"--firewall",
|
"--firewall",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@ -291,3 +310,11 @@ parser.add_argument(
|
|||||||
(internal use only)
|
(internal use only)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-sudo-pythonpath",
|
||||||
|
action="store_false",
|
||||||
|
dest="sudo_pythonpath",
|
||||||
|
help="""
|
||||||
|
do not set PYTHONPATH when invoking sudo
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
@ -9,14 +9,14 @@ def _notify(message):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
addr = '\0' + addr[1:] if addr[0] == '@' else addr
|
addr = '\0' + addr[1:] if addr[0] == '@' else addr
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
||||||
except (OSError, IOError) as e:
|
except (OSError, IOError) as e:
|
||||||
debug1("Error creating socket to notify systemd: %s\n" % e)
|
debug1("Error creating socket to notify systemd: %s\n" % e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not message:
|
if not message:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
assert isinstance(message, bytes)
|
assert isinstance(message, bytes)
|
||||||
|
@ -160,7 +160,7 @@ class Hostwatch:
|
|||||||
|
|
||||||
class DnsProxy(Handler):
|
class DnsProxy(Handler):
|
||||||
|
|
||||||
def __init__(self, mux, chan, request):
|
def __init__(self, mux, chan, request, to_nameserver):
|
||||||
Handler.__init__(self, [])
|
Handler.__init__(self, [])
|
||||||
self.timeout = time.time() + 30
|
self.timeout = time.time() + 30
|
||||||
self.mux = mux
|
self.mux = mux
|
||||||
@ -168,22 +168,43 @@ class DnsProxy(Handler):
|
|||||||
self.tries = 0
|
self.tries = 0
|
||||||
self.request = request
|
self.request = request
|
||||||
self.peers = {}
|
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()
|
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):
|
def try_send(self):
|
||||||
if self.tries >= 3:
|
if self.tries >= 3:
|
||||||
return
|
return
|
||||||
self.tries += 1
|
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 = socket.socket(family, socket.SOCK_DGRAM)
|
||||||
sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
|
sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
|
||||||
sock.connect((peer, 53))
|
sock.connect(sockaddr)
|
||||||
|
|
||||||
self.peers[sock] = peer
|
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:
|
try:
|
||||||
sock.send(self.request)
|
sock.send(self.request)
|
||||||
self.socks.append(sock)
|
self.socks.append(sock)
|
||||||
@ -258,7 +279,7 @@ class UdpProxy(Handler):
|
|||||||
self.mux.send(self.chan, ssnet.CMD_UDP_DATA, hdr + data)
|
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, auto_nets):
|
||||||
debug1('Starting server with Python version %s\n'
|
debug1('Starting server with Python version %s\n'
|
||||||
% platform.python_version())
|
% platform.python_version())
|
||||||
|
|
||||||
@ -268,10 +289,6 @@ def main(latency_control, auto_hosts):
|
|||||||
helpers.logprefix = 'server: '
|
helpers.logprefix = 'server: '
|
||||||
debug1('latency control setting = %r\n' % latency_control)
|
debug1('latency control setting = %r\n' % latency_control)
|
||||||
|
|
||||||
routes = list(list_routes())
|
|
||||||
debug1('available routes:\n')
|
|
||||||
for r in routes:
|
|
||||||
debug1(' %d/%s/%d\n' % r)
|
|
||||||
|
|
||||||
# synchronization header
|
# synchronization header
|
||||||
sys.stdout.write('\0\0SSHUTTLE0001')
|
sys.stdout.write('\0\0SSHUTTLE0001')
|
||||||
@ -283,6 +300,16 @@ def main(latency_control, auto_hosts):
|
|||||||
socket.fromfd(sys.stdout.fileno(),
|
socket.fromfd(sys.stdout.fileno(),
|
||||||
socket.AF_INET, socket.SOCK_STREAM))
|
socket.AF_INET, socket.SOCK_STREAM))
|
||||||
handlers.append(mux)
|
handlers.append(mux)
|
||||||
|
|
||||||
|
debug1('auto-nets:' + str(auto_nets) + '\n')
|
||||||
|
if auto_nets:
|
||||||
|
routes = list(list_routes())
|
||||||
|
debug1('available routes:\n')
|
||||||
|
for r in routes:
|
||||||
|
debug1(' %d/%s/%d\n' % r)
|
||||||
|
else:
|
||||||
|
routes = []
|
||||||
|
|
||||||
routepkt = ''
|
routepkt = ''
|
||||||
for r in routes:
|
for r in routes:
|
||||||
routepkt += '%d,%s,%d\n' % r
|
routepkt += '%d,%s,%d\n' % r
|
||||||
@ -309,7 +336,7 @@ def main(latency_control, auto_hosts):
|
|||||||
def got_host_req(data):
|
def got_host_req(data):
|
||||||
if not hw.pid:
|
if not hw.pid:
|
||||||
(hw.pid, hw.sock) = start_hostwatch(
|
(hw.pid, hw.sock) = start_hostwatch(
|
||||||
data.strip().split(), auto_hosts)
|
data.decode("ASCII").strip().split(), auto_hosts)
|
||||||
handlers.append(Handler(socks=[hw.sock],
|
handlers.append(Handler(socks=[hw.sock],
|
||||||
callback=hostwatch_ready))
|
callback=hostwatch_ready))
|
||||||
mux.got_host_req = got_host_req
|
mux.got_host_req = got_host_req
|
||||||
@ -332,7 +359,7 @@ def main(latency_control, auto_hosts):
|
|||||||
|
|
||||||
def dns_req(channel, data):
|
def dns_req(channel, data):
|
||||||
debug2('Incoming DNS request channel=%d.\n' % channel)
|
debug2('Incoming DNS request channel=%d.\n' % channel)
|
||||||
h = DnsProxy(mux, channel, data)
|
h = DnsProxy(mux, channel, data, to_nameserver)
|
||||||
handlers.append(h)
|
handlers.append(h)
|
||||||
dnshandlers[channel] = h
|
dnshandlers[channel] = h
|
||||||
mux.got_dns_req = dns_req
|
mux.got_dns_req = dns_req
|
||||||
|
@ -69,7 +69,7 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
|
|||||||
rhost = result[0].strip('[')
|
rhost = result[0].strip('[')
|
||||||
if len(result) > 1:
|
if len(result) > 1:
|
||||||
result[1] = result[1].strip(':')
|
result[1] = result[1].strip(':')
|
||||||
if result[1] is not '':
|
if result[1] != '':
|
||||||
portl = ['-p', str(int(result[1]))]
|
portl = ['-p', str(int(result[1]))]
|
||||||
# can't disambiguate IPv6 colons and a port number. pass the hostname
|
# can't disambiguate IPv6 colons and a port number. pass the hostname
|
||||||
# through.
|
# through.
|
||||||
@ -116,8 +116,8 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
|
|||||||
if python:
|
if python:
|
||||||
pycmd = "'%s' -c '%s'" % (python, pyscript)
|
pycmd = "'%s' -c '%s'" % (python, pyscript)
|
||||||
else:
|
else:
|
||||||
pycmd = ("P=python3.5; $P -V 2>/dev/null || P=python; "
|
pycmd = ("P=python3; $P -V 2>%s || P=python; "
|
||||||
"exec \"$P\" -c %s") % quote(pyscript)
|
"exec \"$P\" -c %s") % (os.devnull, quote(pyscript))
|
||||||
pycmd = ("exec /bin/sh -c %s" % quote(pycmd))
|
pycmd = ("exec /bin/sh -c %s" % quote(pycmd))
|
||||||
argv = (sshl +
|
argv = (sshl +
|
||||||
portl +
|
portl +
|
||||||
|
@ -95,6 +95,8 @@ def _try_peername(sock):
|
|||||||
_, e = sys.exc_info()[:2]
|
_, e = sys.exc_info()[:2]
|
||||||
if e.args[0] not in (errno.ENOTCONN, errno.ENOTSOCK):
|
if e.args[0] not in (errno.ENOTCONN, errno.ENOTSOCK):
|
||||||
raise
|
raise
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
return 'unknown'
|
return 'unknown'
|
||||||
|
|
||||||
|
|
||||||
@ -200,7 +202,8 @@ class SockWrapper:
|
|||||||
_, e = sys.exc_info()[:2]
|
_, e = sys.exc_info()[:2]
|
||||||
self.seterr('nowrite: %s' % e)
|
self.seterr('nowrite: %s' % e)
|
||||||
|
|
||||||
def too_full(self):
|
@staticmethod
|
||||||
|
def too_full():
|
||||||
return False # fullness is determined by the socket's select() state
|
return False # fullness is determined by the socket's select() state
|
||||||
|
|
||||||
def uwrite(self, buf):
|
def uwrite(self, buf):
|
||||||
@ -270,7 +273,7 @@ class Handler:
|
|||||||
|
|
||||||
def callback(self, sock):
|
def callback(self, sock):
|
||||||
log('--no callback defined-- %r\n' % self)
|
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:
|
for s in r:
|
||||||
v = s.recv(4096)
|
v = s.recv(4096)
|
||||||
if not v:
|
if not v:
|
||||||
@ -349,7 +352,7 @@ class Mux(Handler):
|
|||||||
|
|
||||||
def next_channel(self):
|
def next_channel(self):
|
||||||
# channel 0 is special, so we never allocate it
|
# channel 0 is special, so we never allocate it
|
||||||
for timeout in range(1024):
|
for _ in range(1024):
|
||||||
self.chani += 1
|
self.chani += 1
|
||||||
if self.chani > MAX_CHANNEL:
|
if self.chani > MAX_CHANNEL:
|
||||||
self.chani = 1
|
self.chani = 1
|
||||||
@ -478,7 +481,7 @@ class Mux(Handler):
|
|||||||
_add(w, self.wsock)
|
_add(w, self.wsock)
|
||||||
|
|
||||||
def callback(self, sock):
|
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:
|
if self.rsock in r:
|
||||||
self.handle()
|
self.handle()
|
||||||
if self.outbuf and self.wsock in w:
|
if self.outbuf and self.wsock in w:
|
||||||
|
@ -8,12 +8,24 @@ _p = None
|
|||||||
|
|
||||||
def start_syslog():
|
def start_syslog():
|
||||||
global _p
|
global _p
|
||||||
_p = ssubprocess.Popen(['logger',
|
with open(os.devnull, 'w') as devnull:
|
||||||
'-p', 'daemon.notice',
|
_p = ssubprocess.Popen(
|
||||||
'-t', 'sshuttle'], stdin=ssubprocess.PIPE)
|
['logger', '-p', 'daemon.notice', '-t', 'sshuttle'],
|
||||||
|
stdin=ssubprocess.PIPE,
|
||||||
|
stdout=devnull,
|
||||||
|
stderr=devnull
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def close_stdin():
|
||||||
|
sys.stdin.close()
|
||||||
|
|
||||||
|
|
||||||
|
def stdout_to_syslog():
|
||||||
|
sys.stdout.flush()
|
||||||
|
os.dup2(_p.stdin.fileno(), sys.stdout.fileno())
|
||||||
|
|
||||||
|
|
||||||
def stderr_to_syslog():
|
def stderr_to_syslog():
|
||||||
sys.stdout.flush()
|
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
os.dup2(_p.stdin.fileno(), 2)
|
os.dup2(_p.stdin.fileno(), sys.stderr.fileno())
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
from mock import Mock, patch, call
|
from mock import Mock, patch, call
|
||||||
import io
|
import io
|
||||||
import socket
|
from socket import AF_INET, AF_INET6
|
||||||
|
|
||||||
import sshuttle.firewall
|
import sshuttle.firewall
|
||||||
|
|
||||||
|
|
||||||
def setup_daemon():
|
def setup_daemon():
|
||||||
stdin = io.StringIO(u"""ROUTES
|
stdin = io.StringIO(u"""ROUTES
|
||||||
2,24,0,1.2.3.0,8000,9000
|
{inet},24,0,1.2.3.0,8000,9000
|
||||||
2,32,1,1.2.3.66,8080,8080
|
{inet},32,1,1.2.3.66,8080,8080
|
||||||
10,64,0,2404:6800:4004:80c::,0,0
|
{inet6},64,0,2404:6800:4004:80c::,0,0
|
||||||
10,128,1,2404:6800:4004:80c::101f,80,80
|
{inet6},128,1,2404:6800:4004:80c::101f,80,80
|
||||||
NSLIST
|
NSLIST
|
||||||
2,1.2.3.33
|
{inet},1.2.3.33
|
||||||
10,2404:6800:4004:80c::33
|
{inet6},2404:6800:4004:80c::33
|
||||||
PORTS 1024,1025,1026,1027
|
PORTS 1024,1025,1026,1027
|
||||||
GO 1
|
GO 1 -
|
||||||
HOST 1.2.3.3,existing
|
HOST 1.2.3.3,existing
|
||||||
""")
|
""".format(inet=AF_INET, inet6=AF_INET6))
|
||||||
stdout = Mock()
|
stdout = Mock()
|
||||||
return stdin, stdout
|
return stdin, stdout
|
||||||
|
|
||||||
@ -61,30 +61,30 @@ def test_rewrite_etc_hosts(tmpdir):
|
|||||||
|
|
||||||
def test_subnet_weight():
|
def test_subnet_weight():
|
||||||
subnets = [
|
subnets = [
|
||||||
(socket.AF_INET, 16, 0, '192.168.0.0', 0, 0),
|
(AF_INET, 16, 0, '192.168.0.0', 0, 0),
|
||||||
(socket.AF_INET, 24, 0, '192.168.69.0', 0, 0),
|
(AF_INET, 24, 0, '192.168.69.0', 0, 0),
|
||||||
(socket.AF_INET, 32, 0, '192.168.69.70', 0, 0),
|
(AF_INET, 32, 0, '192.168.69.70', 0, 0),
|
||||||
(socket.AF_INET, 32, 1, '192.168.69.70', 0, 0),
|
(AF_INET, 32, 1, '192.168.69.70', 0, 0),
|
||||||
(socket.AF_INET, 32, 1, '192.168.69.70', 80, 80),
|
(AF_INET, 32, 1, '192.168.69.70', 80, 80),
|
||||||
(socket.AF_INET, 0, 1, '0.0.0.0', 0, 0),
|
(AF_INET, 0, 1, '0.0.0.0', 0, 0),
|
||||||
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 9000),
|
(AF_INET, 0, 1, '0.0.0.0', 8000, 9000),
|
||||||
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 8500),
|
(AF_INET, 0, 1, '0.0.0.0', 8000, 8500),
|
||||||
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 8000),
|
(AF_INET, 0, 1, '0.0.0.0', 8000, 8000),
|
||||||
(socket.AF_INET, 0, 1, '0.0.0.0', 400, 450)
|
(AF_INET, 0, 1, '0.0.0.0', 400, 450)
|
||||||
]
|
]
|
||||||
subnets_sorted = [
|
subnets_sorted = [
|
||||||
(socket.AF_INET, 32, 1, '192.168.69.70', 80, 80),
|
(AF_INET, 32, 1, '192.168.69.70', 80, 80),
|
||||||
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 8000),
|
(AF_INET, 0, 1, '0.0.0.0', 8000, 8000),
|
||||||
(socket.AF_INET, 0, 1, '0.0.0.0', 400, 450),
|
(AF_INET, 0, 1, '0.0.0.0', 400, 450),
|
||||||
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 8500),
|
(AF_INET, 0, 1, '0.0.0.0', 8000, 8500),
|
||||||
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 9000),
|
(AF_INET, 0, 1, '0.0.0.0', 8000, 9000),
|
||||||
(socket.AF_INET, 32, 1, '192.168.69.70', 0, 0),
|
(AF_INET, 32, 1, '192.168.69.70', 0, 0),
|
||||||
(socket.AF_INET, 32, 0, '192.168.69.70', 0, 0),
|
(AF_INET, 32, 0, '192.168.69.70', 0, 0),
|
||||||
(socket.AF_INET, 24, 0, '192.168.69.0', 0, 0),
|
(AF_INET, 24, 0, '192.168.69.0', 0, 0),
|
||||||
(socket.AF_INET, 16, 0, '192.168.0.0', 0, 0),
|
(AF_INET, 16, 0, '192.168.0.0', 0, 0),
|
||||||
(socket.AF_INET, 0, 1, '0.0.0.0', 0, 0)
|
(AF_INET, 0, 1, '0.0.0.0', 0, 0)
|
||||||
]
|
]
|
||||||
|
|
||||||
assert subnets_sorted == \
|
assert subnets_sorted == \
|
||||||
sorted(subnets, key=sshuttle.firewall.subnet_weight, reverse=True)
|
sorted(subnets, key=sshuttle.firewall.subnet_weight, reverse=True)
|
||||||
|
|
||||||
@ -117,18 +117,20 @@ def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts):
|
|||||||
call('not_auto'),
|
call('not_auto'),
|
||||||
call().setup_firewall(
|
call().setup_firewall(
|
||||||
1024, 1026,
|
1024, 1026,
|
||||||
[(10, u'2404:6800:4004:80c::33')],
|
[(AF_INET6, u'2404:6800:4004:80c::33')],
|
||||||
10,
|
AF_INET6,
|
||||||
[(10, 64, False, u'2404:6800:4004:80c::', 0, 0),
|
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 0, 0),
|
||||||
(10, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
|
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
|
||||||
True),
|
True,
|
||||||
|
None),
|
||||||
call().setup_firewall(
|
call().setup_firewall(
|
||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(AF_INET, u'1.2.3.33')],
|
||||||
2,
|
AF_INET,
|
||||||
[(2, 24, False, u'1.2.3.0', 8000, 9000),
|
[(AF_INET, 24, False, u'1.2.3.0', 8000, 9000),
|
||||||
(2, 32, True, u'1.2.3.66', 8080, 8080)],
|
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
|
||||||
True),
|
True,
|
||||||
call().restore_firewall(1024, 10, True),
|
None),
|
||||||
call().restore_firewall(1025, 2, True),
|
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 sys
|
||||||
import io
|
import io
|
||||||
import socket
|
import socket
|
||||||
|
from socket import AF_INET, AF_INET6
|
||||||
|
import errno
|
||||||
|
|
||||||
import sshuttle.helpers
|
import sshuttle.helpers
|
||||||
|
|
||||||
@ -132,10 +134,12 @@ nameserver 2404:6800:4004:80c::4
|
|||||||
|
|
||||||
ns = sshuttle.helpers.resolvconf_nameservers()
|
ns = sshuttle.helpers.resolvconf_nameservers()
|
||||||
assert ns == [
|
assert ns == [
|
||||||
(2, u'192.168.1.1'), (2, u'192.168.2.1'),
|
(AF_INET, u'192.168.1.1'), (AF_INET, u'192.168.2.1'),
|
||||||
(2, u'192.168.3.1'), (2, u'192.168.4.1'),
|
(AF_INET, u'192.168.3.1'), (AF_INET, u'192.168.4.1'),
|
||||||
(10, u'2404:6800:4004:80c::1'), (10, u'2404:6800:4004:80c::2'),
|
(AF_INET6, u'2404:6800:4004:80c::1'),
|
||||||
(10, u'2404:6800:4004:80c::3'), (10, u'2404:6800:4004:80c::4')
|
(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()
|
ns = sshuttle.helpers.resolvconf_random_nameserver()
|
||||||
assert ns in [
|
assert ns in [
|
||||||
(2, u'192.168.1.1'), (2, u'192.168.2.1'),
|
(AF_INET, u'192.168.1.1'), (AF_INET, u'192.168.2.1'),
|
||||||
(2, u'192.168.3.1'), (2, u'192.168.4.1'),
|
(AF_INET, u'192.168.3.1'), (AF_INET, u'192.168.4.1'),
|
||||||
(10, u'2404:6800:4004:80c::1'), (10, u'2404:6800:4004:80c::2'),
|
(AF_INET6, u'2404:6800:4004:80c::1'),
|
||||||
(10, u'2404:6800:4004:80c::3'), (10, u'2404:6800:4004:80c::4')
|
(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():
|
@patch('sshuttle.helpers.socket.socket.bind')
|
||||||
assert sshuttle.helpers.islocal("127.0.0.1", socket.AF_INET)
|
def test_islocal(mock_bind):
|
||||||
assert not sshuttle.helpers.islocal("192.0.2.1", socket.AF_INET)
|
bind_error = socket.error(errno.EADDRNOTAVAIL)
|
||||||
assert sshuttle.helpers.islocal("::1", socket.AF_INET6)
|
mock_bind.side_effect = [None, bind_error, None, bind_error]
|
||||||
assert not sshuttle.helpers.islocal("2001:db8::1", socket.AF_INET6)
|
|
||||||
|
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():
|
def test_family_ip_tuple():
|
||||||
assert sshuttle.helpers.family_ip_tuple("127.0.0.1") \
|
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") \
|
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") \
|
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") \
|
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():
|
def test_family_to_string():
|
||||||
assert sshuttle.helpers.family_to_string(socket.AF_INET) == "AF_INET"
|
assert sshuttle.helpers.family_to_string(AF_INET) == "AF_INET"
|
||||||
assert sshuttle.helpers.family_to_string(socket.AF_INET6) == "AF_INET6"
|
assert sshuttle.helpers.family_to_string(AF_INET6) == "AF_INET6"
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
expected = "1"
|
expected = "1"
|
||||||
assert sshuttle.helpers.family_to_string(socket.AF_UNIX) == "1"
|
assert sshuttle.helpers.family_to_string(socket.AF_UNIX) == "1"
|
@ -1,6 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from mock import Mock, patch, call
|
from mock import Mock, patch, call
|
||||||
import socket
|
import socket
|
||||||
|
from socket import AF_INET, AF_INET6
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from sshuttle.helpers import Fatal
|
from sshuttle.helpers import Fatal
|
||||||
@ -18,7 +19,7 @@ def test_get_supported_features():
|
|||||||
def test_get_tcp_dstip():
|
def test_get_tcp_dstip():
|
||||||
sock = Mock()
|
sock = Mock()
|
||||||
sock.getsockopt.return_value = struct.pack(
|
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')
|
method = get_method('nat')
|
||||||
assert method.get_tcp_dstip(sock) == ('127.0.0.1', 1024)
|
assert method.get_tcp_dstip(sock) == ('127.0.0.1', 1024)
|
||||||
assert sock.mock_calls == [call.getsockopt(0, 80, 16)]
|
assert sock.mock_calls == [call.getsockopt(0, 80, 16)]
|
||||||
@ -84,11 +85,12 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
|||||||
with pytest.raises(Exception) as excinfo:
|
with pytest.raises(Exception) as excinfo:
|
||||||
method.setup_firewall(
|
method.setup_firewall(
|
||||||
1024, 1026,
|
1024, 1026,
|
||||||
[(10, u'2404:6800:4004:80c::33')],
|
[(AF_INET6, u'2404:6800:4004:80c::33')],
|
||||||
10,
|
AF_INET6,
|
||||||
[(10, 64, False, u'2404:6800:4004:80c::', 0, 0),
|
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 0, 0),
|
||||||
(10, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
|
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
|
||||||
True)
|
True,
|
||||||
|
None)
|
||||||
assert str(excinfo.value) \
|
assert str(excinfo.value) \
|
||||||
== 'Address family "AF_INET6" unsupported by nat method_name'
|
== 'Address family "AF_INET6" unsupported by nat method_name'
|
||||||
assert mock_ipt_chain_exists.mock_calls == []
|
assert mock_ipt_chain_exists.mock_calls == []
|
||||||
@ -98,11 +100,12 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
|||||||
with pytest.raises(Exception) as excinfo:
|
with pytest.raises(Exception) as excinfo:
|
||||||
method.setup_firewall(
|
method.setup_firewall(
|
||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(AF_INET, u'1.2.3.33')],
|
||||||
2,
|
AF_INET,
|
||||||
[(2, 24, False, u'1.2.3.0', 8000, 9000),
|
[(AF_INET, 24, False, u'1.2.3.0', 8000, 9000),
|
||||||
(2, 32, True, u'1.2.3.66', 8080, 8080)],
|
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
|
||||||
True)
|
True,
|
||||||
|
None)
|
||||||
assert str(excinfo.value) == 'UDP not supported by nat method_name'
|
assert str(excinfo.value) == 'UDP not supported by nat method_name'
|
||||||
assert mock_ipt_chain_exists.mock_calls == []
|
assert mock_ipt_chain_exists.mock_calls == []
|
||||||
assert mock_ipt_ttl.mock_calls == []
|
assert mock_ipt_ttl.mock_calls == []
|
||||||
@ -110,48 +113,49 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
|||||||
|
|
||||||
method.setup_firewall(
|
method.setup_firewall(
|
||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(AF_INET, u'1.2.3.33')],
|
||||||
2,
|
AF_INET,
|
||||||
[(2, 24, False, u'1.2.3.0', 8000, 9000),
|
[(AF_INET, 24, False, u'1.2.3.0', 8000, 9000),
|
||||||
(2, 32, True, u'1.2.3.66', 8080, 8080)],
|
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
|
||||||
False)
|
False,
|
||||||
|
None)
|
||||||
assert mock_ipt_chain_exists.mock_calls == [
|
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_ttl.mock_calls == [
|
||||||
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',
|
'--dest', u'1.2.3.0/24', '-p', 'tcp', '--dport', '8000:9000',
|
||||||
'--to-ports', '1025'),
|
'--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.33/32', '-p', 'udp',
|
'--dest', u'1.2.3.33/32', '-p', 'udp',
|
||||||
'--dport', '53', '--to-ports', '1027')
|
'--dport', '53', '--to-ports', '1027')
|
||||||
]
|
]
|
||||||
assert mock_ipt.mock_calls == [
|
assert mock_ipt.mock_calls == [
|
||||||
call(2, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
|
call(AF_INET, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
|
||||||
call(2, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'),
|
call(AF_INET, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'),
|
||||||
call(2, 'nat', '-F', 'sshuttle-1025'),
|
call(AF_INET, 'nat', '-F', 'sshuttle-1025'),
|
||||||
call(2, 'nat', '-X', 'sshuttle-1025'),
|
call(AF_INET, 'nat', '-X', 'sshuttle-1025'),
|
||||||
call(2, 'nat', '-N', 'sshuttle-1025'),
|
call(AF_INET, 'nat', '-N', 'sshuttle-1025'),
|
||||||
call(2, 'nat', '-F', 'sshuttle-1025'),
|
call(AF_INET, 'nat', '-F', 'sshuttle-1025'),
|
||||||
call(2, 'nat', '-I', 'OUTPUT', '1', '-j', 'sshuttle-1025'),
|
call(AF_INET, 'nat', '-I', 'OUTPUT', '1', '-j', 'sshuttle-1025'),
|
||||||
call(2, 'nat', '-I', 'PREROUTING', '1', '-j', 'sshuttle-1025'),
|
call(AF_INET, 'nat', '-I', 'PREROUTING', '1', '-j', 'sshuttle-1025'),
|
||||||
call(2, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
|
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
|
||||||
'--dest', u'1.2.3.66/32', '-p', 'tcp', '--dport', '8080:8080')
|
'--dest', u'1.2.3.66/32', '-p', 'tcp', '--dport', '8080:8080')
|
||||||
]
|
]
|
||||||
mock_ipt_chain_exists.reset_mock()
|
mock_ipt_chain_exists.reset_mock()
|
||||||
mock_ipt_ttl.reset_mock()
|
mock_ipt_ttl.reset_mock()
|
||||||
mock_ipt.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 == [
|
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_ttl.mock_calls == []
|
||||||
assert mock_ipt.mock_calls == [
|
assert mock_ipt.mock_calls == [
|
||||||
call(2, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
|
call(AF_INET, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
|
||||||
call(2, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'),
|
call(AF_INET, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'),
|
||||||
call(2, 'nat', '-F', 'sshuttle-1025'),
|
call(AF_INET, 'nat', '-F', 'sshuttle-1025'),
|
||||||
call(2, 'nat', '-X', 'sshuttle-1025')
|
call(AF_INET, 'nat', '-X', 'sshuttle-1025')
|
||||||
]
|
]
|
||||||
mock_ipt_chain_exists.reset_mock()
|
mock_ipt_chain_exists.reset_mock()
|
||||||
mock_ipt_ttl.reset_mock()
|
mock_ipt_ttl.reset_mock()
|
@ -1,6 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from mock import Mock, patch, call, ANY
|
from mock import Mock, patch, call, ANY
|
||||||
import socket
|
import socket
|
||||||
|
from socket import AF_INET, AF_INET6
|
||||||
|
|
||||||
from sshuttle.methods import get_method
|
from sshuttle.methods import get_method
|
||||||
from sshuttle.helpers import Fatal
|
from sshuttle.helpers import Fatal
|
||||||
@ -20,7 +21,7 @@ def test_get_tcp_dstip():
|
|||||||
sock = Mock()
|
sock = Mock()
|
||||||
sock.getpeername.return_value = ("127.0.0.1", 1024)
|
sock.getpeername.return_value = ("127.0.0.1", 1024)
|
||||||
sock.getsockname.return_value = ("127.0.0.2", 1025)
|
sock.getsockname.return_value = ("127.0.0.2", 1025)
|
||||||
sock.family = socket.AF_INET
|
sock.family = AF_INET
|
||||||
|
|
||||||
firewall = Mock()
|
firewall = Mock()
|
||||||
firewall.pfile.readline.return_value = \
|
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")
|
assert not method.firewall_command("somthing")
|
||||||
|
|
||||||
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
|
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)
|
"127.0.0.1", 1025, "127.0.0.2", 1024)
|
||||||
assert method.firewall_command(command)
|
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")
|
assert not method.firewall_command("somthing")
|
||||||
|
|
||||||
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
|
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)
|
"127.0.0.1", 1025, "127.0.0.2", 1024)
|
||||||
assert method.firewall_command(command)
|
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")
|
assert not method.firewall_command("somthing")
|
||||||
|
|
||||||
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
|
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)
|
"127.0.0.1", 1025, "127.0.0.2", 1024)
|
||||||
assert method.firewall_command(command)
|
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(
|
method.setup_firewall(
|
||||||
1024, 1026,
|
1024, 1026,
|
||||||
[(10, u'2404:6800:4004:80c::33')],
|
[(AF_INET6, u'2404:6800:4004:80c::33')],
|
||||||
10,
|
AF_INET6,
|
||||||
[(10, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
|
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
|
||||||
(10, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||||
False)
|
False,
|
||||||
|
None)
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||||
call(mock_pf_get_dev(), 0xCC20441A, ANY),
|
call(mock_pf_get_dev(), 0xCC20441A, ANY),
|
||||||
@ -199,14 +201,14 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
call('-s all'),
|
call('-s all'),
|
||||||
call('-a sshuttle6-1024 -f /dev/stdin',
|
call('-a sshuttle6-1024 -f /dev/stdin',
|
||||||
b'table <dns_servers> {2404:6800:4004:80c::33}\n'
|
b'table <dns_servers> {2404:6800:4004:80c::33}\n'
|
||||||
b'rdr pass on lo0 inet6 proto tcp to '
|
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'2404:6800:4004:80c::/64 port 8000:9000 -> ::1 port 1024\n'
|
||||||
b'rdr pass on lo0 inet6 proto udp '
|
b'rdr pass on lo0 inet6 proto udp '
|
||||||
b'to <dns_servers> port 53 -> ::1 port 1026\n'
|
b'to <dns_servers> port 53 -> ::1 port 1026\n'
|
||||||
b'pass out 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'pass out route-to lo0 inet6 proto tcp to '
|
||||||
b'2404:6800:4004:80c::/64 port 8000:9000 keep state\n'
|
b'2404:6800:4004:80c::/64 port 8000:9000 keep state\n'
|
||||||
|
b'pass out inet6 proto tcp to '
|
||||||
|
b'2404:6800:4004:80c::101f/128 port 8080:8080\n'
|
||||||
b'pass out route-to lo0 inet6 proto udp '
|
b'pass out route-to lo0 inet6 proto udp '
|
||||||
b'to <dns_servers> port 53 keep state\n'),
|
b'to <dns_servers> port 53 keep state\n'),
|
||||||
call('-E'),
|
call('-E'),
|
||||||
@ -218,11 +220,12 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
with pytest.raises(Exception) as excinfo:
|
with pytest.raises(Exception) as excinfo:
|
||||||
method.setup_firewall(
|
method.setup_firewall(
|
||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(AF_INET, u'1.2.3.33')],
|
||||||
2,
|
AF_INET,
|
||||||
[(2, 24, False, u'1.2.3.0', 0, 0),
|
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
|
||||||
(2, 32, True, u'1.2.3.66', 80, 80)],
|
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
True)
|
True,
|
||||||
|
None)
|
||||||
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
||||||
assert mock_pf_get_dev.mock_calls == []
|
assert mock_pf_get_dev.mock_calls == []
|
||||||
assert mock_ioctl.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(
|
method.setup_firewall(
|
||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(AF_INET, u'1.2.3.33')],
|
||||||
2,
|
AF_INET,
|
||||||
[(2, 24, False, u'1.2.3.0', 0, 0), (2, 32, True, u'1.2.3.66', 80, 80)],
|
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
|
||||||
False)
|
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
|
False,
|
||||||
|
None)
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||||
call(mock_pf_get_dev(), 0xCC20441A, ANY),
|
call(mock_pf_get_dev(), 0xCC20441A, ANY),
|
||||||
@ -248,12 +253,12 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
call('-s all'),
|
call('-s all'),
|
||||||
call('-a sshuttle-1025 -f /dev/stdin',
|
call('-a sshuttle-1025 -f /dev/stdin',
|
||||||
b'table <dns_servers> {1.2.3.33}\n'
|
b'table <dns_servers> {1.2.3.33}\n'
|
||||||
b'rdr pass on lo0 inet proto tcp to 1.2.3.0/24 '
|
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'-> 127.0.0.1 port 1025\n'
|
||||||
b'rdr pass on lo0 inet proto udp '
|
b'rdr pass on lo0 inet proto udp '
|
||||||
b'to <dns_servers> port 53 -> 127.0.0.1 port 1027\n'
|
b'to <dns_servers> port 53 -> 127.0.0.1 port 1027\n'
|
||||||
b'pass out 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 tcp to 1.2.3.0/24 keep state\n'
|
||||||
|
b'pass out inet proto tcp to 1.2.3.66/32 port 80:80\n'
|
||||||
b'pass out route-to lo0 inet proto udp '
|
b'pass out route-to lo0 inet proto udp '
|
||||||
b'to <dns_servers> port 53 keep state\n'),
|
b'to <dns_servers> port 53 keep state\n'),
|
||||||
call('-E'),
|
call('-E'),
|
||||||
@ -262,7 +267,7 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
mock_ioctl.reset_mock()
|
mock_ioctl.reset_mock()
|
||||||
mock_pfctl.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_ioctl.mock_calls == []
|
||||||
assert mock_pfctl.mock_calls == [
|
assert mock_pfctl.mock_calls == [
|
||||||
call('-a sshuttle-1025 -F all'),
|
call('-a sshuttle-1025 -F all'),
|
||||||
@ -275,10 +280,12 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
|
|
||||||
@patch('sshuttle.helpers.verbose', new=3)
|
@patch('sshuttle.helpers.verbose', new=3)
|
||||||
@patch('sshuttle.methods.pf.pf', FreeBsd())
|
@patch('sshuttle.methods.pf.pf', FreeBsd())
|
||||||
|
@patch('subprocess.call')
|
||||||
@patch('sshuttle.methods.pf.pfctl')
|
@patch('sshuttle.methods.pf.pfctl')
|
||||||
@patch('sshuttle.methods.pf.ioctl')
|
@patch('sshuttle.methods.pf.ioctl')
|
||||||
@patch('sshuttle.methods.pf.pf_get_dev')
|
@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
|
mock_pfctl.side_effect = pfctl
|
||||||
|
|
||||||
method = get_method('pf')
|
method = get_method('pf')
|
||||||
@ -286,28 +293,30 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
|
|
||||||
method.setup_firewall(
|
method.setup_firewall(
|
||||||
1024, 1026,
|
1024, 1026,
|
||||||
[(10, u'2404:6800:4004:80c::33')],
|
[(AF_INET6, u'2404:6800:4004:80c::33')],
|
||||||
10,
|
AF_INET6,
|
||||||
[(10, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
|
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
|
||||||
(10, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||||
False)
|
False,
|
||||||
|
None)
|
||||||
|
|
||||||
assert mock_pfctl.mock_calls == [
|
assert mock_pfctl.mock_calls == [
|
||||||
call('-s all'),
|
call('-s all'),
|
||||||
call('-a sshuttle6-1024 -f /dev/stdin',
|
call('-a sshuttle6-1024 -f /dev/stdin',
|
||||||
b'table <dns_servers> {2404:6800:4004:80c::33}\n'
|
b'table <dns_servers> {2404:6800:4004:80c::33}\n'
|
||||||
b'rdr pass on lo0 inet6 proto tcp to 2404:6800:4004:80c::/64 '
|
b'rdr pass on lo0 inet6 proto tcp from ! ::1 to '
|
||||||
b'port 8000:9000 -> ::1 port 1024\n'
|
b'2404:6800:4004:80c::/64 port 8000:9000 -> ::1 port 1024\n'
|
||||||
b'rdr pass on lo0 inet6 proto udp '
|
b'rdr pass on lo0 inet6 proto udp '
|
||||||
b'to <dns_servers> port 53 -> ::1 port 1026\n'
|
b'to <dns_servers> port 53 -> ::1 port 1026\n'
|
||||||
b'pass out 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'pass out route-to lo0 inet6 proto tcp to '
|
||||||
b'2404:6800:4004:80c::/64 port 8000:9000 keep state\n'
|
b'2404:6800:4004:80c::/64 port 8000:9000 keep state\n'
|
||||||
|
b'pass out inet6 proto tcp to '
|
||||||
|
b'2404:6800:4004:80c::101f/128 port 8080:8080\n'
|
||||||
b'pass out route-to lo0 inet6 proto udp '
|
b'pass out route-to lo0 inet6 proto udp '
|
||||||
b'to <dns_servers> port 53 keep state\n'),
|
b'to <dns_servers> port 53 keep state\n'),
|
||||||
call('-e'),
|
call('-e'),
|
||||||
]
|
]
|
||||||
|
assert call(['kldload', 'pf']) in mock_subprocess_call.mock_calls
|
||||||
mock_pf_get_dev.reset_mock()
|
mock_pf_get_dev.reset_mock()
|
||||||
mock_ioctl.reset_mock()
|
mock_ioctl.reset_mock()
|
||||||
mock_pfctl.reset_mock()
|
mock_pfctl.reset_mock()
|
||||||
@ -315,11 +324,12 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
with pytest.raises(Exception) as excinfo:
|
with pytest.raises(Exception) as excinfo:
|
||||||
method.setup_firewall(
|
method.setup_firewall(
|
||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(AF_INET, u'1.2.3.33')],
|
||||||
2,
|
AF_INET,
|
||||||
[(2, 24, False, u'1.2.3.0', 0, 0),
|
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
|
||||||
(2, 32, True, u'1.2.3.66', 80, 80)],
|
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
True)
|
True,
|
||||||
|
None)
|
||||||
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
||||||
assert mock_pf_get_dev.mock_calls == []
|
assert mock_pf_get_dev.mock_calls == []
|
||||||
assert mock_ioctl.mock_calls == []
|
assert mock_ioctl.mock_calls == []
|
||||||
@ -327,10 +337,12 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
|
|
||||||
method.setup_firewall(
|
method.setup_firewall(
|
||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(AF_INET, u'1.2.3.33')],
|
||||||
2,
|
AF_INET,
|
||||||
[(2, 24, False, u'1.2.3.0', 0, 0), (2, 32, True, u'1.2.3.66', 80, 80)],
|
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
|
||||||
False)
|
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
|
False,
|
||||||
|
None)
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||||
call(mock_pf_get_dev(), 0xCBE0441A, ANY),
|
call(mock_pf_get_dev(), 0xCBE0441A, ANY),
|
||||||
@ -343,12 +355,12 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
call('-s all'),
|
call('-s all'),
|
||||||
call('-a sshuttle-1025 -f /dev/stdin',
|
call('-a sshuttle-1025 -f /dev/stdin',
|
||||||
b'table <dns_servers> {1.2.3.33}\n'
|
b'table <dns_servers> {1.2.3.33}\n'
|
||||||
b'rdr pass on lo0 inet proto tcp to 1.2.3.0/24 -> '
|
b'rdr pass on lo0 inet proto tcp from ! 127.0.0.1 '
|
||||||
b'127.0.0.1 port 1025\n'
|
b'to 1.2.3.0/24 -> 127.0.0.1 port 1025\n'
|
||||||
b'rdr pass on lo0 inet proto udp '
|
b'rdr pass on lo0 inet proto udp '
|
||||||
b'to <dns_servers> port 53 -> 127.0.0.1 port 1027\n'
|
b'to <dns_servers> port 53 -> 127.0.0.1 port 1027\n'
|
||||||
b'pass out 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 tcp to 1.2.3.0/24 keep state\n'
|
||||||
|
b'pass out inet proto tcp to 1.2.3.66/32 port 80:80\n'
|
||||||
b'pass out route-to lo0 inet proto udp '
|
b'pass out route-to lo0 inet proto udp '
|
||||||
b'to <dns_servers> port 53 keep state\n'),
|
b'to <dns_servers> port 53 keep state\n'),
|
||||||
call('-e'),
|
call('-e'),
|
||||||
@ -357,10 +369,12 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
mock_ioctl.reset_mock()
|
mock_ioctl.reset_mock()
|
||||||
mock_pfctl.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_ioctl.mock_calls == []
|
||||||
assert mock_pfctl.mock_calls == [
|
assert mock_pfctl.mock_calls == [
|
||||||
call('-a sshuttle-1025 -F all'),
|
call('-a sshuttle-1025 -F all'),
|
||||||
|
call('-a sshuttle6-1024 -F all'),
|
||||||
call("-d"),
|
call("-d"),
|
||||||
]
|
]
|
||||||
mock_pf_get_dev.reset_mock()
|
mock_pf_get_dev.reset_mock()
|
||||||
@ -381,15 +395,16 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
|
|
||||||
method.setup_firewall(
|
method.setup_firewall(
|
||||||
1024, 1026,
|
1024, 1026,
|
||||||
[(10, u'2404:6800:4004:80c::33')],
|
[(AF_INET6, u'2404:6800:4004:80c::33')],
|
||||||
10,
|
AF_INET6,
|
||||||
[(10, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
|
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
|
||||||
(10, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||||
False)
|
False,
|
||||||
|
None)
|
||||||
|
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
call(mock_pf_get_dev(), 0xcd48441a, ANY),
|
call(mock_pf_get_dev(), 0xcd58441a, ANY),
|
||||||
call(mock_pf_get_dev(), 0xcd48441a, ANY),
|
call(mock_pf_get_dev(), 0xcd58441a, ANY),
|
||||||
]
|
]
|
||||||
assert mock_pfctl.mock_calls == [
|
assert mock_pfctl.mock_calls == [
|
||||||
call('-s Interfaces -i lo -v'),
|
call('-s Interfaces -i lo -v'),
|
||||||
@ -401,10 +416,10 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
b'port 8000:9000 divert-to ::1 port 1024\n'
|
b'port 8000:9000 divert-to ::1 port 1024\n'
|
||||||
b'pass in on lo0 inet6 proto udp '
|
b'pass in on lo0 inet6 proto udp '
|
||||||
b'to <dns_servers> port 53 rdr-to ::1 port 1026\n'
|
b'to <dns_servers> port 53 rdr-to ::1 port 1026\n'
|
||||||
b'pass out 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'pass out inet6 proto tcp to 2404:6800:4004:80c::/64 '
|
||||||
b'port 8000:9000 route-to lo0 keep state\n'
|
b'port 8000:9000 route-to lo0 keep state\n'
|
||||||
|
b'pass out inet6 proto tcp to '
|
||||||
|
b'2404:6800:4004:80c::101f/128 port 8080:8080\n'
|
||||||
b'pass out inet6 proto udp to '
|
b'pass out inet6 proto udp to '
|
||||||
b'<dns_servers> port 53 route-to lo0 keep state\n'),
|
b'<dns_servers> port 53 route-to lo0 keep state\n'),
|
||||||
call('-e'),
|
call('-e'),
|
||||||
@ -412,15 +427,16 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
mock_pf_get_dev.reset_mock()
|
mock_pf_get_dev.reset_mock()
|
||||||
mock_ioctl.reset_mock()
|
mock_ioctl.reset_mock()
|
||||||
mock_pfctl.reset_mock()
|
mock_pfctl.reset_mock()
|
||||||
|
|
||||||
with pytest.raises(Exception) as excinfo:
|
with pytest.raises(Exception) as excinfo:
|
||||||
method.setup_firewall(
|
method.setup_firewall(
|
||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(AF_INET, u'1.2.3.33')],
|
||||||
2,
|
AF_INET,
|
||||||
[(2, 24, False, u'1.2.3.0', 0, 0),
|
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
|
||||||
(2, 32, True, u'1.2.3.66', 80, 80)],
|
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
True)
|
True,
|
||||||
|
None)
|
||||||
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
||||||
assert mock_pf_get_dev.mock_calls == []
|
assert mock_pf_get_dev.mock_calls == []
|
||||||
assert mock_ioctl.mock_calls == []
|
assert mock_ioctl.mock_calls == []
|
||||||
@ -428,14 +444,15 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
|
|
||||||
method.setup_firewall(
|
method.setup_firewall(
|
||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(AF_INET, u'1.2.3.33')],
|
||||||
2,
|
AF_INET,
|
||||||
[(2, 24, False, u'1.2.3.0', 0, 0),
|
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
|
||||||
(2, 32, True, u'1.2.3.66', 80, 80)],
|
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
False)
|
False,
|
||||||
|
None)
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
call(mock_pf_get_dev(), 0xcd48441a, ANY),
|
call(mock_pf_get_dev(), 0xcd58441a, ANY),
|
||||||
call(mock_pf_get_dev(), 0xcd48441a, ANY),
|
call(mock_pf_get_dev(), 0xcd58441a, ANY),
|
||||||
]
|
]
|
||||||
assert mock_pfctl.mock_calls == [
|
assert mock_pfctl.mock_calls == [
|
||||||
call('-s Interfaces -i lo -v'),
|
call('-s Interfaces -i lo -v'),
|
||||||
@ -447,8 +464,8 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
b'127.0.0.1 port 1025\n'
|
b'127.0.0.1 port 1025\n'
|
||||||
b'pass in on lo0 inet proto udp to '
|
b'pass in on lo0 inet proto udp to '
|
||||||
b'<dns_servers> port 53 rdr-to 127.0.0.1 port 1027\n'
|
b'<dns_servers> port 53 rdr-to 127.0.0.1 port 1027\n'
|
||||||
b'pass out 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 tcp to 1.2.3.0/24 route-to lo0 keep state\n'
|
||||||
|
b'pass out inet proto tcp to 1.2.3.66/32 port 80:80\n'
|
||||||
b'pass out inet proto udp to '
|
b'pass out inet proto udp to '
|
||||||
b'<dns_servers> port 53 route-to lo0 keep state\n'),
|
b'<dns_servers> port 53 route-to lo0 keep state\n'),
|
||||||
call('-e'),
|
call('-e'),
|
||||||
@ -457,11 +474,13 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
|||||||
mock_ioctl.reset_mock()
|
mock_ioctl.reset_mock()
|
||||||
mock_pfctl.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_ioctl.mock_calls == []
|
||||||
assert mock_pfctl.mock_calls == [
|
assert mock_pfctl.mock_calls == [
|
||||||
call('-a sshuttle-1025 -F all'),
|
call('-a sshuttle-1025 -F all'),
|
||||||
call("-d"),
|
call('-a sshuttle6-1024 -F all'),
|
||||||
|
call('-d'),
|
||||||
]
|
]
|
||||||
mock_pf_get_dev.reset_mock()
|
mock_pf_get_dev.reset_mock()
|
||||||
mock_pfctl.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 mock import Mock, patch, call
|
||||||
|
|
||||||
from sshuttle.methods import get_method
|
from sshuttle.methods import get_method
|
||||||
@ -49,7 +52,7 @@ def test_send_udp(mock_socket):
|
|||||||
assert sock.mock_calls == []
|
assert sock.mock_calls == []
|
||||||
assert mock_socket.mock_calls == [
|
assert mock_socket.mock_calls == [
|
||||||
call(sock.family, 2),
|
call(sock.family, 2),
|
||||||
call().setsockopt(1, 2, 1),
|
call().setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1),
|
||||||
call().setsockopt(0, 19, 1),
|
call().setsockopt(0, 19, 1),
|
||||||
call().bind('127.0.0.2'),
|
call().bind('127.0.0.2'),
|
||||||
call().sendto("2222222", '127.0.0.1'),
|
call().sendto("2222222", '127.0.0.1'),
|
||||||
@ -100,71 +103,73 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
|||||||
|
|
||||||
method.setup_firewall(
|
method.setup_firewall(
|
||||||
1024, 1026,
|
1024, 1026,
|
||||||
[(10, u'2404:6800:4004:80c::33')],
|
[(AF_INET6, u'2404:6800:4004:80c::33')],
|
||||||
10,
|
AF_INET6,
|
||||||
[(10, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
|
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
|
||||||
(10, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||||
True)
|
True,
|
||||||
|
None)
|
||||||
assert mock_ipt_chain_exists.mock_calls == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(10, 'mangle', 'sshuttle-m-1024'),
|
call(AF_INET6, 'mangle', 'sshuttle-m-1024'),
|
||||||
call(10, 'mangle', 'sshuttle-t-1024'),
|
call(AF_INET6, 'mangle', 'sshuttle-t-1024'),
|
||||||
call(10, 'mangle', 'sshuttle-d-1024')
|
call(AF_INET6, 'mangle', 'sshuttle-d-1024')
|
||||||
]
|
]
|
||||||
assert mock_ipt_ttl.mock_calls == []
|
assert mock_ipt_ttl.mock_calls == []
|
||||||
assert mock_ipt.mock_calls == [
|
assert mock_ipt.mock_calls == [
|
||||||
call(10, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1024'),
|
call(AF_INET6, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1024'),
|
||||||
call(10, 'mangle', '-F', 'sshuttle-m-1024'),
|
call(AF_INET6, 'mangle', '-F', 'sshuttle-m-1024'),
|
||||||
call(10, 'mangle', '-X', 'sshuttle-m-1024'),
|
call(AF_INET6, 'mangle', '-X', 'sshuttle-m-1024'),
|
||||||
call(10, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1024'),
|
call(AF_INET6, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1024'),
|
||||||
call(10, 'mangle', '-F', 'sshuttle-t-1024'),
|
call(AF_INET6, 'mangle', '-F', 'sshuttle-t-1024'),
|
||||||
call(10, 'mangle', '-X', 'sshuttle-t-1024'),
|
call(AF_INET6, 'mangle', '-X', 'sshuttle-t-1024'),
|
||||||
call(10, 'mangle', '-F', 'sshuttle-d-1024'),
|
call(AF_INET6, 'mangle', '-F', 'sshuttle-d-1024'),
|
||||||
call(10, 'mangle', '-X', 'sshuttle-d-1024'),
|
call(AF_INET6, 'mangle', '-X', 'sshuttle-d-1024'),
|
||||||
call(10, 'mangle', '-N', 'sshuttle-m-1024'),
|
call(AF_INET6, 'mangle', '-N', 'sshuttle-m-1024'),
|
||||||
call(10, 'mangle', '-F', 'sshuttle-m-1024'),
|
call(AF_INET6, 'mangle', '-F', 'sshuttle-m-1024'),
|
||||||
call(10, 'mangle', '-N', 'sshuttle-d-1024'),
|
call(AF_INET6, 'mangle', '-N', 'sshuttle-d-1024'),
|
||||||
call(10, 'mangle', '-F', 'sshuttle-d-1024'),
|
call(AF_INET6, 'mangle', '-F', 'sshuttle-d-1024'),
|
||||||
call(10, 'mangle', '-N', 'sshuttle-t-1024'),
|
call(AF_INET6, 'mangle', '-N', 'sshuttle-t-1024'),
|
||||||
call(10, 'mangle', '-F', 'sshuttle-t-1024'),
|
call(AF_INET6, 'mangle', '-F', 'sshuttle-t-1024'),
|
||||||
call(10, 'mangle', '-I', 'OUTPUT', '1', '-j', 'sshuttle-m-1024'),
|
call(AF_INET6, 'mangle', '-I', 'OUTPUT', '1', '-j', 'sshuttle-m-1024'),
|
||||||
call(10, 'mangle', '-I', 'PREROUTING', '1', '-j', 'sshuttle-t-1024'),
|
call(AF_INET6, 'mangle', '-I', 'PREROUTING', '1', '-j',
|
||||||
call(10, 'mangle', '-A', 'sshuttle-d-1024', '-j', 'MARK',
|
'sshuttle-t-1024'),
|
||||||
|
call(AF_INET6, 'mangle', '-A', 'sshuttle-d-1024', '-j', 'MARK',
|
||||||
'--set-mark', '1'),
|
'--set-mark', '1'),
|
||||||
call(10, 'mangle', '-A', 'sshuttle-d-1024', '-j', 'ACCEPT'),
|
call(AF_INET6, 'mangle', '-A', 'sshuttle-d-1024', '-j', 'ACCEPT'),
|
||||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket',
|
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket',
|
||||||
'-j', 'sshuttle-d-1024', '-m', 'tcp', '-p', 'tcp'),
|
'-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'),
|
'-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',
|
'--set-mark', '1', '--dest', u'2404:6800:4004:80c::33/32',
|
||||||
'-m', 'udp', '-p', 'udp', '--dport', '53'),
|
'-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',
|
'--tproxy-mark', '0x1/0x1',
|
||||||
'--dest', u'2404:6800:4004:80c::33/32',
|
'--dest', u'2404:6800:4004:80c::33/32',
|
||||||
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1026'),
|
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1026'),
|
||||||
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
|
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
|
||||||
'--dest', u'2404:6800:4004:80c::101f/128',
|
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||||
'-m', 'tcp', '-p', 'tcp', '--dport', '8080:8080'),
|
'-m', 'tcp', '-p', 'tcp', '--dport', '8080:8080'),
|
||||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'RETURN',
|
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'RETURN',
|
||||||
'--dest', u'2404:6800:4004:80c::101f/128',
|
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||||
'-m', 'tcp', '-p', 'tcp', '--dport', '8080:8080'),
|
'-m', 'tcp', '-p', 'tcp', '--dport', '8080:8080'),
|
||||||
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',
|
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||||
'-m', 'udp', '-p', 'udp', '--dport', '8080:8080'),
|
'-m', 'udp', '-p', 'udp', '--dport', '8080:8080'),
|
||||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'RETURN',
|
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'RETURN',
|
||||||
'--dest', u'2404:6800:4004:80c::101f/128',
|
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||||
'-m', 'udp', '-p', 'udp', '--dport', '8080:8080'),
|
'-m', 'udp', '-p', 'udp', '--dport', '8080:8080'),
|
||||||
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::/64',
|
'--set-mark', '1', '--dest', u'2404:6800:4004:80c::/64',
|
||||||
'-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000'),
|
'-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000'),
|
||||||
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',
|
'--tproxy-mark', '0x1/0x1', '--dest', u'2404:6800:4004:80c::/64',
|
||||||
'-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000',
|
'-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000',
|
||||||
'--on-port', '1024'),
|
'--on-port', '1024'),
|
||||||
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::/64',
|
'--set-mark', '1', '--dest', u'2404:6800:4004:80c::/64',
|
||||||
'-m', 'udp', '-p', 'udp'),
|
'-m', 'udp', '-p', 'udp'),
|
||||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
|
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
|
||||||
'--tproxy-mark', '0x1/0x1', '--dest', u'2404:6800:4004:80c::/64',
|
'--tproxy-mark', '0x1/0x1', '--dest', u'2404:6800:4004:80c::/64',
|
||||||
'-m', 'udp', '-p', 'udp', '--dport', '8000:9000',
|
'-m', 'udp', '-p', 'udp', '--dport', '8000:9000',
|
||||||
'--on-port', '1024')
|
'--on-port', '1024')
|
||||||
@ -173,22 +178,22 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
|||||||
mock_ipt_ttl.reset_mock()
|
mock_ipt_ttl.reset_mock()
|
||||||
mock_ipt.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 == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(10, 'mangle', 'sshuttle-m-1025'),
|
call(AF_INET6, 'mangle', 'sshuttle-m-1025'),
|
||||||
call(10, 'mangle', 'sshuttle-t-1025'),
|
call(AF_INET6, 'mangle', 'sshuttle-t-1025'),
|
||||||
call(10, 'mangle', 'sshuttle-d-1025')
|
call(AF_INET6, 'mangle', 'sshuttle-d-1025')
|
||||||
]
|
]
|
||||||
assert mock_ipt_ttl.mock_calls == []
|
assert mock_ipt_ttl.mock_calls == []
|
||||||
assert mock_ipt.mock_calls == [
|
assert mock_ipt.mock_calls == [
|
||||||
call(10, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
|
call(AF_INET6, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
|
||||||
call(10, 'mangle', '-F', 'sshuttle-m-1025'),
|
call(AF_INET6, 'mangle', '-F', 'sshuttle-m-1025'),
|
||||||
call(10, 'mangle', '-X', 'sshuttle-m-1025'),
|
call(AF_INET6, 'mangle', '-X', 'sshuttle-m-1025'),
|
||||||
call(10, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
|
call(AF_INET6, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
|
||||||
call(10, 'mangle', '-F', 'sshuttle-t-1025'),
|
call(AF_INET6, 'mangle', '-F', 'sshuttle-t-1025'),
|
||||||
call(10, 'mangle', '-X', 'sshuttle-t-1025'),
|
call(AF_INET6, 'mangle', '-X', 'sshuttle-t-1025'),
|
||||||
call(10, 'mangle', '-F', 'sshuttle-d-1025'),
|
call(AF_INET6, 'mangle', '-F', 'sshuttle-d-1025'),
|
||||||
call(10, 'mangle', '-X', 'sshuttle-d-1025')
|
call(AF_INET6, 'mangle', '-X', 'sshuttle-d-1025')
|
||||||
]
|
]
|
||||||
mock_ipt_chain_exists.reset_mock()
|
mock_ipt_chain_exists.reset_mock()
|
||||||
mock_ipt_ttl.reset_mock()
|
mock_ipt_ttl.reset_mock()
|
||||||
@ -198,68 +203,71 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
|||||||
|
|
||||||
method.setup_firewall(
|
method.setup_firewall(
|
||||||
1025, 1027,
|
1025, 1027,
|
||||||
[(2, u'1.2.3.33')],
|
[(AF_INET, u'1.2.3.33')],
|
||||||
2,
|
AF_INET,
|
||||||
[(2, 24, False, u'1.2.3.0', 0, 0), (2, 32, True, u'1.2.3.66', 80, 80)],
|
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
|
||||||
True)
|
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
|
||||||
|
True,
|
||||||
|
None)
|
||||||
assert mock_ipt_chain_exists.mock_calls == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(2, 'mangle', 'sshuttle-m-1025'),
|
call(AF_INET, 'mangle', 'sshuttle-m-1025'),
|
||||||
call(2, 'mangle', 'sshuttle-t-1025'),
|
call(AF_INET, 'mangle', 'sshuttle-t-1025'),
|
||||||
call(2, 'mangle', 'sshuttle-d-1025')
|
call(AF_INET, 'mangle', 'sshuttle-d-1025')
|
||||||
]
|
]
|
||||||
assert mock_ipt_ttl.mock_calls == []
|
assert mock_ipt_ttl.mock_calls == []
|
||||||
assert mock_ipt.mock_calls == [
|
assert mock_ipt.mock_calls == [
|
||||||
call(2, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
|
call(AF_INET, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
|
||||||
call(2, 'mangle', '-F', 'sshuttle-m-1025'),
|
call(AF_INET, 'mangle', '-F', 'sshuttle-m-1025'),
|
||||||
call(2, 'mangle', '-X', 'sshuttle-m-1025'),
|
call(AF_INET, 'mangle', '-X', 'sshuttle-m-1025'),
|
||||||
call(2, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
|
call(AF_INET, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
|
||||||
call(2, 'mangle', '-F', 'sshuttle-t-1025'),
|
call(AF_INET, 'mangle', '-F', 'sshuttle-t-1025'),
|
||||||
call(2, 'mangle', '-X', 'sshuttle-t-1025'),
|
call(AF_INET, 'mangle', '-X', 'sshuttle-t-1025'),
|
||||||
call(2, 'mangle', '-F', 'sshuttle-d-1025'),
|
call(AF_INET, 'mangle', '-F', 'sshuttle-d-1025'),
|
||||||
call(2, 'mangle', '-X', 'sshuttle-d-1025'),
|
call(AF_INET, 'mangle', '-X', 'sshuttle-d-1025'),
|
||||||
call(2, 'mangle', '-N', 'sshuttle-m-1025'),
|
call(AF_INET, 'mangle', '-N', 'sshuttle-m-1025'),
|
||||||
call(2, 'mangle', '-F', 'sshuttle-m-1025'),
|
call(AF_INET, 'mangle', '-F', 'sshuttle-m-1025'),
|
||||||
call(2, 'mangle', '-N', 'sshuttle-d-1025'),
|
call(AF_INET, 'mangle', '-N', 'sshuttle-d-1025'),
|
||||||
call(2, 'mangle', '-F', 'sshuttle-d-1025'),
|
call(AF_INET, 'mangle', '-F', 'sshuttle-d-1025'),
|
||||||
call(2, 'mangle', '-N', 'sshuttle-t-1025'),
|
call(AF_INET, 'mangle', '-N', 'sshuttle-t-1025'),
|
||||||
call(2, 'mangle', '-F', 'sshuttle-t-1025'),
|
call(AF_INET, 'mangle', '-F', 'sshuttle-t-1025'),
|
||||||
call(2, 'mangle', '-I', 'OUTPUT', '1', '-j', 'sshuttle-m-1025'),
|
call(AF_INET, 'mangle', '-I', 'OUTPUT', '1', '-j', 'sshuttle-m-1025'),
|
||||||
call(2, 'mangle', '-I', 'PREROUTING', '1', '-j', 'sshuttle-t-1025'),
|
call(AF_INET, 'mangle', '-I', 'PREROUTING', '1', '-j',
|
||||||
call(2, 'mangle', '-A', 'sshuttle-d-1025',
|
'sshuttle-t-1025'),
|
||||||
|
call(AF_INET, 'mangle', '-A', 'sshuttle-d-1025',
|
||||||
'-j', 'MARK', '--set-mark', '1'),
|
'-j', 'MARK', '--set-mark', '1'),
|
||||||
call(2, 'mangle', '-A', 'sshuttle-d-1025', '-j', 'ACCEPT'),
|
call(AF_INET, 'mangle', '-A', 'sshuttle-d-1025', '-j', 'ACCEPT'),
|
||||||
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket',
|
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket',
|
||||||
'-j', 'sshuttle-d-1025', '-m', 'tcp', '-p', 'tcp'),
|
'-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'),
|
'-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',
|
'--set-mark', '1', '--dest', u'1.2.3.33/32',
|
||||||
'-m', 'udp', '-p', 'udp', '--dport', '53'),
|
'-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',
|
'--tproxy-mark', '0x1/0x1', '--dest', u'1.2.3.33/32',
|
||||||
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1027'),
|
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1027'),
|
||||||
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
||||||
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp',
|
|
||||||
'--dport', '80:80'),
|
|
||||||
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'RETURN',
|
|
||||||
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp',
|
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp',
|
||||||
'--dport', '80:80'),
|
'--dport', '80:80'),
|
||||||
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
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',
|
'--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp',
|
||||||
'--dport', '80:80'),
|
'--dport', '80:80'),
|
||||||
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'RETURN',
|
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'RETURN',
|
||||||
'--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp',
|
'--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp',
|
||||||
'--dport', '80:80'),
|
'--dport', '80:80'),
|
||||||
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',
|
'--set-mark', '1', '--dest', u'1.2.3.0/24',
|
||||||
'-m', 'tcp', '-p', 'tcp'),
|
'-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',
|
'--tproxy-mark', '0x1/0x1', '--dest', u'1.2.3.0/24',
|
||||||
'-m', 'tcp', '-p', 'tcp', '--on-port', '1025'),
|
'-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',
|
'--set-mark', '1', '--dest', u'1.2.3.0/24',
|
||||||
'-m', 'udp', '-p', 'udp'),
|
'-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',
|
'--tproxy-mark', '0x1/0x1', '--dest', u'1.2.3.0/24',
|
||||||
'-m', 'udp', '-p', 'udp', '--on-port', '1025')
|
'-m', 'udp', '-p', 'udp', '--on-port', '1025')
|
||||||
]
|
]
|
||||||
@ -267,22 +275,22 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
|||||||
mock_ipt_ttl.reset_mock()
|
mock_ipt_ttl.reset_mock()
|
||||||
mock_ipt.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 == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(2, 'mangle', 'sshuttle-m-1025'),
|
call(AF_INET, 'mangle', 'sshuttle-m-1025'),
|
||||||
call(2, 'mangle', 'sshuttle-t-1025'),
|
call(AF_INET, 'mangle', 'sshuttle-t-1025'),
|
||||||
call(2, 'mangle', 'sshuttle-d-1025')
|
call(AF_INET, 'mangle', 'sshuttle-d-1025')
|
||||||
]
|
]
|
||||||
assert mock_ipt_ttl.mock_calls == []
|
assert mock_ipt_ttl.mock_calls == []
|
||||||
assert mock_ipt.mock_calls == [
|
assert mock_ipt.mock_calls == [
|
||||||
call(2, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
|
call(AF_INET, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
|
||||||
call(2, 'mangle', '-F', 'sshuttle-m-1025'),
|
call(AF_INET, 'mangle', '-F', 'sshuttle-m-1025'),
|
||||||
call(2, 'mangle', '-X', 'sshuttle-m-1025'),
|
call(AF_INET, 'mangle', '-X', 'sshuttle-m-1025'),
|
||||||
call(2, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
|
call(AF_INET, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
|
||||||
call(2, 'mangle', '-F', 'sshuttle-t-1025'),
|
call(AF_INET, 'mangle', '-F', 'sshuttle-t-1025'),
|
||||||
call(2, 'mangle', '-X', 'sshuttle-t-1025'),
|
call(AF_INET, 'mangle', '-X', 'sshuttle-t-1025'),
|
||||||
call(2, 'mangle', '-F', 'sshuttle-d-1025'),
|
call(AF_INET, 'mangle', '-F', 'sshuttle-d-1025'),
|
||||||
call(2, 'mangle', '-X', 'sshuttle-d-1025')
|
call(AF_INET, 'mangle', '-X', 'sshuttle-d-1025')
|
||||||
]
|
]
|
||||||
mock_ipt_chain_exists.reset_mock()
|
mock_ipt_chain_exists.reset_mock()
|
||||||
mock_ipt_ttl.reset_mock()
|
mock_ipt_ttl.reset_mock()
|
@ -51,7 +51,7 @@ def test_parse_subnetport_ip4_with_port():
|
|||||||
for ip_repr, ip in _ip4_reprs.items():
|
for ip_repr, ip in _ip4_reprs.items():
|
||||||
assert sshuttle.options.parse_subnetport(':'.join((ip_repr, '80'))) \
|
assert sshuttle.options.parse_subnetport(':'.join((ip_repr, '80'))) \
|
||||||
== (socket.AF_INET, ip, 32, 80, 80)
|
== (socket.AF_INET, ip, 32, 80, 80)
|
||||||
assert sshuttle.options.parse_subnetport(':'.join((ip_repr, '80-90'))) \
|
assert sshuttle.options.parse_subnetport(':'.join((ip_repr, '80-90')))\
|
||||||
== (socket.AF_INET, ip, 32, 80, 90)
|
== (socket.AF_INET, ip, 32, 80, 90)
|
||||||
|
|
||||||
|
|
||||||
@ -67,9 +67,6 @@ def test_parse_subnetport_ip6():
|
|||||||
for ip_repr, ip in _ip6_reprs.items():
|
for ip_repr, ip in _ip6_reprs.items():
|
||||||
assert sshuttle.options.parse_subnetport(ip_repr) \
|
assert sshuttle.options.parse_subnetport(ip_repr) \
|
||||||
== (socket.AF_INET6, ip, 128, 0, 0)
|
== (socket.AF_INET6, ip, 128, 0, 0)
|
||||||
with pytest.raises(Fatal) as excinfo:
|
|
||||||
sshuttle.options.parse_subnetport('2001::1::3f')
|
|
||||||
assert str(excinfo.value) == 'Unable to resolve address: 2001::1::3f'
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_subnetport_ip6_with_mask():
|
def test_parse_subnetport_ip6_with_mask():
|
||||||
@ -97,5 +94,5 @@ def test_parse_subnetport_ip6_with_mask_and_port():
|
|||||||
for ip_repr, ip in _ip6_reprs.items():
|
for ip_repr, ip in _ip6_reprs.items():
|
||||||
assert sshuttle.options.parse_subnetport('[' + ip_repr + '/128]:80') \
|
assert sshuttle.options.parse_subnetport('[' + ip_repr + '/128]:80') \
|
||||||
== (socket.AF_INET6, ip, 128, 80, 80)
|
== (socket.AF_INET6, ip, 128, 80, 80)
|
||||||
assert sshuttle.options.parse_subnetport('[' + ip_repr + '/16]:80-90') \
|
assert sshuttle.options.parse_subnetport('[' + ip_repr + '/16]:80-90')\
|
||||||
== (socket.AF_INET6, ip, 16, 80, 90)
|
== (socket.AF_INET6, ip, 16, 80, 90)
|
@ -1,6 +1,4 @@
|
|||||||
from mock import Mock, patch, call
|
from mock import Mock, patch, call
|
||||||
import sys
|
|
||||||
import io
|
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
import sshuttle.sdnotify
|
import sshuttle.sdnotify
|
||||||
@ -59,7 +57,7 @@ def test_notify(mock_get, mock_socket):
|
|||||||
sock.sendto.return_value = 1
|
sock.sendto.return_value = 1
|
||||||
mock_get.return_value = '/run/valid_path'
|
mock_get.return_value = '/run/valid_path'
|
||||||
mock_socket.return_value = sock
|
mock_socket.return_value = sock
|
||||||
|
|
||||||
assert sshuttle.sdnotify.send(*messages)
|
assert sshuttle.sdnotify.send(*messages)
|
||||||
assert sock.sendto.mock_calls == [
|
assert sock.sendto.mock_calls == [
|
||||||
call(b'\n'.join(messages), socket_path),
|
call(b'\n'.join(messages), socket_path),
|
@ -7,4 +7,4 @@ else:
|
|||||||
|
|
||||||
collect_ignore = []
|
collect_ignore = []
|
||||||
if not good_python:
|
if not good_python:
|
||||||
collect_ignore.append("sshuttle/tests/client")
|
collect_ignore.append("client")
|
@ -1,8 +1,7 @@
|
|||||||
import os
|
|
||||||
import io
|
import io
|
||||||
import socket
|
import socket
|
||||||
import sshuttle.server
|
import sshuttle.server
|
||||||
from mock import patch, Mock, call
|
from mock import patch, Mock
|
||||||
|
|
||||||
|
|
||||||
def test__ipmatch():
|
def test__ipmatch():
|
9
tox.ini
9
tox.ini
@ -1,10 +1,10 @@
|
|||||||
[tox]
|
[tox]
|
||||||
downloadcache = {toxworkdir}/cache/
|
downloadcache = {toxworkdir}/cache/
|
||||||
envlist =
|
envlist =
|
||||||
py26,
|
|
||||||
py27,
|
py27,
|
||||||
py34,
|
py34,
|
||||||
py35,
|
py35,
|
||||||
|
py36,
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
basepython =
|
basepython =
|
||||||
@ -12,9 +12,10 @@ basepython =
|
|||||||
py27: python2.7
|
py27: python2.7
|
||||||
py34: python3.4
|
py34: python3.4
|
||||||
py35: python3.5
|
py35: python3.5
|
||||||
|
py36: python3.6
|
||||||
commands =
|
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
|
py.test
|
||||||
deps =
|
deps =
|
||||||
pytest
|
-rrequirements-tests.txt
|
||||||
mock
|
|
||||||
setuptools>=17.1
|
|
||||||
|
Reference in New Issue
Block a user