Compare commits

...

81 Commits

Author SHA1 Message Date
752a953101 Release 0.78.5 2019-01-28 16:28:57 +13:00
61f4cd9de5 Update CHANGES.rst for new release 2019-01-28 11:36:14 +13:00
8e35f049e2 auto-nets: retrieve routes only if using auto-nets
There's a known issue that makes sshuttle crash if there are too
many routes on the remote host (that don't fit in 64KB). This patch
requests the routes only if auto-nets is specified on the command
line.
2019-01-28 08:53:51 +13:00
0e99adc5d1 Fix potential deadlock condition in nft_get_handle
This was susceptible to the same deadlock issue that ipt_chain_exists
had and was fixed in d43db80 where if the command returned a significant
amount of output, it wouldn't all be read in, resulting in the
subprocess hanging waiting for the output to be read.
2019-01-23 18:53:45 +13:00
04849df7e3 Use subprocess.check_output instead of run
subprocess.run only exists for python3, and this needs to also support
python 2.7
2019-01-23 18:53:45 +13:00
531a17c151 docs: document --ns-hosts --to-ns and update --dns
--ns-hosts is available since commit d2ee34d71c
("dns: Added --ns-hosts to tunnel only some requests")
(released as v0.72), but was never documented.

--to-ns is available since commit be559fc78b
("Fix case where there is no --dns.") after several
bugfixes, released as v0.78.4, but was never
documented.
2018-12-29 15:02:18 +11:00
d43db80dec Fix deadlock with iptables with large ruleset
When running sshuttle with a large list of routes it's failing to clean
them up at exit. It returns the following:

$ sshuttle -r user@host.example.com -s /tmp/aws-cidrs.txt
user@host.example.com's password:
client: Connected.
^CAnother app is currently holding the xtables lock; still -9s 0us time ahead to have a chance to grab the lock...
Another app is currently holding the xtables lock; still -19s 0us time ahead to have a chance to grab the lock...
Another app is currently holding the xtables lock; still -29s 0us time ahead to have a chance to grab the lock...

This continues indefinitely. Looking in ps reveals that there are 2
iptables processes running. Killing -9 the first one, allows sshuttle to
continue and clean up successfully.

The problem lies with the use of Popen here. The function currently
returns as soon as it finds a match without consuming everything from
stdout. This means that if there's more output from iptables than will
fit in the buffer it doesn't exit, and therefore doesn't release the
kernel xtables lock.
2018-12-09 18:03:54 +11:00
0b1a260436 Fix typo in docs 2018-12-03 14:34:42 +11:00
efc854c33e Document --version option 2018-11-29 08:02:58 +11:00
ca41026c89 Changes pf exclusion rules precedence
Before this change, in pf, exclusions used a pass out quick which gave
them higher precedence than any other rule independent of subnet width.
As reported in #265 this causes exclusion from one instance of sshuttle
to also take effect on other instances because quick aborts the
evaluation of rules across all anchors.

This commit changes the precedence of rules so quick can now be
dropped. The new order is defined by the following rule, from
subnet_weight:

"We need to go from smaller, more specific, port ranges, to larger,
less-specific, port ranges. At each level, we order by subnet
width, from most-specific subnets (largest swidth) to
least-specific. On ties, excludes come first."
2018-11-03 12:24:32 +11:00
b473b91633 Close stdin, stdout, and stderr when using syslog or forking to daemon (#283)
* Close stdin, stdout, and stderr when using syslog or forking to daemon

Fixes #139

* Ensure we close devnull after use
2018-11-01 09:27:50 +11:00
7a54d12f80 Fixes support for OpenBSD (6.1+) (#282)
* Fixes support for OpenBSD (6.1+)

As reported in #219, new versions of OpenBSD ship with a different
pfioc_rule struct. This commit adjusts the offset to match the new struct.

* Fixes tests for OpenBSD 6.1+
2018-10-23 07:31:29 +11:00
d4bbf3b68d Added coverage report to tests 2018-10-17 20:54:28 +11:00
41f5b3e9c1 replace path /dev/null by os.devnull 2018-10-17 20:53:06 +11:00
c780597de3 updated bandit config 2018-10-17 20:52:04 +11:00
d085a419b2 updated path 2018-10-17 20:52:04 +11:00
842768f9cf Moved sshuttle/tests into tests to.
Having the tests in a `tests` directory in root is the most common
approach. Also moved pytest's conftest.py into `tests` making the
fixture available for client and server tests.
2018-10-17 20:52:04 +11:00
97ed2030f3 Fix missing string formatting argument 2018-10-07 11:30:41 +11:00
6dc368bde8 Merge pull request #271 from usabilla/no-sudo-pythonpath
Add --no-sudo-pythonpath option
2018-09-22 17:57:33 +10:00
f528bb9846 Add --no-sudo-pythonpath option
This provides a way to avoid setting PYTHONPATH when invoking the
privileged part of sshuttle with sudo. This is useful if running
sshuttle as a PEX archive, as Telepresence does, as it enables
sshuttle's sudo access to be securely locked down.

PEX archives will extract themselves into the invoking user's home
directory, which means that the invoking user has full control over
the code in them. This makes restricting sudo access with
PYTHONPATH set completely pointless in this scenario -- an attacker
could put any code into ~/.pex and gain full root access anyway.

On the other hand, if sshuttle is a PEX archive, the privileged
invocation will simply extract itself into /root/.pex anyway, so
there is no need to set PYTHONPATH in this case.
2018-09-21 18:48:31 +02:00
561b648e4b works on ChromeOS with Crostini VM (#262)
* works on ChromeOS with Crostini VM

tested on ASUS C101PA on Dev channel, should also work on Intel machines and Beta channel

* crostini doc, and a note about xterms and VNC

tested on ASUS C101PA on Dev channel, should also work on Intel machines and Beta channel
2018-08-25 10:30:45 +10:00
0dba8a8beb Don't crash if we can't look up peername
Peername is only used for information display messages.

Fixes #259
2018-08-21 08:36:51 +10:00
7b6f082454 Doc Update
Remove reference to autossh per
https://github.com/sshuttle/sshuttle/issues/143
2018-06-29 07:38:55 +10:00
1ec17e1b1b Update README.rst 2018-06-22 16:02:11 +10:00
cecccc2efd Doc: Fix typo 2018-06-07 07:00:17 +10:00
db69ba6d8d Doc: Improve Systemd service recommendation (fixes #238) 2018-06-03 07:38:21 +10:00
2bb92cd6d4 Fix sudo/doas
Fixes #227
2018-05-13 20:35:18 +10:00
ae5bd28dcf Add doas support for client 2018-05-09 17:46:33 +10:00
55bd78fd43 Fix line length for CI. 2018-04-30 07:40:58 +10:00
1f5ed9c66e Fix concatening string to tuple. Allow for forwarding a single port. 2018-04-30 07:40:58 +10:00
6ec42adbf4 Prepare for 0.78.4 2018-04-02 14:52:22 +10:00
2200d824bf Improve formatting 2018-03-22 07:59:10 +11:00
9715a1d6f2 Preserve peer and port properly 2018-03-22 07:59:10 +11:00
8bfc03b256 Make --to-dns and --ns-host work well together 2018-03-22 07:59:10 +11:00
884bd6deb0 Remove test that fails under OSX
Fixes #213
2018-03-16 18:40:32 +11:00
a215f1b227 Remove Python 2.6 from automatic tests
Automatic python 2.6 testing is becoming harder, especially as pytest
3.4.2 is unavailable for Python 2.6.
2018-03-16 18:34:15 +11:00
11455d0bcd Various updates to tests 2018-03-16 18:27:50 +11:00
74acc10385 Add entries to .gitignore 2018-03-16 18:10:09 +11:00
084bf5f0f2 Specify pip requirements for tests 2018-03-16 18:10:09 +11:00
1940b524f1 Add nat-like method using nftables instead of iptables 2018-03-13 07:36:00 +11:00
d11f5b9d16 Use flake8 to find Python syntax errors or undefined names 2018-02-22 18:02:36 +11:00
93b969a049 Fix compatibility with the sudoers file
Starting sshuttle without having to type in one's password requires to
put the sudo-ed command in the `/etc/sudoers` file. However, sshuttle
sets an environment variable, which cannot be done as-is in the sudoers
file. This fix prepend the /usr/bin/env command, which allows one to
pass fixed environment variables to a sudo-ed command.

In practice, the sub-command:

```
sudo PYTHONPATH=/usr/lib/python3/dist-packages -- \
        /usr/bin/python3 /usr/bin/sshuttle --method auto --firewall
```

becomes

```
sudo /usr/bin/env PYTHONPATH=/usr/lib/python3/dist-packages \
        /usr/bin/python3 /usr/bin/sshuttle --method auto --firewall
```
2018-02-16 08:07:02 +11:00
f27b27b0e8 Stop using SO_REUSEADDR on sockets 2018-02-16 08:04:22 +11:00
fc08fb4086 Declare 'verbosity' as global variable to placate linters 2018-02-15 21:34:05 +11:00
e82d5a8e7c Adds 'cd sshuttle' after 'git' to README and docs 2018-02-15 07:37:15 +11:00
d9d61e6ab2 Documentation for loading options from configuration file 2018-01-30 17:08:30 +11:00
179bb107e1 Load options from a file
This small change will allow a file path to be passed as argument from which
the command line options will be loaded.

Extra command line options can be passed (in addition to those already in the
file) and existing ones can be overriden.

Example sshuttle.conf file:
192.168.0.0/16
--remote
user@example.com

Example sshuttle call:
sshuttle @/path/to/sshuttle.conf

Example sshuttle call with verbose flags added:
sshuttle @/path/to/sshuttle.conf -vvv

Example sshuttle call overriding the remote server:
sshuttle @/path/to/sshuttle.conf -r otheruser@test.example.com
2018-01-30 17:08:30 +11:00
9a176aa96f Update firewall.py 2018-01-01 09:35:41 +11:00
6b48301b86 move sdnotify after setting up firewall rules 2018-01-01 09:35:41 +11:00
be90cc8abd Fix tests on Macos
Swap hardcoded AF_INET(6) values for Python-provided values as they
differ between Darwin and Linux (30 vs 10 for AF_INET6 for instance).
2018-01-01 09:33:41 +11:00
512396e06b Add changelog entry about fixed license 2017-11-16 19:57:33 +11:00
7a71ae1380 Remove trailing whitespace 2017-11-16 18:06:33 +11:00
3a6f6cb795 Add changes entry for next release 2017-11-16 18:06:01 +11:00
81ab587698 Updating per @brianmay correspondence in https://github.com/sshuttle/sshuttle/issues/186 2017-11-16 18:05:39 +11:00
817284c2f8 Use more standard filename and format for bandit conifguration 2017-11-13 11:58:43 +11:00
71d65f3831 Fixes some style issues and minor bugs 2017-11-13 11:58:43 +11:00
9f238ebca8 Properly decode seed hosts argument in server.py
When I starting sshuttle with option `--seed-hosts example.com`, got the following error:

```
hostwatch: Starting hostwatch with Python version 3.5.2
hostwatch: Traceback (most recent call last):
--->   File "sshuttle.server", line 144, in start_hostwatch
--->   File "sshuttle.hostwatch", line 272, in hw_main
--->   File "sshuttle.hostwatch", line 234, in check_host
--->   File "sshuttle.hostwatch", line 32, in _is_ip
--->   File "/usr/lib/python3.5/re.py", line 163, in match
--->     return _compile(pattern, flags).match(string)
---> TypeError: cannot use a string pattern on a bytes-like object
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "assembler.py", line 37, in <module>
  File "sshuttle.server", line 393, in main
  File "sshuttle.ssnet", line 596, in runonce
  File "sshuttle.server", line 324, in hostwatch_ready
sshuttle.helpers.Fatal: hostwatch process died
```

It seems like the list of hosts is not properly decoded on the server side. This is an attempt to fix that.
2017-11-11 10:06:37 +11:00
9b315746d1 Using exec in the assembler is okay 2017-11-09 12:02:31 +11:00
6a488b3db9 Initial configuration for Bandit and Prospector
With this configuration it should be feasible to achieve a perfect score
without contortion.

Rules skiped for Bandit:
B101: assert_used
B104: hardcoded_bind_all_interfaces
B404: import_subprocess
B603: subprocess_without_shell_equals_true
B606: start_process_with_no_shell
B607: start_process_with_partial_path

Rules skiped for pylint:
- too-many-statements
- too-many-locals
- too-many-function-args
- too-many-arguments
- too-many-branches
- bare-except
- protected-access
- no-else-return
2017-11-09 12:02:31 +11:00
112931dd2c Changes methods that do not reference the instance to static methods 2017-11-08 16:17:06 +11:00
ad676029c7 Fix no value passed for argument auto_hosts in hw_main call 2017-11-08 16:17:06 +11:00
47030e846b Remove trailing whitespaces 2017-11-08 16:17:06 +11:00
416636fa9b Mock socket bind to avoid depending on local IPs being available in test box 2017-11-07 10:08:16 +11:00
4300a02343 Remove unused variable 'timeout' 2017-11-07 10:08:16 +11:00
4e8c5411b5 Also register por for dns proxy and for pairs in use by other procs 2017-11-07 10:08:16 +11:00
6cdc4da1e4 Fixes UDP and DNS proxies binding to the same socket address
As suggested by @colinmkeith the UDP and DNS proxies should listen on different
ports otherwise the DNS proxy can get traffic intended to the UDP proxy (or
vice-versa) and handle it incorrectly as reported in #178.

At first sight it seems that we had the code in place to try another port if
the one we are binding is already bound, however, with UDP and REUSEADDR the
OS will not refuse to bind two sockets to the same socket address, so both
the UDP proxy and DNS proxy were being bound to the same pair.
2017-11-07 10:08:16 +11:00
8add00866c turn off debugging 2017-10-23 06:58:21 +11:00
94ea0a3bed nested if should be and 2017-10-23 06:58:21 +11:00
9b7ce2811e Use versions of python3 greater than 3.5 when available (e.g. 3.6)
Some Linux distros, like Alpine, Arch, etc and some BSDs, like FreeBSD, are
now shipping with python3.6 as the default python3. Both the client and the
server are failing to run in this distros, because we are specifically looking
for python3.5.

These changes make the run shell script use python3 if the version is greater
than 3.5, otherwise falling back as usual.

On the server any version of python3 will do, use it before falling back to
python, as the server code can run with any version of python3.
2017-10-23 06:58:21 +11:00
7726dea27c Test double restore (ipv4, ipv6) disables only once; test kldload 2017-10-21 12:10:31 +11:00
3635cc17ad Load pf kernel module when enabling pf
When the pf module is not loaded our calls to pfctl will fail with
unhelpful messages.
This change spares the user the pain of decrypting those messages and manually
enabling pf. It also keeps track if pf was loaded by sshuttle and unloads on
exit if that was the case.

Also fixed the case where both ipv4 and ipv6 anchors were added by sshuttle
but the first call of disable would disable pf before the second call had the
chance of cleaning it's anchor.
2017-10-21 12:10:31 +11:00
ae13316e83 Just skip empty lines of routes data instead of stopping processing 2017-10-19 13:45:34 +11:00
e173eb6016 Skip empty lines on incoming routes data
If we receive no routes from server or if, for some reason, we receive
some empty lines, we should skip them instead of crashing.

Fixes on of the problems in #147.
2017-10-19 13:45:34 +11:00
29cd75b6f7 Make hostwatch find both fqdn and hostname
Currently hostwatch only adds hostnames even when FQDNs are available.
This commit changes found_host so that when the name is a FQDN, both the FQDN
and an hostname are added, e.g., given api.foo.com both api and api.foo.com
will be added.

Fixes #151 if merged.

N.B.: I rarely use hostwatch, it would probably be a good idea to get feedback
from people who actually use it before merging. Not too sure about this...
2017-10-17 07:12:06 +11:00
4c50be0bc7 Use getaddrinfo to obtain a correct sockaddr
While with AF_INET sockaddr is a 2-tuple composed by (address, port),
with AF_INET6 it is a 4-tuple with (address, port, flow info, scope id).

We were always passing a 2-tuple to socket.connect which would fail whenever
the address was, for instance, a link-local IPv6 address that needs a scope id.

With this change we now use getaddrinfo to correctly compute the full tuple.

Fixes #156.
2017-10-15 12:43:04 +11:00
max
2fa0cd06fb Route traffic by linux user 2017-09-17 15:33:34 +10:00
4d8b758d32 Add homebrew instructions
Per https://github.com/apenwarr/sshuttle/pull/45/files
2017-08-03 13:55:04 +10:00
4e8c2b9c68 Avoid port forwarding from loopback address
When doing port forwarding on lo0 avoid the special case where the
traffic on lo0 did not came from sshuttle pass out rule but from the lo0
address itself. Fixes #159.
2017-07-29 17:15:32 +10:00
be559fc78b Fix case where there is no --dns. 2017-07-18 17:15:03 +10:00
d2e97a60f7 Add new option for overriding destination DNS server. 2017-07-18 17:15:03 +10:00
cdbb379910 Talk to custom DNS server on pod, instead of the ones in /etc/resolv.conf 2017-07-18 17:15:03 +10:00
43 changed files with 1067 additions and 511 deletions

2
.gitignore vendored
View File

@ -13,3 +13,5 @@
/.do_built
/.do_built.dir
/.redo
/.pytest_cache/
/.python-version

24
.prospector.yml Normal file
View 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

View File

@ -1,13 +1,19 @@
language: python
python:
- 2.6
- 2.7
- 3.4
- 3.5
- 3.6
- pypy
install:
- travis_retry pip install -q pytest mock
- travis_retry pip install -q -r requirements-tests.txt
before_script:
# stop the build if there are Python syntax errors or undefined names.
- flake8 sshuttle --count --select=E901,E999,F821,F822,F823 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide.
- flake8 sshuttle --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
script:
- PYTHONPATH=. py.test

View File

@ -9,6 +9,86 @@ adheres to `Semantic Versioning`_.
.. _`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
-------------------
The "I should have done a git pull" first release.

View File

@ -40,6 +40,7 @@ Obtaining sshuttle
- Clone::
git clone https://github.com/sshuttle/sshuttle.git
cd sshuttle
sudo ./setup.py install
It is also possible to install into a virtualenv as a non-root user.
@ -55,12 +56,18 @@ It is also possible to install into a virtualenv as a non-root user.
virtualenv -p python3 /tmp/sshuttle
. /tmp/sshuttle/bin/activate
git clone https://github.com/sshuttle/sshuttle.git
cd sshuttle
./setup.py install
- Homebrew::
brew install sshuttle
Documentation
-------------
The documentation for the stable version is available at:
http://sshuttle.readthedocs.org/
https://sshuttle.readthedocs.org/
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
View File

@ -0,0 +1,9 @@
exclude_dirs:
- tests
skips:
- B101
- B104
- B404
- B603
- B606
- B607

12
docs/chromeos.rst Normal file
View 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

View File

@ -8,4 +8,5 @@ Installation
- Clone::
git clone https://github.com/sshuttle/sshuttle.git
cd sshuttle
./setup.py install

View File

@ -94,7 +94,30 @@ Options
.. option:: --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
@ -204,6 +227,33 @@ Options
makes it a lot easier to debug and test the :option:`--auto-hosts`
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
--------
@ -253,6 +303,24 @@ and subnet guessing::
c : Keyboard interrupt: exiting.
c : SW#6:192.168.42.121:60554: deleting
Run :program:`sshuttle` with a `/etc/sshuttle.conf` configuration file::
$ sshuttle @/etc/sshuttle.conf
Use the options defined in `/etc/sshuttle.conf` but be more verbose::
$ sshuttle @/etc/sshuttle.conf -vvv
Override the remote server defined in `/etc/sshuttle.conf`::
$ sshuttle @/etc/sshuttle.conf -r otheruser@test.example.com
Example configuration file::
192.168.0.0/16
--remote
user@example.com
Discussion
----------

View File

@ -6,5 +6,6 @@ Contents:
.. toctree::
:maxdepth: 2
chromeos
tproxy
windows

View File

@ -73,8 +73,6 @@ possible as support for older versions might be dropped in the future.
Additional Suggested Software
-----------------------------
- You may want to use autossh, available in various package management
systems.
- If you are using systemd, sshuttle can notify it when the connection to
the remote end is established and the firewall rules are installed. For
this feature to work you must configure the process start-up type for the
@ -89,6 +87,7 @@ Additional Suggested Software
[Service]
Type=notify
NotifyAccess=all
ExecStart=/usr/bin/sshuttle --dns --remote <user>@<server> <subnets...>
[Install]

5
requirements-tests.txt Normal file
View 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
View File

@ -1,11 +1,16 @@
#!/bin/sh
source_dir="$(dirname $0)"
(cd "$source_dir" && "$source_dir/setup.py" --version > /dev/null)
export PYTHONPATH="$source_dir:$PYTHONPATH"
if python3.5 -V >/dev/null 2>&1; then
exec python3.5 -m "sshuttle" "$@"
elif python2.7 -V >/dev/null 2>&1; then
exec python2.7 -m "sshuttle" "$@"
else
exec python -m "sshuttle" "$@"
fi
#!/usr/bin/env sh
set -e
export PYTHONPATH="$(dirname $0):$PYTHONPATH"
python_best_version() {
if [ -x "$(command -v python3)" ] &&
python3 -c "import sys; sys.exit(not sys.version_info > (3, 5))"; then
exec python3 "$@"
elif [ -x "$(command -v python2.7)" ]; then
exec python2.7 "$@"
else
exec python "$@"
fi
}
python_best_version -m "sshuttle" "$@"

View File

@ -7,3 +7,6 @@ universal = 1
[upload]
sign=true
identity=0x1784577F811F6EAC
[tool:pytest]
addopts = --cov=sshuttle --cov-branch --cov-report=term-missing

View File

@ -2,20 +2,20 @@
# Copyright 2012-2014 Brian May
#
# This file is part of python-tldap.
# This file is part of sshuttle.
#
# python-tldap is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# sshuttle is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of
# the License, or (at your option) any later version.
#
# python-tldap is distributed in the hope that it will be useful,
# sshuttle is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with python-tldap If not, see <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU Lesser General Public License
# along with sshuttle; If not, see <http://www.gnu.org/licenses/>.
from setuptools import setup, find_packages
@ -39,14 +39,14 @@ setup(
author_email='brian@linuxpenguins.xyz',
description='Full-featured" VPN over an SSH tunnel',
packages=find_packages(),
license="GPL2+",
license="LGPL2.1+",
long_description=open('README.rst').read(),
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: "
"GNU General Public License v2 or later (GPLv2+)",
"GNU Lesser General Public License v2 or later (LGPLv2+)",
"Operating System :: OS Independent",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.5",
@ -57,6 +57,12 @@ setup(
'sshuttle = sshuttle.cmdline:main',
],
},
tests_require=['pytest', 'pytest-runner', 'mock'],
tests_require=[
'pytest',
'pytest-cov',
'pytest-runner',
'mock',
'flake8',
],
keywords="ssh vpn",
)

View File

@ -2,6 +2,7 @@ import sys
import zlib
import imp
verbosity = verbosity # noqa: F821 must be a previously defined global
z = zlib.decompressobj()
while 1:
name = sys.stdin.readline().strip()
@ -21,7 +22,7 @@ while 1:
setattr(sys.modules[parent], parent_name, module)
code = compile(content, name, "exec")
exec(code, module.__dict__)
exec(code, module.__dict__) # nosec
sys.modules[name] = module
else:
break
@ -34,4 +35,5 @@ sshuttle.helpers.verbose = verbosity
import sshuttle.cmdline_options as options
from sshuttle.server import main
main(options.latency_control, options.auto_hosts)
main(options.latency_control, options.auto_hosts, options.to_nameserver,
options.auto_nets)

View File

@ -8,13 +8,16 @@ import os
import sshuttle.ssnet as ssnet
import sshuttle.ssh as ssh
import sshuttle.ssyslog as ssyslog
import sshuttle.sdnotify as sdnotify
import sys
import platform
from sshuttle.ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, islocal, \
resolvconf_nameservers
from sshuttle.methods import get_method, Features
try:
from pwd import getpwnam
except ImportError:
getpwnam = None
try:
# try getting recvmsg from python
@ -30,7 +33,7 @@ except AttributeError:
except ImportError:
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):
@ -90,7 +93,7 @@ def daemonize():
# be deleted.
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(), 1)
si.close()
@ -108,8 +111,8 @@ def daemon_cleanup():
class MultiListener:
def __init__(self, type=socket.SOCK_STREAM, proto=0):
self.type = type
def __init__(self, kind=socket.SOCK_STREAM, proto=0):
self.type = kind
self.proto = proto
self.v6 = None
self.v4 = None
@ -157,13 +160,11 @@ class MultiListener:
self.bind_called = True
if address_v6 is not None:
self.v6 = socket.socket(socket.AF_INET6, self.type, self.proto)
self.v6.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.v6.bind(address_v6)
else:
self.v6 = None
if address_v4 is not None:
self.v4 = socket.socket(socket.AF_INET, self.type, self.proto)
self.v4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.v4.bind(address_v4)
else:
self.v4 = None
@ -182,7 +183,13 @@ class MultiListener:
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 = []
python_path = os.path.dirname(os.path.dirname(__file__))
argvbase = ([sys.executable, sys.argv[0]] +
@ -191,11 +198,13 @@ class FirewallClient:
['--firewall'])
if ssyslog._p:
argvbase += ['--syslog']
argv_tries = [
['sudo', '-p', '[local sudo] Password: ',
('PYTHONPATH=%s' % python_path), '--'] + argvbase,
argvbase
]
elev_prefix = [part % {'eb': elevbin}
for part in ['%(eb)s', '-p',
'[local %(eb)s] Password: ']]
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,
# because stupid Linux 'su' requires that stdin be attached to a tty.
@ -238,7 +247,8 @@ class FirewallClient:
self.method.set_firewall(self)
def setup(self, subnets_include, subnets_exclude, nslist,
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp):
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp,
user):
self.subnets_include = subnets_include
self.subnets_exclude = subnets_exclude
self.nslist = nslist
@ -247,6 +257,7 @@ class FirewallClient:
self.dnsport_v6 = dnsport_v6
self.dnsport_v4 = dnsport_v4
self.udp = udp
self.user = user
def check(self):
rv = self.p.poll()
@ -276,8 +287,14 @@ class FirewallClient:
udp = 0
if self.udp:
udp = 1
if self.user is None:
user = b'-'
elif isinstance(self.user, str):
user = bytes(self.user, 'utf-8')
else:
user = b'%d' % self.user
self.pfile.write(b'GO %d\n' % udp)
self.pfile.write(b'GO %d %s\n' % (udp, user))
self.pfile.flush()
line = self.pfile.readline()
@ -286,7 +303,7 @@ class FirewallClient:
raise Fatal('%r expected STARTED, got %r' % (self.argv, line))
def sethostip(self, hostname, ip):
assert(not re.search(b'[^-\w]', hostname))
assert(not re.search(b'[^-\w\.]', hostname))
assert(not re.search(b'[^0-9.]', ip))
self.pfile.write(b'HOST %s,%s\n' % (hostname, ip))
self.pfile.flush()
@ -338,7 +355,7 @@ def onaccept_tcp(listener, method, mux, handlers):
sock, srcip = listener.accept()
sock.close()
finally:
_extra_fd = os.open('/dev/null', os.O_RDONLY)
_extra_fd = os.open(os.devnull, os.O_RDONLY)
return
else:
raise
@ -377,7 +394,7 @@ def onaccept_udp(listener, method, mux, handlers):
srcip, dstip, data = t
debug1('Accept UDP: %r -> %r.\n' % (srcip, dstip,))
if srcip in udp_by_src:
chan, timeout = udp_by_src[srcip]
chan, _ = udp_by_src[srcip]
else:
chan = mux.next_channel()
mux.channels[chan] = lambda cmd, data: udp_done(
@ -415,7 +432,8 @@ def ondns(listener, method, mux, handlers):
def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
python, latency_control,
dns_listener, seed_hosts, auto_hosts, auto_nets, daemon):
dns_listener, seed_hosts, auto_hosts, auto_nets, daemon,
to_nameserver):
debug1('Starting client with Python version %s\n'
% platform.python_version())
@ -434,7 +452,9 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
ssh_cmd, remotename, python,
stderr=ssyslog._p and ssyslog._p.stdin,
options=dict(latency_control=latency_control,
auto_hosts=auto_hosts))
auto_hosts=auto_hosts,
to_nameserver=to_nameserver,
auto_nets=auto_nets))
except socket.error as e:
if e.args[0] == errno.EPIPE:
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):
if auto_nets:
for line in routestr.strip().split(b'\n'):
if not line: continue
(family, ip, width) = line.split(b',', 2)
family = int(family)
width = int(width)
@ -518,9 +539,6 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
debug1('seed_hosts: %r\n' % 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:
rv = serverproc.poll()
if rv:
@ -534,7 +552,8 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
def main(listenip_v6, listenip_v4,
ssh_cmd, remotename, python, latency_control, dns, nslist,
method_name, seed_hosts, auto_hosts, auto_nets,
subnets_include, subnets_exclude, daemon, pidfile):
subnets_include, subnets_exclude, daemon, to_nameserver, pidfile,
user, sudo_pythonpath):
if daemon:
try:
@ -544,11 +563,16 @@ def main(listenip_v6, listenip_v4,
return 5
debug1('Starting sshuttle proxy.\n')
fw = FirewallClient(method_name)
fw = FirewallClient(method_name, sudo_pythonpath)
# Get family specific subnet lists
if dns:
nslist += resolvconf_nameservers()
if to_nameserver is not None:
to_nameserver = "%s@%s" % tuple(to_nameserver[1:])
else:
# option doesn't make sense if we aren't proxying dns
to_nameserver = None
subnets = subnets_include + subnets_exclude # we don't care here
subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6]
@ -566,10 +590,19 @@ def main(listenip_v6, listenip_v4,
else:
listenip_v6 = None
if user is not None:
if getpwnam is None:
raise Fatal("Routing by user not available on this system.")
try:
user = getpwnam(user).pw_uid
except KeyError:
raise Fatal("User %s does not exist." % user)
required.ipv6 = len(subnets_v6) > 0 or listenip_v6 is not None
required.ipv4 = len(subnets_v4) > 0 or listenip_v4 is not None
required.udp = avail.udp
required.dns = len(nslist) > 0
required.user = False if user is None else True
# if IPv6 not supported, ignore IPv6 DNS servers
if not required.ipv6:
@ -585,6 +618,7 @@ def main(listenip_v6, listenip_v4,
debug1("IPv6 enabled: %r\n" % required.ipv6)
debug1("UDP enabled: %r\n" % required.udp)
debug1("DNS enabled: %r\n" % required.dns)
debug1("User enabled: %r\n" % required.user)
# bind to required ports
if listenip_v4 == "auto":
@ -604,6 +638,9 @@ def main(listenip_v6, listenip_v4,
else:
# if at least one port missing, we have to search
ports = range(12300, 9000, -1)
# keep track of failed bindings and used ports to avoid trying to
# bind to the same socket address twice in different listeners
used_ports = []
# search for free ports and try to bind
last_e = None
@ -645,10 +682,12 @@ def main(listenip_v6, listenip_v4,
if udp_listener:
udp_listener.bind(lv6, lv4)
bound = True
used_ports.append(port)
break
except socket.error as e:
if e.errno == errno.EADDRINUSE:
last_e = e
used_ports.append(port)
else:
raise e
@ -668,6 +707,8 @@ def main(listenip_v6, listenip_v4,
ports = range(12300, 9000, -1)
for port in ports:
debug2(' %d' % port)
if port in used_ports: continue
dns_listener = MultiListener(socket.SOCK_DGRAM)
if listenip_v6:
@ -687,10 +728,12 @@ def main(listenip_v6, listenip_v4,
try:
dns_listener.bind(lv6, lv4)
bound = True
used_ports.append(port)
break
except socket.error as e:
if e.errno == errno.EADDRINUSE:
last_e = e
used_ports.append(port)
else:
raise e
debug2('\n')
@ -706,22 +749,22 @@ def main(listenip_v6, listenip_v4,
# Last minute sanity checks.
# These should never fail.
# If these do fail, something is broken above.
if len(subnets_v6) > 0:
if subnets_v6:
assert required.ipv6
if redirectport_v6 == 0:
raise Fatal("IPv6 subnets defined but not listening")
if len(nslist_v6) > 0:
if nslist_v6:
assert required.dns
assert required.ipv6
if dnsport_v6 == 0:
raise Fatal("IPv6 ns servers defined but not listening")
if len(subnets_v4) > 0:
if subnets_v4:
if redirectport_v4 == 0:
raise Fatal("IPv4 subnets defined but not listening")
if len(nslist_v4) > 0:
if nslist_v4:
if dnsport_v4 == 0:
raise Fatal("IPv4 ns servers defined but not listening")
@ -735,13 +778,13 @@ def main(listenip_v6, listenip_v4,
# start the firewall
fw.setup(subnets_include, subnets_exclude, nslist,
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4,
required.udp)
required.udp, user)
# start the client process
try:
return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
python, latency_control, dns_listener,
seed_hosts, auto_hosts, auto_nets, daemon)
seed_hosts, auto_hosts, auto_nets, daemon, to_nameserver)
finally:
try:
if daemon:

View File

@ -25,7 +25,7 @@ def main():
parser.error('exactly zero arguments expected')
return firewall.main(opt.method, opt.syslog)
elif opt.hostwatch:
return hostwatch.hw_main(opt.subnets)
return hostwatch.hw_main(opt.subnets, opt.auto_hosts)
else:
includes = opt.subnets + opt.subnets_file
excludes = opt.exclude
@ -45,8 +45,8 @@ def main():
if opt.listen:
ipport_v6 = None
ipport_v4 = None
list = opt.listen.split(",")
for ip in list:
lst = opt.listen.split(",")
for ip in lst:
family, ip, port = parse_ipport(ip)
if family == socket.AF_INET6:
ipport_v6 = (ip, port)
@ -59,6 +59,8 @@ def main():
ipport_v6 = "auto" if not opt.disable_ipv6 else None
if opt.syslog:
ssyslog.start_syslog()
ssyslog.close_stdin()
ssyslog.stdout_to_syslog()
ssyslog.stderr_to_syslog()
return_code = client.main(ipport_v6, ipport_v4,
opt.ssh_cmd,
@ -73,12 +75,16 @@ def main():
opt.auto_nets,
includes,
excludes,
opt.daemon, opt.pidfile)
opt.daemon,
opt.to_ns,
opt.pidfile,
opt.user,
opt.sudo_pythonpath)
if return_code == 0:
log('Normal exit code, exiting...')
else:
log('Abnormal exit code detected, failing...' % return_code)
log('Abnormal exit code %d detected, failing...' % return_code)
return return_code
except Fatal as e:

View File

@ -2,6 +2,7 @@ import errno
import socket
import signal
import sshuttle.ssyslog as ssyslog
import sshuttle.sdnotify as sdnotify
import sys
import os
import platform
@ -164,7 +165,7 @@ def main(method_name, syslog):
_, _, ports = line.partition(" ")
ports = ports.split(",")
if len(ports) != 4:
raise Fatal('firewall: expected 4 ports but got %n' % len(ports))
raise Fatal('firewall: expected 4 ports but got %d' % len(ports))
port_v6 = int(ports[0])
port_v4 = int(ports[1])
dnsport_v6 = int(ports[2])
@ -188,9 +189,12 @@ def main(method_name, syslog):
elif not line.startswith("GO "):
raise Fatal('firewall: expected GO but got %r' % line)
_, _, udp = line.partition(" ")
_, _, args = line.partition(" ")
udp, user = args.strip().split(" ", 1)
udp = bool(int(udp))
debug2('firewall manager: Got udp: %r\n' % udp)
if user == '-':
user = None
debug2('firewall manager: Got udp: %r, user: %r\n' % (udp, user))
subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6]
nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6]
@ -200,19 +204,23 @@ def main(method_name, syslog):
try:
debug1('firewall manager: setting up.\n')
if len(subnets_v6) > 0 or len(nslist_v6) > 0:
if subnets_v6 or nslist_v6:
debug2('firewall manager: setting up IPv6.\n')
method.setup_firewall(
port_v6, dnsport_v6, nslist_v6,
socket.AF_INET6, subnets_v6, udp)
socket.AF_INET6, subnets_v6, udp,
user)
if len(subnets_v4) > 0 or len(nslist_v4) > 0:
if subnets_v4 or nslist_v4:
debug2('firewall manager: setting up IPv4.\n')
method.setup_firewall(
port_v4, dnsport_v4, nslist_v4,
socket.AF_INET, subnets_v4, udp)
socket.AF_INET, subnets_v4, udp,
user)
stdout.write('STARTED\n')
sdnotify.send(sdnotify.ready(),
sdnotify.status('Connected'))
try:
stdout.flush()
@ -244,9 +252,9 @@ def main(method_name, syslog):
pass
try:
if len(subnets_v6) > 0 or len(nslist_v6) > 0:
if subnets_v6 or nslist_v6:
debug2('firewall manager: undoing IPv6 changes.\n')
method.restore_firewall(port_v6, socket.AF_INET6, udp)
method.restore_firewall(port_v6, socket.AF_INET6, udp, user)
except:
try:
debug1("firewall manager: "
@ -257,9 +265,9 @@ def main(method_name, syslog):
pass
try:
if len(subnets_v4) > 0 or len(nslist_v4) > 0:
if subnets_v4 or nslist_v4:
debug2('firewall manager: undoing IPv4 changes.\n')
method.restore_firewall(port_v4, socket.AF_INET, udp)
method.restore_firewall(port_v4, socket.AF_INET, udp, user)
except:
try:
debug1("firewall manager: "

View File

@ -21,7 +21,7 @@ _smb_ok = True
hostnames = {}
queue = {}
try:
null = open('/dev/null', 'wb')
null = open(os.devnull, 'wb')
except IOError:
_, e = sys.exc_info()[:2]
log('warning: %s\n' % e)
@ -61,23 +61,27 @@ def read_host_cache():
words = line.strip().split(',')
if len(words) == 2:
(name, ip) = words
name = re.sub(r'[^-\w]', '-', name).strip()
name = re.sub(r'[^-\w\.]', '-', name).strip()
ip = re.sub(r'[^0-9.]', '', ip).strip()
if name and ip:
found_host(name, ip)
def found_host(hostname, ip):
hostname = re.sub(r'\..*', '', hostname)
hostname = re.sub(r'[^-\w]', '_', hostname)
def found_host(name, ip):
hostname = re.sub(r'\..*', '', name)
hostname = re.sub(r'[^-\w\.]', '_', hostname)
if (ip.startswith('127.') or ip.startswith('255.') or
hostname == 'localhost'):
return
oldip = hostnames.get(hostname)
if hostname != name:
found_host(hostname, ip)
oldip = hostnames.get(name)
if oldip != ip:
hostnames[hostname] = ip
debug1('Found: %s: %s\n' % (hostname, ip))
sys.stdout.write('%s,%s\n' % (hostname, ip))
hostnames[name] = ip
debug1('Found: %s: %s\n' % (name, ip))
sys.stdout.write('%s,%s\n' % (name, ip))
write_host_cache()
@ -247,7 +251,7 @@ def _enqueue(op, *args):
def _stdin_still_ok(timeout):
r, w, x = select.select([sys.stdin.fileno()], [], [], timeout)
r, _, _ = select.select([sys.stdin.fileno()], [], [], timeout)
if r:
b = os.read(sys.stdin.fileno(), 4096)
if not b:

View File

@ -1,3 +1,4 @@
import re
import os
import socket
import subprocess as ssubprocess
@ -23,13 +24,13 @@ def ipt_chain_exists(family, table, name):
'PATH': os.environ['PATH'],
'LC_ALL': "C",
}
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=env)
for line in p.stdout:
if line.startswith(b'Chain %s ' % name.encode("ASCII")):
try:
output = ssubprocess.check_output(argv, env=env)
for line in output.decode('ASCII').split('\n'):
if line.startswith('Chain %s ' % name):
return True
rv = p.wait()
if rv:
raise Fatal('%r returned %d' % (argv, rv))
except ssubprocess.CalledProcessError as e:
raise Fatal('%r returned %d' % (argv, e.returncode))
def ipt(family, table, *args):
@ -49,6 +50,39 @@ def ipt(family, table, *args):
raise Fatal('%r returned %d' % (argv, rv))
def nft(family, table, action, *args):
if family == socket.AF_INET:
argv = ['nft', action, 'ip', table] + list(args)
elif family == socket.AF_INET6:
argv = ['nft', action, 'ip6', table] + list(args)
else:
raise Exception('Unsupported family "%s"' % family_to_string(family))
debug1('>> %s\n' % ' '.join(argv))
env = {
'PATH': os.environ['PATH'],
'LC_ALL': "C",
}
rv = ssubprocess.call(argv, env=env)
if rv:
raise Fatal('%r returned %d' % (argv, rv))
def nft_get_handle(expression, chain):
cmd = 'nft'
argv = [cmd, 'list', expression, '-a']
env = {
'PATH': os.environ['PATH'],
'LC_ALL': "C",
}
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

View File

@ -35,17 +35,21 @@ class BaseMethod(object):
def set_firewall(self, firewall):
self.firewall = firewall
def get_supported_features(self):
@staticmethod
def get_supported_features():
result = Features()
result.ipv6 = False
result.udp = False
result.dns = True
result.user = False
return result
def get_tcp_dstip(self, sock):
@staticmethod
def get_tcp_dstip(sock):
return original_dst(sock)
def recv_udp(self, udp_listener, bufsize):
@staticmethod
def recv_udp(udp_listener, bufsize):
debug3('Accept UDP using recvfrom.\n')
data, srcip = udp_listener.recvfrom(bufsize)
return (srcip, None, data)
@ -64,19 +68,21 @@ class BaseMethod(object):
def assert_features(self, features):
avail = self.get_supported_features()
for key in ["udp", "dns", "ipv6"]:
for key in ["udp", "dns", "ipv6", "user"]:
if getattr(features, key) and not getattr(avail, key):
raise Fatal(
"Feature %s not supported with method %s.\n" %
(key, self.name))
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
user):
raise NotImplementedError()
def restore_firewall(self, port, family, udp):
def restore_firewall(self, port, family, udp, user):
raise NotImplementedError()
def firewall_command(self, line):
@staticmethod
def firewall_command(line):
return False
@ -96,12 +102,14 @@ def get_method(method_name):
def get_auto_method():
if _program_exists('iptables'):
method_name = "nat"
elif _program_exists('nft'):
method_name = "nft"
elif _program_exists('pfctl'):
method_name = "pf"
elif _program_exists('ipfw'):
method_name = "ipfw"
else:
raise Fatal(
"can't find either iptables or pfctl; check your PATH")
"can't find either iptables, nft or pfctl; check your PATH")
return get_method(method_name)

View File

@ -1,6 +1,4 @@
import os
import sys
import struct
import subprocess as ssubprocess
from sshuttle.methods import BaseMethod
from sshuttle.helpers import log, debug1, debug3, \
@ -31,9 +29,9 @@ IPV6_RECVDSTADDR = 74
if recvmsg == "python":
def recv_udp(listener, bufsize):
debug3('Accept UDP python using recvmsg.\n')
data, ancdata, msg_flags, srcip = listener.recvmsg(4096, socket.CMSG_SPACE(4))
data, ancdata, _, srcip = \
listener.recvmsg(4096, socket.CMSG_SPACE(4))
dstip = None
family = None
for cmsg_level, cmsg_type, cmsg_data in ancdata:
if cmsg_level == socket.SOL_IP and cmsg_type == IP_RECVDSTADDR:
port = 53
@ -44,13 +42,13 @@ if recvmsg == "python":
elif recvmsg == "socket_ext":
def recv_udp(listener, bufsize):
debug3('Accept UDP using socket_ext recvmsg.\n')
srcip, data, adata, flags = listener.recvmsg((bufsize,), socket.CMSG_SPACE(4))
srcip, data, adata, _ = \
listener.recvmsg((bufsize,), socket.CMSG_SPACE(4))
dstip = None
family = None
for a in adata:
if a.cmsg_level == socket.SOL_IP and a.cmsg_type == IP_RECVDSTADDR:
port = 53
ip = socket.inet_ntop(socket.AF_INET, cmsg_data[0:4])
ip = socket.inet_ntop(socket.AF_INET, a.cmsg_data[0:4])
dstip = (ip, port)
break
return (srcip, dstip, data[0])
@ -107,7 +105,7 @@ def _fill_oldctls(prefix):
def _sysctl_set(name, val):
argv = ['sysctl', '-w', '%s=%s' % (name, val)]
debug1('>> %s\n' % ' '.join(argv))
return ssubprocess.call(argv, stdout=open('/dev/null', 'w'))
return ssubprocess.call(argv, stdout=open(os.devnull, 'w'))
_changedctls = []
@ -193,9 +191,10 @@ class Method(BaseMethod):
#if udp_listener.v6 is not None:
# udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVDSTADDR, 1)
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
user):
# IPv6 not supported
if family not in [socket.AF_INET ]:
if family not in [socket.AF_INET]:
raise Exception(
'Address family "%s" unsupported by ipfw method_name'
% family_to_string(family))
@ -224,7 +223,7 @@ class Method(BaseMethod):
ipfw_noexit('table', '124', 'flush')
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))
dnscount += 1
if dnscount > 0:
@ -232,15 +231,6 @@ class Method(BaseMethod):
'udp',
'from', 'any', 'to', 'table(124)',
'not', 'ipttl', '42')
"""if udp:
ipfw('add', '1', 'skipto', '2',
'udp',
'from', 'any', 'to', 'table(125)')
ipfw('add', '1', 'fwd', '127.0.0.1,%d' % port,
'udp',
'from', 'any', 'to', 'table(126)',
'not', 'ipttl', '42')
"""
ipfw('add', '1', 'allow',
'udp',
'from', 'any', 'to', 'any',
@ -248,14 +238,14 @@ class Method(BaseMethod):
if subnets:
# create new subnet entries
for f, swidth, sexclude, snet \
for _, swidth, sexclude, snet \
in sorted(subnets, key=lambda s: s[1], reverse=True):
if sexclude:
ipfw('table', '125', 'add', '%s/%s' % (snet, swidth))
else:
ipfw('table', '126', 'add', '%s/%s' % (snet, swidth))
def restore_firewall(self, port, family, udp):
def restore_firewall(self, port, family, udp, user):
if family not in [socket.AF_INET]:
raise Exception(
'Address family "%s" unsupported by tproxy method'
@ -265,4 +255,3 @@ class Method(BaseMethod):
ipfw_noexit('table', '124', 'flush')
ipfw_noexit('table', '125', 'flush')
ipfw_noexit('table', '126', 'flush')

View File

@ -12,7 +12,8 @@ class Method(BaseMethod):
# the multiple copies shouldn't have overlapping subnets, or only the most-
# recently-started one will win (because we use "-I OUTPUT 1" instead of
# "-A OUTPUT").
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
user):
# only ipv4 supported with NAT
if family != socket.AF_INET:
raise Exception(
@ -29,18 +30,28 @@ class Method(BaseMethod):
def _ipt_ttl(*args):
return ipt_ttl(family, table, *args)
def _ipm(*args):
return ipt(family, "mangle", *args)
chain = 'sshuttle-%s' % port
# basic cleanup/setup of chains
self.restore_firewall(port, family, udp)
self.restore_firewall(port, family, udp, user)
_ipt('-N', chain)
_ipt('-F', chain)
_ipt('-I', 'OUTPUT', '1', '-j', chain)
_ipt('-I', 'PREROUTING', '1', '-j', chain)
if user is not None:
_ipm('-I', 'OUTPUT', '1', '-m', 'owner', '--uid-owner', str(user),
'-j', 'MARK', '--set-mark', str(port))
args = '-m', 'mark', '--mark', str(port), '-j', chain
else:
args = '-j', chain
_ipt('-I', 'OUTPUT', '1', *args)
_ipt('-I', 'PREROUTING', '1', *args)
# create new subnet entries.
for f, swidth, sexclude, snet, fport, lport \
for _, swidth, sexclude, snet, fport, lport \
in sorted(subnets, key=subnet_weight, reverse=True):
tcp_ports = ('-p', 'tcp')
if fport:
@ -55,14 +66,14 @@ class Method(BaseMethod):
'--dest', '%s/%s' % (snet, swidth),
*(tcp_ports + ('--to-ports', str(port))))
for f, ip in [i for i in nslist if i[0] == family]:
for _, ip in [i for i in nslist if i[0] == family]:
_ipt_ttl('-A', chain, '-j', 'REDIRECT',
'--dest', '%s/32' % ip,
'-p', 'udp',
'--dport', '53',
'--to-ports', str(dnsport))
def restore_firewall(self, port, family, udp):
def restore_firewall(self, port, family, udp, user):
# only ipv4 supported with NAT
if family != socket.AF_INET:
raise Exception(
@ -79,11 +90,25 @@ class Method(BaseMethod):
def _ipt_ttl(*args):
return ipt_ttl(family, table, *args)
def _ipm(*args):
return ipt(family, "mangle", *args)
chain = 'sshuttle-%s' % port
# basic cleanup/setup of chains
if ipt_chain_exists(family, table, chain):
nonfatal(_ipt, '-D', 'OUTPUT', '-j', chain)
nonfatal(_ipt, '-D', 'PREROUTING', '-j', chain)
if user is not None:
nonfatal(_ipm, '-D', 'OUTPUT', '-m', 'owner', '--uid-owner',
str(user), '-j', 'MARK', '--set-mark', str(port))
args = '-m', 'mark', '--mark', str(port), '-j', chain
else:
args = '-j', chain
nonfatal(_ipt, '-D', 'OUTPUT', *args)
nonfatal(_ipt, '-D', 'PREROUTING', *args)
nonfatal(_ipt, '-F', chain)
_ipt('-X', chain)
def get_supported_features(self):
result = super(Method, self).get_supported_features()
result.user = True
return result

84
sshuttle/methods/nft.py Normal file
View 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)

View File

@ -14,7 +14,11 @@ from sshuttle.helpers import debug1, debug2, debug3, Fatal, family_to_string
from sshuttle.methods import BaseMethod
_pf_context = {'started_by_sshuttle': False, 'Xtoken': []}
_pf_context = {
'started_by_sshuttle': 0,
'loaded_by_sshuttle': True,
'Xtoken': []
}
_pf_fd = None
@ -60,13 +64,14 @@ class Generic(object):
def enable(self):
if b'INFO:\nStatus: Disabled' in self.status:
pfctl('-e')
_pf_context['started_by_sshuttle'] = True
_pf_context['started_by_sshuttle'] += 1
def disable(self, anchor):
@staticmethod
def disable(anchor):
pfctl('-a %s -F all' % anchor)
if _pf_context['started_by_sshuttle']:
if _pf_context['started_by_sshuttle'] == 1:
pfctl('-d')
_pf_context['started_by_sshuttle'] = False
_pf_context['started_by_sshuttle'] -= 1
def query_nat(self, family, proto, src_ip, src_port, dst_ip, dst_port):
[proto, family, src_port, dst_port] = [
@ -94,11 +99,13 @@ class Generic(object):
port = socket.ntohs(self._get_natlook_port(pnl.rdxport))
return (ip, port)
def _add_natlook_ports(self, pnl, src_port, dst_port):
@staticmethod
def _add_natlook_ports(pnl, src_port, dst_port):
pnl.sxport = socket.htons(src_port)
pnl.dxport = socket.htons(dst_port)
def _get_natlook_port(self, xport):
@staticmethod
def _get_natlook_port(xport):
return xport
def add_anchors(self, anchor, status=None):
@ -108,14 +115,14 @@ class Generic(object):
if ('\nanchor "%s"' % anchor).encode('ASCII') not in status:
self._add_anchor_rule(self.PF_PASS, anchor.encode('ASCII'))
def _add_anchor_rule(self, type, name, pr=None):
def _add_anchor_rule(self, kind, name, pr=None):
if pr is None:
pr = self.pfioc_rule()
memmove(addressof(pr) + self.ANCHOR_CALL_OFFSET, name,
min(self.MAXPATHLEN, len(name))) # anchor_call = name
memmove(addressof(pr) + self.RULE_ACTION_OFFSET,
struct.pack('I', type), 4) # rule.action = type
struct.pack('I', kind), 4) # rule.action = kind
memmove(addressof(pr) + self.ACTION_OFFSET, struct.pack(
'I', self.PF_CHANGE_GET_TICKET), 4) # action = PF_CHANGE_GET_TICKET
@ -125,18 +132,22 @@ class Generic(object):
'I', self.PF_CHANGE_ADD_TAIL), 4) # action = PF_CHANGE_ADD_TAIL
ioctl(pf_get_dev(), pf.DIOCCHANGERULE, pr)
def _inet_version(self, family):
@staticmethod
def _inet_version(family):
return b'inet' if family == socket.AF_INET else b'inet6'
def _lo_addr(self, family):
@staticmethod
def _lo_addr(family):
return b'127.0.0.1' if family == socket.AF_INET else b'::1'
def add_rules(self, anchor, rules):
@staticmethod
def add_rules(anchor, rules):
assert isinstance(rules, bytes)
debug3("rules:\n" + rules.decode("ASCII"))
pfctl('-a %s -f /dev/stdin' % anchor, rules)
def has_skip_loopback(self):
@staticmethod
def has_skip_loopback():
return b'skip' in pfctl('-s Interfaces -i lo -v')[0]
@ -165,8 +176,17 @@ class FreeBsd(Generic):
freebsd.pfioc_natlook = pfioc_natlook
return freebsd
def __init__(self):
super(FreeBsd, self).__init__()
def enable(self):
returncode = ssubprocess.call(['kldload', 'pf'])
super(FreeBsd, self).enable()
if returncode == 0:
_pf_context['loaded_by_sshuttle'] = True
def disable(self, anchor):
super(FreeBsd, self).disable(anchor)
if _pf_context['loaded_by_sshuttle'] and \
_pf_context['started_by_sshuttle'] == 0:
ssubprocess.call(['kldunload', 'pf'])
def add_anchors(self, anchor):
status = pfctl('-s all')[0]
@ -174,14 +194,14 @@ class FreeBsd(Generic):
self._add_anchor_rule(self.PF_RDR, anchor.encode('ASCII'))
super(FreeBsd, self).add_anchors(anchor, status=status)
def _add_anchor_rule(self, type, name):
pr = self.pfioc_rule()
def _add_anchor_rule(self, kind, name, pr=None):
pr = pr or self.pfioc_rule()
ppa = self.pfioc_pooladdr()
ioctl(pf_get_dev(), self.DIOCBEGINADDRS, ppa)
# pool ticket
memmove(addressof(pr) + self.POOL_TICKET_OFFSET, ppa[4:8], 4)
super(FreeBsd, self)._add_anchor_rule(type, name, pr=pr)
super(FreeBsd, self)._add_anchor_rule(kind, name, pr=pr)
def add_rules(self, anchor, includes, port, dnsport, nslist, family):
inet_version = self._inet_version(family)
@ -189,19 +209,19 @@ class FreeBsd(Generic):
tables = []
translating_rules = [
b'rdr pass on lo0 %s proto tcp to %s '
b'-> %s port %r' % (inet_version, subnet, lo_addr, port)
b'rdr pass on lo0 %s proto tcp from ! %s to %s '
b'-> %s port %r' % (inet_version, lo_addr, subnet, lo_addr, port)
for exclude, subnet in includes if not exclude
]
filtering_rules = [
b'pass out route-to lo0 %s proto tcp '
b'to %s keep state' % (inet_version, subnet)
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
]
if len(nslist) > 0:
if nslist:
tables.append(
b'table <dns_servers> {%s}' %
b','.join([ns[1].encode("ASCII") for ns in nslist]))
@ -241,7 +261,7 @@ class OpenBsd(Generic):
("proto_variant", c_uint8),
("direction", c_uint8)]
self.pfioc_rule = c_char * 3400
self.pfioc_rule = c_char * 3416
self.pfioc_natlook = pfioc_natlook
super(OpenBsd, self).__init__()
@ -267,11 +287,11 @@ class OpenBsd(Generic):
b'pass out %s proto tcp to %s '
b'route-to lo0 keep state' % (inet_version, subnet)
if not exclude else
b'pass out quick %s proto tcp to %s' % (inet_version, subnet)
b'pass out %s proto tcp to %s' % (inet_version, subnet)
for exclude, subnet in includes
]
if len(nslist) > 0:
if nslist:
tables.append(
b'table <dns_servers> {%s}' %
b','.join([ns[1].encode("ASCII") for ns in nslist]))
@ -417,11 +437,8 @@ class Method(BaseMethod):
return sock.getsockname()
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
tables = []
translating_rules = []
filtering_rules = []
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
user):
if family not in [socket.AF_INET, socket.AF_INET6]:
raise Exception(
'Address family "%s" unsupported by pf method_name'
@ -429,13 +446,13 @@ class Method(BaseMethod):
if udp:
raise Exception("UDP not supported by pf method_name")
if len(subnets) > 0:
if subnets:
includes = []
# If a given subnet is both included and excluded, list the
# exclusion first; the table will ignore the second, opposite
# definition
for f, swidth, sexclude, snet, fport, lport \
in sorted(subnets, key=subnet_weight, reverse=True):
for _, swidth, sexclude, snet, fport, lport \
in sorted(subnets, key=subnet_weight):
includes.append((sexclude, b"%s/%d%s" % (
snet.encode("ASCII"),
swidth,
@ -446,7 +463,7 @@ class Method(BaseMethod):
pf.add_rules(anchor, includes, port, dnsport, nslist, family)
pf.enable()
def restore_firewall(self, port, family, udp):
def restore_firewall(self, port, family, udp, user):
if family not in [socket.AF_INET, socket.AF_INET6]:
raise Exception(
'Address family "%s" unsupported by pf method_name'

View File

@ -33,7 +33,7 @@ IPV6_RECVORIGDSTADDR = IPV6_ORIGDSTADDR
if recvmsg == "python":
def recv_udp(listener, bufsize):
debug3('Accept UDP python using recvmsg.\n')
data, ancdata, msg_flags, srcip = listener.recvmsg(
data, ancdata, _, srcip = listener.recvmsg(
4096, socket.CMSG_SPACE(24))
dstip = None
family = None
@ -64,7 +64,7 @@ if recvmsg == "python":
elif recvmsg == "socket_ext":
def recv_udp(listener, bufsize):
debug3('Accept UDP using socket_ext recvmsg.\n')
srcip, data, adata, flags = listener.recvmsg(
srcip, data, adata, _ = listener.recvmsg(
(bufsize,), socket.CMSG_SPACE(24))
dstip = None
family = None
@ -150,7 +150,8 @@ class Method(BaseMethod):
if udp_listener.v6 is not None:
udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
user):
if family not in [socket.AF_INET, socket.AF_INET6]:
raise Exception(
'Address family "%s" unsupported by tproxy method'
@ -174,7 +175,7 @@ class Method(BaseMethod):
divert_chain = 'sshuttle-d-%s' % port
# basic cleanup/setup of chains
self.restore_firewall(port, family, udp)
self.restore_firewall(port, family, udp, user)
_ipt('-N', mark_chain)
_ipt('-F', mark_chain)
@ -193,7 +194,7 @@ class Method(BaseMethod):
_ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain,
'-m', 'udp', '-p', 'udp')
for f, ip in [i for i in nslist if i[0] == family]:
for _, ip in [i for i in nslist if i[0] == family]:
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
'--dest', '%s/32' % ip,
'-m', 'udp', '-p', 'udp', '--dport', '53')
@ -203,7 +204,7 @@ class Method(BaseMethod):
'-m', 'udp', '-p', 'udp', '--dport', '53',
'--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):
tcp_ports = ('-p', 'tcp')
tcp_ports = _ipt_proto_ports(tcp_ports, fport, lport)
@ -251,7 +252,7 @@ class Method(BaseMethod):
'-m', 'udp',
*(udp_ports + ('--on-port', str(port))))
def restore_firewall(self, port, family, udp):
def restore_firewall(self, port, family, udp, user):
if family not in [socket.AF_INET, socket.AF_INET6]:
raise Exception(
'Address family "%s" unsupported by tproxy method'

View File

@ -13,9 +13,9 @@ def parse_subnetport_file(s):
raw_config_lines = handle.readlines()
subnets = []
for line_no, line in enumerate(raw_config_lines):
for _, line in enumerate(raw_config_lines):
line = line.strip()
if len(line) == 0:
if not line:
continue
if line[0] == '#':
continue
@ -81,8 +81,8 @@ def parse_ipport(s):
return (family,) + addr[:2]
def parse_list(list):
return re.split(r'[\s,]+', list.strip()) if list else []
def parse_list(lst):
return re.split(r'[\s,]+', lst.strip()) if lst else []
class Concat(Action):
@ -98,7 +98,8 @@ class Concat(Action):
parser = ArgumentParser(
prog="sshuttle",
usage="%(prog)s [-l [ip:]port] [-r [user@]sshserver[:port]] <subnets...>"
usage="%(prog)s [-l [ip:]port] [-r [user@]sshserver[:port]] <subnets...>",
fromfile_prefix_chars="@"
)
parser.add_argument(
"subnets",
@ -120,7 +121,8 @@ parser.add_argument(
"-H", "--auto-hosts",
action="store_true",
help="""
continuously scan for remote hostnames and update local /etc/hosts as they are found
continuously scan for remote hostnames and update local /etc/hosts as
they are found
"""
)
parser.add_argument(
@ -146,9 +148,19 @@ parser.add_argument(
capture and forward DNS requests made to the following servers
"""
)
parser.add_argument(
"--to-ns",
metavar="IP[:PORT]",
type=parse_ipport,
help="""
the DNS server to forward requests to; defaults to servers in
/etc/resolv.conf on remote side if not given.
"""
)
parser.add_argument(
"--method",
choices=["auto", "nat", "tproxy", "pf", "ipfw"],
choices=["auto", "nat", "nft", "tproxy", "pf", "ipfw"],
metavar="TYPE",
default="auto",
help="""
@ -218,7 +230,8 @@ parser.add_argument(
metavar="HOSTNAME[,HOSTNAME]",
default=[],
help="""
comma-separated list of hostnames for initial scan (may be used with or without --auto-hosts)
comma-separated list of hostnames for initial scan (may be used with
or without --auto-hosts)
"""
)
parser.add_argument(
@ -277,6 +290,12 @@ parser.add_argument(
pidfile name (only if using --daemon) [%(default)s]
"""
)
parser.add_argument(
"--user",
help="""
apply all the rules only to this linux user
"""
)
parser.add_argument(
"--firewall",
action="store_true",
@ -291,3 +310,11 @@ parser.add_argument(
(internal use only)
"""
)
parser.add_argument(
"--no-sudo-pythonpath",
action="store_false",
dest="sudo_pythonpath",
help="""
do not set PYTHONPATH when invoking sudo
"""
)

View File

@ -160,7 +160,7 @@ class Hostwatch:
class DnsProxy(Handler):
def __init__(self, mux, chan, request):
def __init__(self, mux, chan, request, to_nameserver):
Handler.__init__(self, [])
self.timeout = time.time() + 30
self.mux = mux
@ -168,22 +168,43 @@ class DnsProxy(Handler):
self.tries = 0
self.request = request
self.peers = {}
self.to_ns_peer = None
self.to_ns_port = None
if to_nameserver is None:
self.to_nameserver = None
else:
self.to_ns_peer, self.to_ns_port = to_nameserver.split("@")
self.to_nameserver = self._addrinfo(self.to_ns_peer,
self.to_ns_port)
self.try_send()
@staticmethod
def _addrinfo(peer, port):
if int(port) == 0:
port = 53
family, _, _, _, sockaddr = socket.getaddrinfo(peer, port)[0]
return (family, sockaddr)
def try_send(self):
if self.tries >= 3:
return
self.tries += 1
family, peer = resolvconf_random_nameserver()
if self.to_nameserver is None:
_, peer = resolvconf_random_nameserver()
port = 53
else:
peer = self.to_ns_peer
port = int(self.to_ns_port)
family, sockaddr = self._addrinfo(peer, port)
sock = socket.socket(family, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
sock.connect((peer, 53))
sock.connect(sockaddr)
self.peers[sock] = peer
debug2('DNS: sending to %r (try %d)\n' % (peer, self.tries))
debug2('DNS: sending to %r:%d (try %d)\n' % (peer, port, self.tries))
try:
sock.send(self.request)
self.socks.append(sock)
@ -258,7 +279,7 @@ class UdpProxy(Handler):
self.mux.send(self.chan, ssnet.CMD_UDP_DATA, hdr + data)
def main(latency_control, auto_hosts):
def main(latency_control, auto_hosts, to_nameserver, auto_nets):
debug1('Starting server with Python version %s\n'
% platform.python_version())
@ -268,10 +289,6 @@ def main(latency_control, auto_hosts):
helpers.logprefix = 'server: '
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
sys.stdout.write('\0\0SSHUTTLE0001')
@ -283,6 +300,16 @@ def main(latency_control, auto_hosts):
socket.fromfd(sys.stdout.fileno(),
socket.AF_INET, socket.SOCK_STREAM))
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 = ''
for r in routes:
routepkt += '%d,%s,%d\n' % r
@ -309,7 +336,7 @@ def main(latency_control, auto_hosts):
def got_host_req(data):
if not hw.pid:
(hw.pid, hw.sock) = start_hostwatch(
data.strip().split(), auto_hosts)
data.decode("ASCII").strip().split(), auto_hosts)
handlers.append(Handler(socks=[hw.sock],
callback=hostwatch_ready))
mux.got_host_req = got_host_req
@ -332,7 +359,7 @@ def main(latency_control, auto_hosts):
def dns_req(channel, data):
debug2('Incoming DNS request channel=%d.\n' % channel)
h = DnsProxy(mux, channel, data)
h = DnsProxy(mux, channel, data, to_nameserver)
handlers.append(h)
dnshandlers[channel] = h
mux.got_dns_req = dns_req

View File

@ -69,7 +69,7 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
rhost = result[0].strip('[')
if len(result) > 1:
result[1] = result[1].strip(':')
if result[1] is not '':
if result[1] != '':
portl = ['-p', str(int(result[1]))]
# can't disambiguate IPv6 colons and a port number. pass the hostname
# through.
@ -116,8 +116,8 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
if python:
pycmd = "'%s' -c '%s'" % (python, pyscript)
else:
pycmd = ("P=python3.5; $P -V 2>/dev/null || P=python; "
"exec \"$P\" -c %s") % quote(pyscript)
pycmd = ("P=python3; $P -V 2>%s || P=python; "
"exec \"$P\" -c %s") % (os.devnull, quote(pyscript))
pycmd = ("exec /bin/sh -c %s" % quote(pycmd))
argv = (sshl +
portl +

View File

@ -95,6 +95,8 @@ def _try_peername(sock):
_, e = sys.exc_info()[:2]
if e.args[0] not in (errno.ENOTCONN, errno.ENOTSOCK):
raise
except AttributeError:
pass
return 'unknown'
@ -200,7 +202,8 @@ class SockWrapper:
_, e = sys.exc_info()[:2]
self.seterr('nowrite: %s' % e)
def too_full(self):
@staticmethod
def too_full():
return False # fullness is determined by the socket's select() state
def uwrite(self, buf):
@ -270,7 +273,7 @@ class Handler:
def callback(self, sock):
log('--no callback defined-- %r\n' % self)
(r, w, x) = select.select(self.socks, [], [], 0)
(r, _, _) = select.select(self.socks, [], [], 0)
for s in r:
v = s.recv(4096)
if not v:
@ -349,7 +352,7 @@ class Mux(Handler):
def next_channel(self):
# channel 0 is special, so we never allocate it
for timeout in range(1024):
for _ in range(1024):
self.chani += 1
if self.chani > MAX_CHANNEL:
self.chani = 1
@ -478,7 +481,7 @@ class Mux(Handler):
_add(w, self.wsock)
def callback(self, sock):
(r, w, x) = select.select([self.rsock], [self.wsock], [], 0)
(r, w, _) = select.select([self.rsock], [self.wsock], [], 0)
if self.rsock in r:
self.handle()
if self.outbuf and self.wsock in w:

View File

@ -8,12 +8,24 @@ _p = None
def start_syslog():
global _p
_p = ssubprocess.Popen(['logger',
'-p', 'daemon.notice',
'-t', 'sshuttle'], stdin=ssubprocess.PIPE)
with open(os.devnull, 'w') as devnull:
_p = ssubprocess.Popen(
['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():
sys.stdout.flush()
sys.stderr.flush()
os.dup2(_p.stdin.fileno(), 2)
os.dup2(_p.stdin.fileno(), sys.stderr.fileno())

View File

@ -1,23 +1,23 @@
from mock import Mock, patch, call
import io
import socket
from socket import AF_INET, AF_INET6
import sshuttle.firewall
def setup_daemon():
stdin = io.StringIO(u"""ROUTES
2,24,0,1.2.3.0,8000,9000
2,32,1,1.2.3.66,8080,8080
10,64,0,2404:6800:4004:80c::,0,0
10,128,1,2404:6800:4004:80c::101f,80,80
{inet},24,0,1.2.3.0,8000,9000
{inet},32,1,1.2.3.66,8080,8080
{inet6},64,0,2404:6800:4004:80c::,0,0
{inet6},128,1,2404:6800:4004:80c::101f,80,80
NSLIST
2,1.2.3.33
10,2404:6800:4004:80c::33
{inet},1.2.3.33
{inet6},2404:6800:4004:80c::33
PORTS 1024,1025,1026,1027
GO 1
GO 1 -
HOST 1.2.3.3,existing
""")
""".format(inet=AF_INET, inet6=AF_INET6))
stdout = Mock()
return stdin, stdout
@ -61,28 +61,28 @@ def test_rewrite_etc_hosts(tmpdir):
def test_subnet_weight():
subnets = [
(socket.AF_INET, 16, 0, '192.168.0.0', 0, 0),
(socket.AF_INET, 24, 0, '192.168.69.0', 0, 0),
(socket.AF_INET, 32, 0, '192.168.69.70', 0, 0),
(socket.AF_INET, 32, 1, '192.168.69.70', 0, 0),
(socket.AF_INET, 32, 1, '192.168.69.70', 80, 80),
(socket.AF_INET, 0, 1, '0.0.0.0', 0, 0),
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 9000),
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 8500),
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 8000),
(socket.AF_INET, 0, 1, '0.0.0.0', 400, 450)
(AF_INET, 16, 0, '192.168.0.0', 0, 0),
(AF_INET, 24, 0, '192.168.69.0', 0, 0),
(AF_INET, 32, 0, '192.168.69.70', 0, 0),
(AF_INET, 32, 1, '192.168.69.70', 0, 0),
(AF_INET, 32, 1, '192.168.69.70', 80, 80),
(AF_INET, 0, 1, '0.0.0.0', 0, 0),
(AF_INET, 0, 1, '0.0.0.0', 8000, 9000),
(AF_INET, 0, 1, '0.0.0.0', 8000, 8500),
(AF_INET, 0, 1, '0.0.0.0', 8000, 8000),
(AF_INET, 0, 1, '0.0.0.0', 400, 450)
]
subnets_sorted = [
(socket.AF_INET, 32, 1, '192.168.69.70', 80, 80),
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 8000),
(socket.AF_INET, 0, 1, '0.0.0.0', 400, 450),
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 8500),
(socket.AF_INET, 0, 1, '0.0.0.0', 8000, 9000),
(socket.AF_INET, 32, 1, '192.168.69.70', 0, 0),
(socket.AF_INET, 32, 0, '192.168.69.70', 0, 0),
(socket.AF_INET, 24, 0, '192.168.69.0', 0, 0),
(socket.AF_INET, 16, 0, '192.168.0.0', 0, 0),
(socket.AF_INET, 0, 1, '0.0.0.0', 0, 0)
(AF_INET, 32, 1, '192.168.69.70', 80, 80),
(AF_INET, 0, 1, '0.0.0.0', 8000, 8000),
(AF_INET, 0, 1, '0.0.0.0', 400, 450),
(AF_INET, 0, 1, '0.0.0.0', 8000, 8500),
(AF_INET, 0, 1, '0.0.0.0', 8000, 9000),
(AF_INET, 32, 1, '192.168.69.70', 0, 0),
(AF_INET, 32, 0, '192.168.69.70', 0, 0),
(AF_INET, 24, 0, '192.168.69.0', 0, 0),
(AF_INET, 16, 0, '192.168.0.0', 0, 0),
(AF_INET, 0, 1, '0.0.0.0', 0, 0)
]
assert subnets_sorted == \
@ -117,18 +117,20 @@ def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts):
call('not_auto'),
call().setup_firewall(
1024, 1026,
[(10, u'2404:6800:4004:80c::33')],
10,
[(10, 64, False, u'2404:6800:4004:80c::', 0, 0),
(10, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
True),
[(AF_INET6, u'2404:6800:4004:80c::33')],
AF_INET6,
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 0, 0),
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
True,
None),
call().setup_firewall(
1025, 1027,
[(2, u'1.2.3.33')],
2,
[(2, 24, False, u'1.2.3.0', 8000, 9000),
(2, 32, True, u'1.2.3.66', 8080, 8080)],
True),
call().restore_firewall(1024, 10, True),
call().restore_firewall(1025, 2, True),
[(AF_INET, u'1.2.3.33')],
AF_INET,
[(AF_INET, 24, False, u'1.2.3.0', 8000, 9000),
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
True,
None),
call().restore_firewall(1024, AF_INET6, True, None),
call().restore_firewall(1025, AF_INET, True, None),
]

View File

@ -2,6 +2,8 @@ from mock import patch, call
import sys
import io
import socket
from socket import AF_INET, AF_INET6
import errno
import sshuttle.helpers
@ -132,10 +134,12 @@ nameserver 2404:6800:4004:80c::4
ns = sshuttle.helpers.resolvconf_nameservers()
assert ns == [
(2, u'192.168.1.1'), (2, u'192.168.2.1'),
(2, u'192.168.3.1'), (2, u'192.168.4.1'),
(10, u'2404:6800:4004:80c::1'), (10, u'2404:6800:4004:80c::2'),
(10, u'2404:6800:4004:80c::3'), (10, u'2404:6800:4004:80c::4')
(AF_INET, u'192.168.1.1'), (AF_INET, u'192.168.2.1'),
(AF_INET, u'192.168.3.1'), (AF_INET, u'192.168.4.1'),
(AF_INET6, u'2404:6800:4004:80c::1'),
(AF_INET6, u'2404:6800:4004:80c::2'),
(AF_INET6, u'2404:6800:4004:80c::3'),
(AF_INET6, u'2404:6800:4004:80c::4')
]
@ -155,34 +159,40 @@ nameserver 2404:6800:4004:80c::4
""")
ns = sshuttle.helpers.resolvconf_random_nameserver()
assert ns in [
(2, u'192.168.1.1'), (2, u'192.168.2.1'),
(2, u'192.168.3.1'), (2, u'192.168.4.1'),
(10, u'2404:6800:4004:80c::1'), (10, u'2404:6800:4004:80c::2'),
(10, u'2404:6800:4004:80c::3'), (10, u'2404:6800:4004:80c::4')
(AF_INET, u'192.168.1.1'), (AF_INET, u'192.168.2.1'),
(AF_INET, u'192.168.3.1'), (AF_INET, u'192.168.4.1'),
(AF_INET6, u'2404:6800:4004:80c::1'),
(AF_INET6, u'2404:6800:4004:80c::2'),
(AF_INET6, u'2404:6800:4004:80c::3'),
(AF_INET6, u'2404:6800:4004:80c::4')
]
def test_islocal():
assert sshuttle.helpers.islocal("127.0.0.1", socket.AF_INET)
assert not sshuttle.helpers.islocal("192.0.2.1", socket.AF_INET)
assert sshuttle.helpers.islocal("::1", socket.AF_INET6)
assert not sshuttle.helpers.islocal("2001:db8::1", socket.AF_INET6)
@patch('sshuttle.helpers.socket.socket.bind')
def test_islocal(mock_bind):
bind_error = socket.error(errno.EADDRNOTAVAIL)
mock_bind.side_effect = [None, bind_error, None, bind_error]
assert sshuttle.helpers.islocal("127.0.0.1", AF_INET)
assert not sshuttle.helpers.islocal("192.0.2.1", AF_INET)
assert sshuttle.helpers.islocal("::1", AF_INET6)
assert not sshuttle.helpers.islocal("2001:db8::1", AF_INET6)
def test_family_ip_tuple():
assert sshuttle.helpers.family_ip_tuple("127.0.0.1") \
== (socket.AF_INET, "127.0.0.1")
== (AF_INET, "127.0.0.1")
assert sshuttle.helpers.family_ip_tuple("192.168.2.6") \
== (socket.AF_INET, "192.168.2.6")
== (AF_INET, "192.168.2.6")
assert sshuttle.helpers.family_ip_tuple("::1") \
== (socket.AF_INET6, "::1")
== (AF_INET6, "::1")
assert sshuttle.helpers.family_ip_tuple("2404:6800:4004:80c::1") \
== (socket.AF_INET6, "2404:6800:4004:80c::1")
== (AF_INET6, "2404:6800:4004:80c::1")
def test_family_to_string():
assert sshuttle.helpers.family_to_string(socket.AF_INET) == "AF_INET"
assert sshuttle.helpers.family_to_string(socket.AF_INET6) == "AF_INET6"
assert sshuttle.helpers.family_to_string(AF_INET) == "AF_INET"
assert sshuttle.helpers.family_to_string(AF_INET6) == "AF_INET6"
if sys.version_info < (3, 0):
expected = "1"
assert sshuttle.helpers.family_to_string(socket.AF_UNIX) == "1"

View File

@ -1,6 +1,7 @@
import pytest
from mock import Mock, patch, call
import socket
from socket import AF_INET, AF_INET6
import struct
from sshuttle.helpers import Fatal
@ -18,7 +19,7 @@ def test_get_supported_features():
def test_get_tcp_dstip():
sock = Mock()
sock.getsockopt.return_value = struct.pack(
'!HHBBBB', socket.ntohs(socket.AF_INET), 1024, 127, 0, 0, 1)
'!HHBBBB', socket.ntohs(AF_INET), 1024, 127, 0, 0, 1)
method = get_method('nat')
assert method.get_tcp_dstip(sock) == ('127.0.0.1', 1024)
assert sock.mock_calls == [call.getsockopt(0, 80, 16)]
@ -84,11 +85,12 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
with pytest.raises(Exception) as excinfo:
method.setup_firewall(
1024, 1026,
[(10, u'2404:6800:4004:80c::33')],
10,
[(10, 64, False, u'2404:6800:4004:80c::', 0, 0),
(10, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
True)
[(AF_INET6, u'2404:6800:4004:80c::33')],
AF_INET6,
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 0, 0),
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
True,
None)
assert str(excinfo.value) \
== 'Address family "AF_INET6" unsupported by nat method_name'
assert mock_ipt_chain_exists.mock_calls == []
@ -98,11 +100,12 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
with pytest.raises(Exception) as excinfo:
method.setup_firewall(
1025, 1027,
[(2, u'1.2.3.33')],
2,
[(2, 24, False, u'1.2.3.0', 8000, 9000),
(2, 32, True, u'1.2.3.66', 8080, 8080)],
True)
[(AF_INET, u'1.2.3.33')],
AF_INET,
[(AF_INET, 24, False, u'1.2.3.0', 8000, 9000),
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
True,
None)
assert str(excinfo.value) == 'UDP not supported by nat method_name'
assert mock_ipt_chain_exists.mock_calls == []
assert mock_ipt_ttl.mock_calls == []
@ -110,48 +113,49 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
method.setup_firewall(
1025, 1027,
[(2, u'1.2.3.33')],
2,
[(2, 24, False, u'1.2.3.0', 8000, 9000),
(2, 32, True, u'1.2.3.66', 8080, 8080)],
False)
[(AF_INET, u'1.2.3.33')],
AF_INET,
[(AF_INET, 24, False, u'1.2.3.0', 8000, 9000),
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
False,
None)
assert mock_ipt_chain_exists.mock_calls == [
call(2, 'nat', 'sshuttle-1025')
call(AF_INET, 'nat', 'sshuttle-1025')
]
assert mock_ipt_ttl.mock_calls == [
call(2, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
'--dest', u'1.2.3.0/24', '-p', 'tcp', '--dport', '8000:9000',
'--to-ports', '1025'),
call(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',
'--dport', '53', '--to-ports', '1027')
]
assert mock_ipt.mock_calls == [
call(2, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
call(2, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'),
call(2, 'nat', '-F', 'sshuttle-1025'),
call(2, 'nat', '-X', 'sshuttle-1025'),
call(2, 'nat', '-N', 'sshuttle-1025'),
call(2, 'nat', '-F', 'sshuttle-1025'),
call(2, 'nat', '-I', 'OUTPUT', '1', '-j', 'sshuttle-1025'),
call(2, 'nat', '-I', 'PREROUTING', '1', '-j', 'sshuttle-1025'),
call(2, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
call(AF_INET, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
call(AF_INET, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'),
call(AF_INET, 'nat', '-F', 'sshuttle-1025'),
call(AF_INET, 'nat', '-X', 'sshuttle-1025'),
call(AF_INET, 'nat', '-N', 'sshuttle-1025'),
call(AF_INET, 'nat', '-F', 'sshuttle-1025'),
call(AF_INET, 'nat', '-I', 'OUTPUT', '1', '-j', 'sshuttle-1025'),
call(AF_INET, 'nat', '-I', 'PREROUTING', '1', '-j', 'sshuttle-1025'),
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
'--dest', u'1.2.3.66/32', '-p', 'tcp', '--dport', '8080:8080')
]
mock_ipt_chain_exists.reset_mock()
mock_ipt_ttl.reset_mock()
mock_ipt.reset_mock()
method.restore_firewall(1025, 2, False)
method.restore_firewall(1025, AF_INET, False, None)
assert mock_ipt_chain_exists.mock_calls == [
call(2, 'nat', 'sshuttle-1025')
call(AF_INET, 'nat', 'sshuttle-1025')
]
assert mock_ipt_ttl.mock_calls == []
assert mock_ipt.mock_calls == [
call(2, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
call(2, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'),
call(2, 'nat', '-F', 'sshuttle-1025'),
call(2, 'nat', '-X', 'sshuttle-1025')
call(AF_INET, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
call(AF_INET, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'),
call(AF_INET, 'nat', '-F', 'sshuttle-1025'),
call(AF_INET, 'nat', '-X', 'sshuttle-1025')
]
mock_ipt_chain_exists.reset_mock()
mock_ipt_ttl.reset_mock()

View File

@ -1,6 +1,7 @@
import pytest
from mock import Mock, patch, call, ANY
import socket
from socket import AF_INET, AF_INET6
from sshuttle.methods import get_method
from sshuttle.helpers import Fatal
@ -20,7 +21,7 @@ def test_get_tcp_dstip():
sock = Mock()
sock.getpeername.return_value = ("127.0.0.1", 1024)
sock.getsockname.return_value = ("127.0.0.2", 1025)
sock.family = socket.AF_INET
sock.family = AF_INET
firewall = Mock()
firewall.pfile.readline.return_value = \
@ -94,7 +95,7 @@ def test_firewall_command_darwin(mock_pf_get_dev, mock_ioctl, mock_stdout):
assert not method.firewall_command("somthing")
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
socket.AF_INET, socket.IPPROTO_TCP,
AF_INET, socket.IPPROTO_TCP,
"127.0.0.1", 1025, "127.0.0.2", 1024)
assert method.firewall_command(command)
@ -117,7 +118,7 @@ def test_firewall_command_freebsd(mock_pf_get_dev, mock_ioctl, mock_stdout):
assert not method.firewall_command("somthing")
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
socket.AF_INET, socket.IPPROTO_TCP,
AF_INET, socket.IPPROTO_TCP,
"127.0.0.1", 1025, "127.0.0.2", 1024)
assert method.firewall_command(command)
@ -140,7 +141,7 @@ def test_firewall_command_openbsd(mock_pf_get_dev, mock_ioctl, mock_stdout):
assert not method.firewall_command("somthing")
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
socket.AF_INET, socket.IPPROTO_TCP,
AF_INET, socket.IPPROTO_TCP,
"127.0.0.1", 1025, "127.0.0.2", 1024)
assert method.firewall_command(command)
@ -180,11 +181,12 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
method.setup_firewall(
1024, 1026,
[(10, u'2404:6800:4004:80c::33')],
10,
[(10, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
(10, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
False)
[(AF_INET6, u'2404:6800:4004:80c::33')],
AF_INET6,
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
False,
None)
assert mock_ioctl.mock_calls == [
call(mock_pf_get_dev(), 0xC4704433, ANY),
call(mock_pf_get_dev(), 0xCC20441A, ANY),
@ -199,14 +201,14 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
call('-s all'),
call('-a sshuttle6-1024 -f /dev/stdin',
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'rdr pass on lo0 inet6 proto udp '
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'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'to <dns_servers> port 53 keep state\n'),
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:
method.setup_firewall(
1025, 1027,
[(2, u'1.2.3.33')],
2,
[(2, 24, False, u'1.2.3.0', 0, 0),
(2, 32, True, u'1.2.3.66', 80, 80)],
True)
[(AF_INET, u'1.2.3.33')],
AF_INET,
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
True,
None)
assert str(excinfo.value) == 'UDP not supported by pf method_name'
assert mock_pf_get_dev.mock_calls == []
assert mock_ioctl.mock_calls == []
@ -230,10 +233,12 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
method.setup_firewall(
1025, 1027,
[(2, u'1.2.3.33')],
2,
[(2, 24, False, u'1.2.3.0', 0, 0), (2, 32, True, u'1.2.3.66', 80, 80)],
False)
[(AF_INET, u'1.2.3.33')],
AF_INET,
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
False,
None)
assert mock_ioctl.mock_calls == [
call(mock_pf_get_dev(), 0xC4704433, ANY),
call(mock_pf_get_dev(), 0xCC20441A, ANY),
@ -248,12 +253,12 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
call('-s all'),
call('-a sshuttle-1025 -f /dev/stdin',
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'rdr pass on lo0 inet proto udp '
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 inet proto tcp to 1.2.3.66/32 port 80:80\n'
b'pass out route-to lo0 inet proto udp '
b'to <dns_servers> port 53 keep state\n'),
call('-E'),
@ -262,7 +267,7 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
mock_ioctl.reset_mock()
mock_pfctl.reset_mock()
method.restore_firewall(1025, 2, False)
method.restore_firewall(1025, AF_INET, False, None)
assert mock_ioctl.mock_calls == []
assert mock_pfctl.mock_calls == [
call('-a sshuttle-1025 -F all'),
@ -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.methods.pf.pf', FreeBsd())
@patch('subprocess.call')
@patch('sshuttle.methods.pf.pfctl')
@patch('sshuttle.methods.pf.ioctl')
@patch('sshuttle.methods.pf.pf_get_dev')
def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl,
mock_subprocess_call):
mock_pfctl.side_effect = pfctl
method = get_method('pf')
@ -286,28 +293,30 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
method.setup_firewall(
1024, 1026,
[(10, u'2404:6800:4004:80c::33')],
10,
[(10, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
(10, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
False)
[(AF_INET6, u'2404:6800:4004:80c::33')],
AF_INET6,
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
False,
None)
assert mock_pfctl.mock_calls == [
call('-s all'),
call('-a sshuttle6-1024 -f /dev/stdin',
b'table <dns_servers> {2404:6800:4004:80c::33}\n'
b'rdr pass on lo0 inet6 proto tcp to 2404:6800:4004:80c::/64 '
b'port 8000:9000 -> ::1 port 1024\n'
b'rdr pass on lo0 inet6 proto tcp from ! ::1 to '
b'2404:6800:4004:80c::/64 port 8000:9000 -> ::1 port 1024\n'
b'rdr pass on lo0 inet6 proto udp '
b'to <dns_servers> port 53 -> ::1 port 1026\n'
b'pass out quick inet6 proto tcp to '
b'2404:6800:4004:80c::101f/128 port 8080:8080\n'
b'pass out route-to lo0 inet6 proto tcp to '
b'2404:6800:4004:80c::/64 port 8000:9000 keep state\n'
b'pass out 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'to <dns_servers> port 53 keep state\n'),
call('-e'),
]
assert call(['kldload', 'pf']) in mock_subprocess_call.mock_calls
mock_pf_get_dev.reset_mock()
mock_ioctl.reset_mock()
mock_pfctl.reset_mock()
@ -315,11 +324,12 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
with pytest.raises(Exception) as excinfo:
method.setup_firewall(
1025, 1027,
[(2, u'1.2.3.33')],
2,
[(2, 24, False, u'1.2.3.0', 0, 0),
(2, 32, True, u'1.2.3.66', 80, 80)],
True)
[(AF_INET, u'1.2.3.33')],
AF_INET,
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
True,
None)
assert str(excinfo.value) == 'UDP not supported by pf method_name'
assert mock_pf_get_dev.mock_calls == []
assert mock_ioctl.mock_calls == []
@ -327,10 +337,12 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
method.setup_firewall(
1025, 1027,
[(2, u'1.2.3.33')],
2,
[(2, 24, False, u'1.2.3.0', 0, 0), (2, 32, True, u'1.2.3.66', 80, 80)],
False)
[(AF_INET, u'1.2.3.33')],
AF_INET,
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
False,
None)
assert mock_ioctl.mock_calls == [
call(mock_pf_get_dev(), 0xC4704433, ANY),
call(mock_pf_get_dev(), 0xCBE0441A, ANY),
@ -343,12 +355,12 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
call('-s all'),
call('-a sshuttle-1025 -f /dev/stdin',
b'table <dns_servers> {1.2.3.33}\n'
b'rdr pass on lo0 inet proto tcp to 1.2.3.0/24 -> '
b'127.0.0.1 port 1025\n'
b'rdr pass on lo0 inet proto tcp from ! 127.0.0.1 '
b'to 1.2.3.0/24 -> 127.0.0.1 port 1025\n'
b'rdr pass on lo0 inet proto udp '
b'to <dns_servers> port 53 -> 127.0.0.1 port 1027\n'
b'pass out 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 inet proto tcp to 1.2.3.66/32 port 80:80\n'
b'pass out route-to lo0 inet proto udp '
b'to <dns_servers> port 53 keep state\n'),
call('-e'),
@ -357,10 +369,12 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
mock_ioctl.reset_mock()
mock_pfctl.reset_mock()
method.restore_firewall(1025, 2, False)
method.restore_firewall(1025, AF_INET, False, None)
method.restore_firewall(1024, AF_INET6, False, None)
assert mock_ioctl.mock_calls == []
assert mock_pfctl.mock_calls == [
call('-a sshuttle-1025 -F all'),
call('-a sshuttle6-1024 -F all'),
call("-d"),
]
mock_pf_get_dev.reset_mock()
@ -381,15 +395,16 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
method.setup_firewall(
1024, 1026,
[(10, u'2404:6800:4004:80c::33')],
10,
[(10, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
(10, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
False)
[(AF_INET6, u'2404:6800:4004:80c::33')],
AF_INET6,
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
False,
None)
assert mock_ioctl.mock_calls == [
call(mock_pf_get_dev(), 0xcd48441a, ANY),
call(mock_pf_get_dev(), 0xcd48441a, ANY),
call(mock_pf_get_dev(), 0xcd58441a, ANY),
call(mock_pf_get_dev(), 0xcd58441a, ANY),
]
assert mock_pfctl.mock_calls == [
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'pass in on lo0 inet6 proto udp '
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'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'<dns_servers> port 53 route-to lo0 keep state\n'),
call('-e'),
@ -416,11 +431,12 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
with pytest.raises(Exception) as excinfo:
method.setup_firewall(
1025, 1027,
[(2, u'1.2.3.33')],
2,
[(2, 24, False, u'1.2.3.0', 0, 0),
(2, 32, True, u'1.2.3.66', 80, 80)],
True)
[(AF_INET, u'1.2.3.33')],
AF_INET,
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
True,
None)
assert str(excinfo.value) == 'UDP not supported by pf method_name'
assert mock_pf_get_dev.mock_calls == []
assert mock_ioctl.mock_calls == []
@ -428,14 +444,15 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
method.setup_firewall(
1025, 1027,
[(2, u'1.2.3.33')],
2,
[(2, 24, False, u'1.2.3.0', 0, 0),
(2, 32, True, u'1.2.3.66', 80, 80)],
False)
[(AF_INET, u'1.2.3.33')],
AF_INET,
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
False,
None)
assert mock_ioctl.mock_calls == [
call(mock_pf_get_dev(), 0xcd48441a, ANY),
call(mock_pf_get_dev(), 0xcd48441a, ANY),
call(mock_pf_get_dev(), 0xcd58441a, ANY),
call(mock_pf_get_dev(), 0xcd58441a, ANY),
]
assert mock_pfctl.mock_calls == [
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'pass in on lo0 inet proto udp to '
b'<dns_servers> port 53 rdr-to 127.0.0.1 port 1027\n'
b'pass out 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.66/32 port 80:80\n'
b'pass out inet proto udp to '
b'<dns_servers> port 53 route-to lo0 keep state\n'),
call('-e'),
@ -457,11 +474,13 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
mock_ioctl.reset_mock()
mock_pfctl.reset_mock()
method.restore_firewall(1025, 2, False)
method.restore_firewall(1025, AF_INET, False, None)
method.restore_firewall(1024, AF_INET6, False, None)
assert mock_ioctl.mock_calls == []
assert mock_pfctl.mock_calls == [
call('-a sshuttle-1025 -F all'),
call("-d"),
call('-a sshuttle6-1024 -F all'),
call('-d'),
]
mock_pf_get_dev.reset_mock()
mock_pfctl.reset_mock()

View File

@ -1,3 +1,6 @@
import socket
from socket import AF_INET, AF_INET6
from mock import Mock, patch, call
from sshuttle.methods import get_method
@ -49,7 +52,7 @@ def test_send_udp(mock_socket):
assert sock.mock_calls == []
assert mock_socket.mock_calls == [
call(sock.family, 2),
call().setsockopt(1, 2, 1),
call().setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1),
call().setsockopt(0, 19, 1),
call().bind('127.0.0.2'),
call().sendto("2222222", '127.0.0.1'),
@ -100,71 +103,73 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
method.setup_firewall(
1024, 1026,
[(10, u'2404:6800:4004:80c::33')],
10,
[(10, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
(10, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
True)
[(AF_INET6, u'2404:6800:4004:80c::33')],
AF_INET6,
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 8000, 9000),
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
True,
None)
assert mock_ipt_chain_exists.mock_calls == [
call(10, 'mangle', 'sshuttle-m-1024'),
call(10, 'mangle', 'sshuttle-t-1024'),
call(10, 'mangle', 'sshuttle-d-1024')
call(AF_INET6, 'mangle', 'sshuttle-m-1024'),
call(AF_INET6, 'mangle', 'sshuttle-t-1024'),
call(AF_INET6, 'mangle', 'sshuttle-d-1024')
]
assert mock_ipt_ttl.mock_calls == []
assert mock_ipt.mock_calls == [
call(10, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1024'),
call(10, 'mangle', '-F', 'sshuttle-m-1024'),
call(10, 'mangle', '-X', 'sshuttle-m-1024'),
call(10, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1024'),
call(10, 'mangle', '-F', 'sshuttle-t-1024'),
call(10, 'mangle', '-X', 'sshuttle-t-1024'),
call(10, 'mangle', '-F', 'sshuttle-d-1024'),
call(10, 'mangle', '-X', 'sshuttle-d-1024'),
call(10, 'mangle', '-N', 'sshuttle-m-1024'),
call(10, 'mangle', '-F', 'sshuttle-m-1024'),
call(10, 'mangle', '-N', 'sshuttle-d-1024'),
call(10, 'mangle', '-F', 'sshuttle-d-1024'),
call(10, 'mangle', '-N', 'sshuttle-t-1024'),
call(10, 'mangle', '-F', 'sshuttle-t-1024'),
call(10, 'mangle', '-I', 'OUTPUT', '1', '-j', 'sshuttle-m-1024'),
call(10, 'mangle', '-I', 'PREROUTING', '1', '-j', 'sshuttle-t-1024'),
call(10, 'mangle', '-A', 'sshuttle-d-1024', '-j', 'MARK',
call(AF_INET6, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1024'),
call(AF_INET6, 'mangle', '-F', 'sshuttle-m-1024'),
call(AF_INET6, 'mangle', '-X', 'sshuttle-m-1024'),
call(AF_INET6, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1024'),
call(AF_INET6, 'mangle', '-F', 'sshuttle-t-1024'),
call(AF_INET6, 'mangle', '-X', 'sshuttle-t-1024'),
call(AF_INET6, 'mangle', '-F', 'sshuttle-d-1024'),
call(AF_INET6, 'mangle', '-X', 'sshuttle-d-1024'),
call(AF_INET6, 'mangle', '-N', 'sshuttle-m-1024'),
call(AF_INET6, 'mangle', '-F', 'sshuttle-m-1024'),
call(AF_INET6, 'mangle', '-N', 'sshuttle-d-1024'),
call(AF_INET6, 'mangle', '-F', 'sshuttle-d-1024'),
call(AF_INET6, 'mangle', '-N', 'sshuttle-t-1024'),
call(AF_INET6, 'mangle', '-F', 'sshuttle-t-1024'),
call(AF_INET6, 'mangle', '-I', 'OUTPUT', '1', '-j', 'sshuttle-m-1024'),
call(AF_INET6, 'mangle', '-I', 'PREROUTING', '1', '-j',
'sshuttle-t-1024'),
call(AF_INET6, 'mangle', '-A', 'sshuttle-d-1024', '-j', 'MARK',
'--set-mark', '1'),
call(10, 'mangle', '-A', 'sshuttle-d-1024', '-j', 'ACCEPT'),
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket',
call(AF_INET6, 'mangle', '-A', 'sshuttle-d-1024', '-j', 'ACCEPT'),
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket',
'-j', 'sshuttle-d-1024', '-m', 'tcp', '-p', 'tcp'),
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket',
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket',
'-j', 'sshuttle-d-1024', '-m', 'udp', '-p', 'udp'),
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
'--set-mark', '1', '--dest', u'2404:6800:4004:80c::33/32',
'-m', 'udp', '-p', 'udp', '--dport', '53'),
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
'--tproxy-mark', '0x1/0x1',
'--dest', u'2404:6800:4004:80c::33/32',
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1026'),
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
'--dest', u'2404:6800:4004:80c::101f/128',
'-m', 'tcp', '-p', 'tcp', '--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',
'-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',
'-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',
'-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',
'-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',
'-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000',
'--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',
'-m', 'udp', '-p', 'udp'),
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
'--tproxy-mark', '0x1/0x1', '--dest', u'2404:6800:4004:80c::/64',
'-m', 'udp', '-p', 'udp', '--dport', '8000:9000',
'--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.reset_mock()
method.restore_firewall(1025, 10, True)
method.restore_firewall(1025, AF_INET6, True, None)
assert mock_ipt_chain_exists.mock_calls == [
call(10, 'mangle', 'sshuttle-m-1025'),
call(10, 'mangle', 'sshuttle-t-1025'),
call(10, 'mangle', 'sshuttle-d-1025')
call(AF_INET6, 'mangle', 'sshuttle-m-1025'),
call(AF_INET6, 'mangle', 'sshuttle-t-1025'),
call(AF_INET6, 'mangle', 'sshuttle-d-1025')
]
assert mock_ipt_ttl.mock_calls == []
assert mock_ipt.mock_calls == [
call(10, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
call(10, 'mangle', '-F', 'sshuttle-m-1025'),
call(10, 'mangle', '-X', 'sshuttle-m-1025'),
call(10, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
call(10, 'mangle', '-F', 'sshuttle-t-1025'),
call(10, 'mangle', '-X', 'sshuttle-t-1025'),
call(10, 'mangle', '-F', 'sshuttle-d-1025'),
call(10, 'mangle', '-X', 'sshuttle-d-1025')
call(AF_INET6, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
call(AF_INET6, 'mangle', '-F', 'sshuttle-m-1025'),
call(AF_INET6, 'mangle', '-X', 'sshuttle-m-1025'),
call(AF_INET6, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
call(AF_INET6, 'mangle', '-F', 'sshuttle-t-1025'),
call(AF_INET6, 'mangle', '-X', 'sshuttle-t-1025'),
call(AF_INET6, 'mangle', '-F', 'sshuttle-d-1025'),
call(AF_INET6, 'mangle', '-X', 'sshuttle-d-1025')
]
mock_ipt_chain_exists.reset_mock()
mock_ipt_ttl.reset_mock()
@ -198,68 +203,71 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
method.setup_firewall(
1025, 1027,
[(2, u'1.2.3.33')],
2,
[(2, 24, False, u'1.2.3.0', 0, 0), (2, 32, True, u'1.2.3.66', 80, 80)],
True)
[(AF_INET, u'1.2.3.33')],
AF_INET,
[(AF_INET, 24, False, u'1.2.3.0', 0, 0),
(AF_INET, 32, True, u'1.2.3.66', 80, 80)],
True,
None)
assert mock_ipt_chain_exists.mock_calls == [
call(2, 'mangle', 'sshuttle-m-1025'),
call(2, 'mangle', 'sshuttle-t-1025'),
call(2, 'mangle', 'sshuttle-d-1025')
call(AF_INET, 'mangle', 'sshuttle-m-1025'),
call(AF_INET, 'mangle', 'sshuttle-t-1025'),
call(AF_INET, 'mangle', 'sshuttle-d-1025')
]
assert mock_ipt_ttl.mock_calls == []
assert mock_ipt.mock_calls == [
call(2, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
call(2, 'mangle', '-F', 'sshuttle-m-1025'),
call(2, 'mangle', '-X', 'sshuttle-m-1025'),
call(2, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
call(2, 'mangle', '-F', 'sshuttle-t-1025'),
call(2, 'mangle', '-X', 'sshuttle-t-1025'),
call(2, 'mangle', '-F', 'sshuttle-d-1025'),
call(2, 'mangle', '-X', 'sshuttle-d-1025'),
call(2, 'mangle', '-N', 'sshuttle-m-1025'),
call(2, 'mangle', '-F', 'sshuttle-m-1025'),
call(2, 'mangle', '-N', 'sshuttle-d-1025'),
call(2, 'mangle', '-F', 'sshuttle-d-1025'),
call(2, 'mangle', '-N', 'sshuttle-t-1025'),
call(2, 'mangle', '-F', 'sshuttle-t-1025'),
call(2, 'mangle', '-I', 'OUTPUT', '1', '-j', 'sshuttle-m-1025'),
call(2, 'mangle', '-I', 'PREROUTING', '1', '-j', 'sshuttle-t-1025'),
call(2, 'mangle', '-A', 'sshuttle-d-1025',
call(AF_INET, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
call(AF_INET, 'mangle', '-F', 'sshuttle-m-1025'),
call(AF_INET, 'mangle', '-X', 'sshuttle-m-1025'),
call(AF_INET, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
call(AF_INET, 'mangle', '-F', 'sshuttle-t-1025'),
call(AF_INET, 'mangle', '-X', 'sshuttle-t-1025'),
call(AF_INET, 'mangle', '-F', 'sshuttle-d-1025'),
call(AF_INET, 'mangle', '-X', 'sshuttle-d-1025'),
call(AF_INET, 'mangle', '-N', 'sshuttle-m-1025'),
call(AF_INET, 'mangle', '-F', 'sshuttle-m-1025'),
call(AF_INET, 'mangle', '-N', 'sshuttle-d-1025'),
call(AF_INET, 'mangle', '-F', 'sshuttle-d-1025'),
call(AF_INET, 'mangle', '-N', 'sshuttle-t-1025'),
call(AF_INET, 'mangle', '-F', 'sshuttle-t-1025'),
call(AF_INET, 'mangle', '-I', 'OUTPUT', '1', '-j', 'sshuttle-m-1025'),
call(AF_INET, 'mangle', '-I', 'PREROUTING', '1', '-j',
'sshuttle-t-1025'),
call(AF_INET, 'mangle', '-A', 'sshuttle-d-1025',
'-j', 'MARK', '--set-mark', '1'),
call(2, 'mangle', '-A', 'sshuttle-d-1025', '-j', 'ACCEPT'),
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket',
call(AF_INET, 'mangle', '-A', 'sshuttle-d-1025', '-j', 'ACCEPT'),
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket',
'-j', 'sshuttle-d-1025', '-m', 'tcp', '-p', 'tcp'),
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket',
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket',
'-j', 'sshuttle-d-1025', '-m', 'udp', '-p', 'udp'),
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
'--set-mark', '1', '--dest', u'1.2.3.33/32',
'-m', 'udp', '-p', 'udp', '--dport', '53'),
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
'--tproxy-mark', '0x1/0x1', '--dest', u'1.2.3.33/32',
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1027'),
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
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',
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(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', 'udp', '-p', 'udp',
'--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',
'--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',
'-m', 'tcp', '-p', 'tcp'),
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
'--tproxy-mark', '0x1/0x1', '--dest', u'1.2.3.0/24',
'-m', 'tcp', '-p', 'tcp', '--on-port', '1025'),
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
'--set-mark', '1', '--dest', u'1.2.3.0/24',
'-m', 'udp', '-p', 'udp'),
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
'--tproxy-mark', '0x1/0x1', '--dest', u'1.2.3.0/24',
'-m', 'udp', '-p', 'udp', '--on-port', '1025')
]
@ -267,22 +275,22 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
mock_ipt_ttl.reset_mock()
mock_ipt.reset_mock()
method.restore_firewall(1025, 2, True)
method.restore_firewall(1025, AF_INET, True, None)
assert mock_ipt_chain_exists.mock_calls == [
call(2, 'mangle', 'sshuttle-m-1025'),
call(2, 'mangle', 'sshuttle-t-1025'),
call(2, 'mangle', 'sshuttle-d-1025')
call(AF_INET, 'mangle', 'sshuttle-m-1025'),
call(AF_INET, 'mangle', 'sshuttle-t-1025'),
call(AF_INET, 'mangle', 'sshuttle-d-1025')
]
assert mock_ipt_ttl.mock_calls == []
assert mock_ipt.mock_calls == [
call(2, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
call(2, 'mangle', '-F', 'sshuttle-m-1025'),
call(2, 'mangle', '-X', 'sshuttle-m-1025'),
call(2, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
call(2, 'mangle', '-F', 'sshuttle-t-1025'),
call(2, 'mangle', '-X', 'sshuttle-t-1025'),
call(2, 'mangle', '-F', 'sshuttle-d-1025'),
call(2, 'mangle', '-X', 'sshuttle-d-1025')
call(AF_INET, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
call(AF_INET, 'mangle', '-F', 'sshuttle-m-1025'),
call(AF_INET, 'mangle', '-X', 'sshuttle-m-1025'),
call(AF_INET, 'mangle', '-D', 'PREROUTING', '-j', 'sshuttle-t-1025'),
call(AF_INET, 'mangle', '-F', 'sshuttle-t-1025'),
call(AF_INET, 'mangle', '-X', 'sshuttle-t-1025'),
call(AF_INET, 'mangle', '-F', 'sshuttle-d-1025'),
call(AF_INET, 'mangle', '-X', 'sshuttle-d-1025')
]
mock_ipt_chain_exists.reset_mock()
mock_ipt_ttl.reset_mock()

View File

@ -51,7 +51,7 @@ def test_parse_subnetport_ip4_with_port():
for ip_repr, ip in _ip4_reprs.items():
assert sshuttle.options.parse_subnetport(':'.join((ip_repr, '80'))) \
== (socket.AF_INET, ip, 32, 80, 80)
assert sshuttle.options.parse_subnetport(':'.join((ip_repr, '80-90'))) \
assert sshuttle.options.parse_subnetport(':'.join((ip_repr, '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():
assert sshuttle.options.parse_subnetport(ip_repr) \
== (socket.AF_INET6, ip, 128, 0, 0)
with pytest.raises(Fatal) as excinfo:
sshuttle.options.parse_subnetport('2001::1::3f')
assert str(excinfo.value) == 'Unable to resolve address: 2001::1::3f'
def test_parse_subnetport_ip6_with_mask():
@ -97,5 +94,5 @@ def test_parse_subnetport_ip6_with_mask_and_port():
for ip_repr, ip in _ip6_reprs.items():
assert sshuttle.options.parse_subnetport('[' + ip_repr + '/128]:80') \
== (socket.AF_INET6, ip, 128, 80, 80)
assert sshuttle.options.parse_subnetport('[' + ip_repr + '/16]:80-90') \
assert sshuttle.options.parse_subnetport('[' + ip_repr + '/16]:80-90')\
== (socket.AF_INET6, ip, 16, 80, 90)

View File

@ -1,6 +1,4 @@
from mock import Mock, patch, call
import sys
import io
import socket
import sshuttle.sdnotify

View File

@ -7,4 +7,4 @@ else:
collect_ignore = []
if not good_python:
collect_ignore.append("sshuttle/tests/client")
collect_ignore.append("client")

View File

@ -1,8 +1,7 @@
import os
import io
import socket
import sshuttle.server
from mock import patch, Mock, call
from mock import patch, Mock
def test__ipmatch():

View File

@ -1,10 +1,10 @@
[tox]
downloadcache = {toxworkdir}/cache/
envlist =
py26,
py27,
py34,
py35,
py36,
[testenv]
basepython =
@ -12,9 +12,10 @@ basepython =
py27: python2.7
py34: python3.4
py35: python3.5
py36: python3.6
commands =
flake8 sshuttle --count --select=E901,E999,F821,F822,F823 --show-source --statistics
flake8 sshuttle --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
py.test
deps =
pytest
mock
setuptools>=17.1
-rrequirements-tests.txt