Fix conflicts in localhost detection after removal of ttl code.

Since sshuttle's TTL code was removed in a recent commit, this code
will cause sshuttle to exit when the host specified with -r is
actually the localhost.
This commit is contained in:
Scott Kuhl 2021-07-15 17:10:41 -04:00
commit 2538beb711
31 changed files with 453 additions and 410 deletions

12
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: pip
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10

View File

@ -5,11 +5,11 @@ name: Python package
on: on:
push: push:
branches: [ master, tproxy_mark_param ] branches: [ master ]
pull_request: pull_request:
branches: [ master, tproxy_mark_param ] branches: [ master ]
workflow_dispatch: workflow_dispatch:
branches: [ tproxy_mark_param ] branches: [ master ]
jobs: jobs:
build: build:
@ -20,9 +20,9 @@ jobs:
python-version: [3.6, 3.7, 3.8, 3.9] python-version: [3.6, 3.7, 3.8, 3.9]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2.3.4
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2 uses: actions/setup-python@v2.2.2
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install dependencies - name: Install dependencies

View File

@ -46,6 +46,10 @@ Obtaining sshuttle
dnf install sshuttle dnf install sshuttle
- Gentoo::
emerge -av net-proxy/sshuttle
- NixOS:: - NixOS::
nix-env -iA nixos.sshuttle nix-env -iA nixos.sshuttle

View File

@ -50,6 +50,14 @@ if [ "$FILE_NAME" == "" ]; then
exit 1 exit 1
fi fi
# Verify that the resulting file name begins with /etc/sudoers.d
FILE_NAME="$(realpath "/etc/sudoers.d/$FILE_NAME")"
if [[ "$FILE_NAME" != "/etc/sudoers.d/"* ]] ; then
echo -n "Invalid sudoers filename: Final sudoers file "
echo "location ($FILE_NAME) does not begin with /etc/sudoers.d"
exit 1
fi
# Make a temp file to hold the sudoers config # Make a temp file to hold the sudoers config
umask 077 umask 077
TEMP_FILE=$(mktemp) TEMP_FILE=$(mktemp)
@ -62,9 +70,9 @@ visudo_code=$?
rm "$TEMP_FILE" rm "$TEMP_FILE"
if [ $visudo_code -eq 0 ]; then if [ $visudo_code -eq 0 ]; then
echo "$CONTENT" > "/etc/sudoers.d/$FILE_NAME" echo "$CONTENT" > "$FILE_NAME"
chmod 0440 "/etc/sudoers.d/$FILE_NAME" chmod 0440 "$FILE_NAME"
echo "The sudoers file /etc/sudoers.d/$FILE_NAME has been successfully created!" echo "The sudoers file $FILE_NAME has been successfully created!"
exit 0 exit 0
else else

View File

@ -4,14 +4,14 @@ sshuttle
Synopsis Synopsis
-------- --------
**sshuttle** [*options*] [**-r** *[username@]sshserver[:port]*] \<*subnets* ...\> **sshuttle** [*options*] **-r** *[username@]sshserver[:port]* \<*subnets* ...\>
Description Description
----------- -----------
:program:`sshuttle` allows you to create a VPN connection from your :program:`sshuttle` allows you to create a VPN connection from your
machine to any remote server that you can connect to via machine to any remote server that you can connect to via ssh, as long
ssh, as long as that server has python 3.6 or higher. as that server has a sufficiently new Python installation.
To work, you must have root access on the local machine, To work, you must have root access on the local machine,
but you can have a normal account on the server. but you can have a normal account on the server.
@ -32,21 +32,22 @@ Options
A list of subnets to route over the VPN, in the form A list of subnets to route over the VPN, in the form
``a.b.c.d[/width][port[-port]]``. Valid examples are 1.2.3.4 (a ``a.b.c.d[/width][port[-port]]``. Valid examples are 1.2.3.4 (a
single IP address), 1.2.3.4/32 (equivalent to 1.2.3.4), single IP address) and 1.2.3.4/32 (equivalent to 1.2.3.4),
1.2.3.0/24 (a 24-bit subnet, ie. with a 255.255.255.0 1.2.3.0/24 (a 24-bit subnet, ie. with a 255.255.255.0 netmask).
netmask), and 0/0 ('just route everything through the Specify subnets 0/0 to match all IPv4 addresses and ::/0 to match
VPN'). Any of the previous examples are also valid if you append all IPv6 addresses. Any of the previous examples are also valid if
a port or a port range, so 1.2.3.4:8000 will only tunnel traffic you append a port or a port range, so 1.2.3.4:8000 will only
that has as the destination port 8000 of 1.2.3.4 and tunnel traffic that has as the destination port 8000 of 1.2.3.4
1.2.3.0/24:8000-9000 will tunnel traffic going to any port between and 1.2.3.0/24:8000-9000 will tunnel traffic going to any port
8000 and 9000 (inclusive) for all IPs in the 1.2.3.0/24 subnet. between 8000 and 9000 (inclusive) for all IPs in the 1.2.3.0/24
A hostname can be provided instead of an IP address. If the subnet. A hostname can be provided instead of an IP address. If
hostname resolves to multiple IPs, all of the IPs are included. the hostname resolves to multiple IPs, all of the IPs are
If a width is provided with a hostname that the width is applied included. If a width is provided with a hostname, the width is
to all of the hostnames IPs (if they are all either IPv4 or IPv6). applied to all of the hostnames IPs (if they are all either IPv4
Widths cannot be supplied to hostnames that resolve to both IPv4 or IPv6). Widths cannot be supplied to hostnames that resolve to
and IPv6. Valid examples are example.com, example.com:8000, both IPv4 and IPv6. Valid examples are example.com,
example.com/24, example.com/24:8000 and example.com:8000-9000. example.com:8000, example.com/24, example.com/24:8000 and
example.com:8000-9000.
.. option:: --method <auto|nat|nft|tproxy|pf|ipfw> .. option:: --method <auto|nat|nft|tproxy|pf|ipfw>
@ -88,6 +89,13 @@ Options
few subnets over the VPN, you probably would prefer to few subnets over the VPN, you probably would prefer to
keep using your local DNS server for everything else. keep using your local DNS server for everything else.
:program:`sshuttle` tries to store a cache of the hostnames in
~/.sshuttle.hosts on the remote host. Similarly, it tries to read
the file when you later reconnect to the host with --auto-hosts
enabled to quickly populate the host list. When troubleshooting
this feature, try removing this file on the remote host when
sshuttle is not running.
.. option:: -N, --auto-nets .. option:: -N, --auto-nets
In addition to the subnets provided on the command In addition to the subnets provided on the command
@ -141,7 +149,10 @@ Options
The remote hostname and optional username and ssh The remote hostname and optional username and ssh
port number to use for connecting to the remote server. port number to use for connecting to the remote server.
For example, example.com, testuser@example.com, For example, example.com, testuser@example.com,
testuser@example.com:2222, or example.com:2244. testuser@example.com:2222, or example.com:2244. This
hostname is passed to ssh, so it will recognize any
aliases and settings you may have configured in
~/.ssh/config.
.. option:: -x <subnet>, --exclude=<subnet> .. option:: -x <subnet>, --exclude=<subnet>
@ -174,7 +185,7 @@ Options
A comma-separated list of hostnames to use to A comma-separated list of hostnames to use to
initialize the :option:`--auto-hosts` scan algorithm. initialize the :option:`--auto-hosts` scan algorithm.
:option:`--auto-hosts` does things like poll local SMB servers :option:`--auto-hosts` does things like poll netstat output
for lists of local hostnames, but can speed things up for lists of local hostnames, but can speed things up
if you use this option to give it a few names to start if you use this option to give it a few names to start
from. from.
@ -274,9 +285,10 @@ Options
Set the file name for the sudoers.d file to be added. Default is Set the file name for the sudoers.d file to be added. Default is
"sshuttle_auto". Only works with --sudoers. "sshuttle_auto". Only works with --sudoers.
.. option:: -t, --tmark .. option:: -t <mark>, --tmark=<mark>
Transproxy optional traffic mark with provided MARK value. An option used by the tproxy method: Use the specified traffic
mark. The mark must be a hexadecimal value. Defaults to 0x01.
.. option:: --version .. option:: --version
@ -305,11 +317,8 @@ Arguments read from a file must be one per line, as shown below::
--option2 --option2
value2 value2
Comments in config file The configuration file supports comments for human-readable
....................... annotations. For example::
It's possible to add comments in the configuration file. This allows annotating the
various subnets with human-readable descriptions, like::
# company-internal API # company-internal API
8.8.8.8/32 8.8.8.8/32
@ -319,51 +328,96 @@ various subnets with human-readable descriptions, like::
Examples Examples
-------- --------
Test locally by proxying all local connections, without using ssh::
$ sshuttle -v 0/0 Use the following command to route all IPv4 TCP traffic through remote
(-r) host example.com (and possibly other traffic too, depending on
the selected --method). The 0/0 subnet, short for 0.0.0.0/0, matches
all IPv4 addresses. The ::/0 subnet, matching all IPv6 addresses could
be added to the example. We also exclude (-x) example.com:22 so that
we can establish ssh connections from our local machine to the remote
host without them being routed through sshuttle. Excluding the remote
host may be necessary on some machines for sshuttle to work properly.
Press Ctrl+C to exit. To also route DNS queries through sshuttle, try
adding --dns. Add or remove -v options to see more or less
information::
Starting sshuttle proxy. $ sshuttle -r example.com -x example.com:22 0/0
Listening on ('0.0.0.0', 12300).
Starting sshuttle proxy (version ...).
[local sudo] Password: [local sudo] Password:
firewall manager ready. fw: Starting firewall with Python version 3.9.5
c : connecting to server... fw: ready method name nat.
s: available routes: c : IPv6 disabled since it isn't supported by method nat.
s: 192.168.42.0/24 c : Method: nat
c : connected. c : IPv4: on
firewall manager: starting transproxy. c : IPv6: off (not available with nat method)
c : Accept: 192.168.42.106:50035 -> 192.168.42.121:139. c : UDP : off (not available with nat method)
c : Accept: 192.168.42.121:47523 -> 77.141.99.22:443. c : DNS : off (available)
...etc... c : User: off (available)
c : Subnets to forward through remote host (type, IP, cidr mask width, startPort, endPort):
c : (<AddressFamily.AF_INET: 2>, '0.0.0.0', 0, 0, 0)
c : Subnets to exclude from forwarding:
c : (<AddressFamily.AF_INET: 2>, '...', 32, 22, 22)
c : (<AddressFamily.AF_INET: 2>, '127.0.0.1', 32, 0, 0)
c : TCP redirector listening on ('127.0.0.1', 12299).
c : Starting client with Python version 3.9.5
c : Connecting to server...
user@example.com's password:
s: Starting server with Python version 3.6.8
s: latency control setting = True
s: auto-nets:False
c : Connected to server.
fw: setting up.
fw: iptables -w -t nat -N sshuttle-12299
fw: iptables -w -t nat -F sshuttle-12299
...
Accept: 192.168.42.121:60554 -> 77.141.99.22:22.
^C ^C
firewall manager: undoing changes.
KeyboardInterrupt
c : Keyboard interrupt: exiting. c : Keyboard interrupt: exiting.
c : SW#8:192.168.42.121:47523: deleting c : SW'unknown':Mux#1: deleting (1 remain)
c : SW#6:192.168.42.106:50035: deleting c : SW#7:192.168.42.121:60554: deleting (0 remain)
Test connection to a remote server, with automatic hostname
Connect to a remote server, with automatic hostname
and subnet guessing:: and subnet guessing::
$ sshuttle -vNHr example.org $ sshuttle -vNHr example.com -x example.com:22
Starting sshuttle proxy (version ...).
Starting sshuttle proxy. [local sudo] Password:
Listening on ('0.0.0.0', 12300). fw: Starting firewall with Python version 3.9.5
firewall manager ready. fw: ready method name nat.
c : connecting to server... c : IPv6 disabled since it isn't supported by method nat.
c : Method: nat
c : IPv4: on
c : IPv6: off (not available with nat method)
c : UDP : off (not available with nat method)
c : DNS : off (available)
c : User: off (available)
c : Subnets to forward through remote host (type, IP, cidr mask width, startPort, endPort):
c : NOTE: Additional subnets to forward may be added below by --auto-nets.
c : Subnets to exclude from forwarding:
c : (<AddressFamily.AF_INET: 2>, '...', 32, 22, 22)
c : (<AddressFamily.AF_INET: 2>, '127.0.0.1', 32, 0, 0)
c : TCP redirector listening on ('127.0.0.1', 12300).
c : Starting client with Python version 3.9.5
c : Connecting to server...
user@example.com's password:
s: Starting server with Python version 3.6.8
s: latency control setting = True
s: auto-nets:True
c : Connected to server.
c : seed_hosts: []
s: available routes: s: available routes:
s: 77.141.99.0/24 s: 77.141.99.0/24
c : connected. fw: setting up.
c : seed_hosts: [] fw: iptables -w -t nat -N sshuttle-12300
firewall manager: starting transproxy. fw: iptables -w -t nat -F sshuttle-12300
hostwatch: Found: testbox1: 1.2.3.4 ...
hostwatch: Found: mytest2: 5.6.7.8
hostwatch: Found: domaincontroller: 99.1.2.3
c : Accept: 192.168.42.121:60554 -> 77.141.99.22:22. c : Accept: 192.168.42.121:60554 -> 77.141.99.22:22.
^C ^C
firewall manager: undoing changes.
c : Keyboard interrupt: exiting. c : Keyboard interrupt: exiting.
c : SW#6:192.168.42.121:60554: deleting c : SW'unknown':Mux#1: deleting (1 remain)
c : SW#7:192.168.42.121:60554: deleting (0 remain)
Run :program:`sshuttle` with a `/etc/sshuttle.conf` configuration file:: Run :program:`sshuttle` with a `/etc/sshuttle.conf` configuration file::
@ -387,9 +441,7 @@ Example configuration file::
Discussion Discussion
---------- ----------
When it starts, :program:`sshuttle` creates an ssh session to the When it starts, :program:`sshuttle` creates an ssh session to the
server specified by the ``-r`` option. If ``-r`` is omitted, server specified by the ``-r`` option.
it will start both its client and server locally, which is
sometimes useful for testing.
After connecting to the remote server, :program:`sshuttle` uploads its After connecting to the remote server, :program:`sshuttle` uploads its
(python) source code to the remote end and executes it (python) source code to the remote end and executes it

View File

@ -15,10 +15,12 @@ Supports:
* IPv4 TCP * IPv4 TCP
* IPv4 DNS * IPv4 DNS
* IPv6 TCP
* IPv6 DNS
Requires: Requires:
* iptables DNAT, REDIRECT, and ttl modules. * iptables DNAT and REDIRECT modules. ip6tables for IPv6.
Linux with nft method Linux with nft method
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~

View File

@ -12,7 +12,8 @@ There are some things you need to consider for TPROXY to work:
ip -6 route add local default dev lo table 100 ip -6 route add local default dev lo table 100
ip -6 rule add fwmark {TMARK} lookup 100 ip -6 rule add fwmark {TMARK} lookup 100
where {TMARK} is the identifier mark passed with -t or --tmark flag (default value is 1). where {TMARK} is the identifier mark passed with -t or --tmark flag
as a hexadecimal string (default value is '0x01').
- The ``--auto-nets`` feature does not detect IPv6 routes automatically. Add IPv6 - The ``--auto-nets`` feature does not detect IPv6 routes automatically. Add IPv6
routes manually. e.g. by adding ``'::/0'`` to the end of the command line. routes manually. e.g. by adding ``'::/0'`` to the end of the command line.

View File

@ -11,6 +11,10 @@ Forward all traffic::
sshuttle -r username@sshserver 0.0.0.0/0 sshuttle -r username@sshserver 0.0.0.0/0
- Use the :option:`sshuttle -r` parameter to specify a remote server. - Use the :option:`sshuttle -r` parameter to specify a remote server.
One some systems, you may also need to use the :option:`sshuttle -x`
parameter to exclude sshserver or sshserver:22 so that your local
machine can communicate directly to sshserver without it being
redirected by sshuttle.
- By default sshuttle will automatically choose a method to use. Override with - By default sshuttle will automatically choose a method to use. Override with
the :option:`sshuttle --method` parameter. the :option:`sshuttle --method` parameter.

View File

@ -1,5 +1,5 @@
-r requirements.txt -r requirements.txt
pytest==6.2.2 pytest==6.2.4
pytest-cov==2.11.1 pytest-cov==2.12.1
flake8==3.8.4 flake8==3.9.2
pyflakes==2.2.0 pyflakes==2.3.1

View File

@ -1,2 +1 @@
setuptools-scm==5.0.2 setuptools-scm==6.0.1
psutil

View File

@ -63,7 +63,6 @@ setup(
}, },
python_requires='>=3.6', python_requires='>=3.6',
install_requires=[ install_requires=[
'psutil',
], ],
tests_require=[ tests_require=[
'pytest', 'pytest',

View File

@ -42,4 +42,4 @@ import sshuttle.cmdline_options as options # noqa: E402
from sshuttle.server import main # noqa: E402 from sshuttle.server import main # noqa: E402
main(options.latency_control, options.latency_buffer_size, main(options.latency_control, options.latency_buffer_size,
options.auto_hosts, options.to_nameserver, options.auto_hosts, options.to_nameserver,
options.auto_nets, options.ttl, options.localhost_detector) options.auto_nets, options.localhost_detector)

View File

@ -7,7 +7,6 @@ import os
import sys import sys
import platform import platform
import tempfile import tempfile
import psutil
import sshuttle.helpers as helpers import sshuttle.helpers as helpers
import sshuttle.ssnet as ssnet import sshuttle.ssnet as ssnet
@ -46,6 +45,7 @@ def got_signal(signum, frame):
sys.exit(1) sys.exit(1)
# Filename of the pidfile created by the sshuttle client.
_pidname = None _pidname = None
# This variable is set to true if the client and the server appear to # This variable is set to true if the client and the server appear to
@ -204,14 +204,13 @@ class MultiListener:
class FirewallClient: class FirewallClient:
def __init__(self, method_name, sudo_pythonpath, ttl): def __init__(self, method_name, sudo_pythonpath):
self.auto_nets = [] self.auto_nets = []
python_path = os.path.dirname(os.path.dirname(__file__))
argvbase = ([sys.executable, sys.argv[0]] + argvbase = ([sys.executable, sys.argv[0]] +
['-v'] * (helpers.verbose or 0) + ['-v'] * (helpers.verbose or 0) +
['--method', method_name] + ['--method', method_name] +
['--firewall'] + ['--firewall'])
['--ttl', ttl])
if ssyslog._p: if ssyslog._p:
argvbase += ['--syslog'] argvbase += ['--syslog']
@ -229,7 +228,8 @@ class FirewallClient:
if sudo_pythonpath: if sudo_pythonpath:
elev_prefix += ['/usr/bin/env', elev_prefix += ['/usr/bin/env',
'PYTHONPATH=%s' % python_path] 'PYTHONPATH=%s' %
os.path.dirname(os.path.dirname(__file__))]
argv_tries = [elev_prefix + argvbase, argvbase] argv_tries = [elev_prefix + argvbase, argvbase]
# we can't use stdin/stdout=subprocess.PIPE here, as we normally would, # we can't use stdin/stdout=subprocess.PIPE here, as we normally would,
@ -266,7 +266,7 @@ class FirewallClient:
def setup(self, subnets_include, subnets_exclude, nslist, def setup(self, subnets_include, subnets_exclude, nslist,
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp, redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp,
user, tmark, ttl): user, tmark):
self.subnets_include = subnets_include self.subnets_include = subnets_include
self.subnets_exclude = subnets_exclude self.subnets_exclude = subnets_exclude
self.nslist = nslist self.nslist = nslist
@ -277,7 +277,6 @@ class FirewallClient:
self.udp = udp self.udp = udp
self.user = user self.user = user
self.tmark = tmark self.tmark = tmark
self.ttl = ttl
def check(self): def check(self):
rv = self.p.poll() rv = self.p.poll()
@ -316,7 +315,8 @@ class FirewallClient:
else: else:
user = b'%d' % self.user user = b'%d' % self.user
self.pfile.write(b'GO %d %s\n' % (udp, user)) self.pfile.write(b'GO %d %s %s\n' %
(udp, user, bytes(self.tmark, 'ascii')))
self.pfile.flush() self.pfile.flush()
line = self.pfile.readline() line = self.pfile.readline()
@ -461,7 +461,7 @@ def ondns(listener, method, mux, handlers):
def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename, def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
python, latency_control, latency_buffer_size, python, latency_control, latency_buffer_size,
dns_listener, seed_hosts, auto_hosts, auto_nets, daemon, dns_listener, seed_hosts, auto_hosts, auto_nets, daemon,
to_nameserver, ttl): to_nameserver):
helpers.logprefix = 'c : ' helpers.logprefix = 'c : '
debug1('Starting client with Python version %s' debug1('Starting client with Python version %s'
@ -491,7 +491,6 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
auto_hosts=auto_hosts, auto_hosts=auto_hosts,
to_nameserver=to_nameserver, to_nameserver=to_nameserver,
auto_nets=auto_nets, auto_nets=auto_nets,
ttl=ttl,
localhost_detector=localhost_detector)) localhost_detector=localhost_detector))
except socket.error as e: except socket.error as e:
if e.args[0] == errno.EPIPE: if e.args[0] == errno.EPIPE:
@ -676,7 +675,9 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
# poll() won't tell us when process exited since the # poll() won't tell us when process exited since the
# process is no longer our child (it returns 0 all the # process is no longer our child (it returns 0 all the
# time). # time).
if not psutil.pid_exists(serverproc.pid): try:
os.kill(serverproc.pid, 0)
except OSError:
raise Fatal('ssh connection to server (pid %d) exited.' % raise Fatal('ssh connection to server (pid %d) exited.' %
serverproc.pid) serverproc.pid)
else: else:
@ -698,12 +699,11 @@ def main(listenip_v6, listenip_v4,
latency_buffer_size, dns, nslist, latency_buffer_size, dns, nslist,
method_name, seed_hosts, auto_hosts, auto_nets, method_name, seed_hosts, auto_hosts, auto_nets,
subnets_include, subnets_exclude, daemon, to_nameserver, pidfile, subnets_include, subnets_exclude, daemon, to_nameserver, pidfile,
user, sudo_pythonpath, tmark, ttl): user, sudo_pythonpath, tmark):
if not remotename: if not remotename:
print("WARNING: You must specify -r/--remote to securely route " raise Fatal("You must use -r/--remote to specify a remote "
"traffic to a remote machine. Running without -r/--remote " "host to route traffic through.")
"is only recommended for testing.")
if daemon: if daemon:
try: try:
@ -714,21 +714,28 @@ def main(listenip_v6, listenip_v4,
debug1('Starting sshuttle proxy (version %s).' % __version__) debug1('Starting sshuttle proxy (version %s).' % __version__)
helpers.logprefix = 'c : ' helpers.logprefix = 'c : '
fw = FirewallClient(method_name, sudo_pythonpath, ttl) fw = FirewallClient(method_name, sudo_pythonpath)
# If --dns is used, store the IP addresses that the client # nslist is the list of name severs to intercept. If --dns is
# normally uses for DNS lookups in nslist. The firewall needs to # used, we add all DNS servers in resolv.conf. Otherwise, the list
# redirect packets outgoing to this server to the remote host # can be populated with the --ns-hosts option (which is already
# stored in nslist). This list is used to setup the firewall so it
# can redirect packets outgoing to this server to the remote host
# instead. # instead.
if dns: if dns:
nslist += resolvconf_nameservers(True) nslist += resolvconf_nameservers(True)
# If we are intercepting DNS requests, we tell the remote host
# where it should send the DNS requests to with the --to-ns
# option.
if len(nslist) > 0:
if to_nameserver is not None: if to_nameserver is not None:
to_nameserver = "%s@%s" % tuple(to_nameserver[1:]) to_nameserver = "%s@%s" % tuple(to_nameserver[1:])
else: else: # if we are not intercepting DNS traffic
# option doesn't make sense if we aren't proxying dns # ...and the user specified a server to send DNS traffic to.
if to_nameserver and len(to_nameserver) > 0: if to_nameserver and len(to_nameserver) > 0:
print("WARNING: --to-ns option is ignored because --dns was not " print("WARNING: --to-ns option is ignored unless "
"used.") "--dns or --ns-hosts is used.")
to_nameserver = None to_nameserver = None
# Get family specific subnet lists. Also, the user may not specify # Get family specific subnet lists. Also, the user may not specify
@ -1024,14 +1031,14 @@ def main(listenip_v6, listenip_v4,
# start the firewall # start the firewall
fw.setup(subnets_include, subnets_exclude, nslist, fw.setup(subnets_include, subnets_exclude, nslist,
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4,
required.udp, user, tmark, ttl) required.udp, user, tmark)
# start the client process # start the client process
try: try:
return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename, return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
python, latency_control, latency_buffer_size, python, latency_control, latency_buffer_size,
dns_listener, seed_hosts, auto_hosts, auto_nets, dns_listener, seed_hosts, auto_hosts, auto_nets,
daemon, to_nameserver, ttl) daemon, to_nameserver)
finally: finally:
try: try:
if daemon: if daemon:

View File

@ -20,7 +20,7 @@ def main():
return 1 return 1
if not opt.sudoers_filename: if not opt.sudoers_filename:
log('--sudoers-file must be set or omited.') log('--sudoers-file must be set or omitted.')
return 1 return 1
sudoers( sudoers(
@ -43,7 +43,7 @@ def main():
if opt.firewall: if opt.firewall:
if opt.subnets or opt.subnets_file: if opt.subnets or opt.subnets_file:
parser.error('exactly zero arguments expected') parser.error('exactly zero arguments expected')
return firewall.main(opt.method, opt.syslog, opt.ttl) return firewall.main(opt.method, opt.syslog)
elif opt.hostwatch: elif opt.hostwatch:
return hostwatch.hw_main(opt.subnets, opt.auto_hosts) return hostwatch.hw_main(opt.subnets, opt.auto_hosts)
else: else:
@ -85,6 +85,13 @@ def main():
ipport_v4 = "auto" ipport_v4 = "auto"
# parse_ipport6('[::1]:0') # parse_ipport6('[::1]:0')
ipport_v6 = "auto" if not opt.disable_ipv6 else None ipport_v6 = "auto" if not opt.disable_ipv6 else None
try:
int(opt.tmark, 16)
except ValueError:
parser.error("--tmark must be a hexadecimal value")
opt.tmark = opt.tmark.lower() # make 'x' in 0x lowercase
if not opt.tmark.startswith("0x"): # accept without 0x prefix
opt.tmark = "0x%s" % opt.tmark
if opt.syslog: if opt.syslog:
ssyslog.start_syslog() ssyslog.start_syslog()
ssyslog.close_stdin() ssyslog.close_stdin()
@ -109,8 +116,7 @@ def main():
opt.pidfile, opt.pidfile,
opt.user, opt.user,
opt.sudo_pythonpath, opt.sudo_pythonpath,
opt.tmark, opt.tmark)
opt.ttl)
if return_code == 0: if return_code == 0:
log('Normal exit code, exiting...') log('Normal exit code, exiting...')

View File

@ -5,6 +5,7 @@ import sys
import os import os
import platform import platform
import traceback import traceback
import subprocess as ssubprocess
import sshuttle.ssyslog as ssyslog import sshuttle.ssyslog as ssyslog
import sshuttle.helpers as helpers import sshuttle.helpers as helpers
@ -89,6 +90,29 @@ def subnet_weight(s):
return (-s[-1] + (s[-2] or -65535), s[1], s[2]) return (-s[-1] + (s[-2] or -65535), s[1], s[2])
def flush_systemd_dns_cache():
# If the user is using systemd-resolve for DNS resolution, it is
# possible for the request to go through systemd-resolve before we
# see it...and it may use a cached result instead of sending a
# request that we can intercept. When sshuttle starts and stops,
# this means that we should clear the cache!
#
# The command to do this was named systemd-resolve, but changed to
# resolvectl in systemd 239.
# https://github.com/systemd/systemd/blob/f8eb41003df1a4eab59ff9bec67b2787c9368dbd/NEWS#L3816
if helpers.which("resolvectl"):
debug2("Flushing systemd's DNS resolver cache: "
"resolvectl flush-caches")
ssubprocess.Popen(["resolvectl", "flush-caches"],
stdout=ssubprocess.PIPE, env=helpers.get_env())
elif helpers.which("systemd-resolve"):
debug2("Flushing systemd's DNS resolver cache: "
"systemd-resolve --flush-caches")
ssubprocess.Popen(["systemd-resolve", "--flush-caches"],
stdout=ssubprocess.PIPE, env=helpers.get_env())
# This is some voodoo for setting up the kernel's transparent # This is some voodoo for setting up the kernel's transparent
# proxying stuff. If subnets is empty, we just delete our sshuttle rules; # proxying stuff. If subnets is empty, we just delete our sshuttle rules;
# otherwise we delete it, then make them from scratch. # otherwise we delete it, then make them from scratch.
@ -97,7 +121,7 @@ def subnet_weight(s):
# exit. In case that fails, it's not the end of the world; future runs will # exit. In case that fails, it's not the end of the world; future runs will
# supercede it in the transproxy list, at least, so the leftover rules # supercede it in the transproxy list, at least, so the leftover rules
# are hopefully harmless. # are hopefully harmless.
def main(method_name, syslog, ttl): def main(method_name, syslog):
helpers.logprefix = 'fw: ' helpers.logprefix = 'fw: '
stdin, stdout = setup_daemon() stdin, stdout = setup_daemon()
hostmap = {} hostmap = {}
@ -199,11 +223,12 @@ def main(method_name, syslog, ttl):
raise Fatal('expected GO but got %r' % line) raise Fatal('expected GO but got %r' % line)
_, _, args = line.partition(" ") _, _, args = line.partition(" ")
udp, user = args.strip().split(" ", 1) udp, user, tmark = args.strip().split(" ", 2)
udp = bool(int(udp)) udp = bool(int(udp))
if user == '-': if user == '-':
user = None user = None
debug2('Got udp: %r, user: %r' % (udp, user)) debug2('Got udp: %r, user: %r, tmark: %s' %
(udp, user, tmark))
subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6] subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6]
nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6] nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6]
@ -218,15 +243,16 @@ def main(method_name, syslog, ttl):
method.setup_firewall( method.setup_firewall(
port_v6, dnsport_v6, nslist_v6, port_v6, dnsport_v6, nslist_v6,
socket.AF_INET6, subnets_v6, udp, socket.AF_INET6, subnets_v6, udp,
user, ttl) user, tmark)
if subnets_v4 or nslist_v4: if subnets_v4 or nslist_v4:
debug2('setting up IPv4.') debug2('setting up IPv4.')
method.setup_firewall( method.setup_firewall(
port_v4, dnsport_v4, nslist_v4, port_v4, dnsport_v4, nslist_v4,
socket.AF_INET, subnets_v4, udp, socket.AF_INET, subnets_v4, udp,
user, ttl) user, tmark)
flush_systemd_dns_cache()
stdout.write('STARTED\n') stdout.write('STARTED\n')
try: try:
@ -288,3 +314,12 @@ def main(method_name, syslog, ttl):
debug1(traceback.format_exc()) debug1(traceback.format_exc())
except BaseException: except BaseException:
debug2('An error occurred, ignoring it.') debug2('An error occurred, ignoring it.')
try:
flush_systemd_dns_cache()
except BaseException:
try:
debug1("Error trying to flush systemd dns cache.")
debug1(traceback.format_exc())
except BaseException:
debug2("An error occurred, ignoring it.")

View File

@ -15,9 +15,9 @@ POLL_TIME = 60 * 15
NETSTAT_POLL_TIME = 30 NETSTAT_POLL_TIME = 30
CACHEFILE = os.path.expanduser('~/.sshuttle.hosts') CACHEFILE = os.path.expanduser('~/.sshuttle.hosts')
# Have we already failed to write CACHEFILE?
CACHE_WRITE_FAILED = False
_nmb_ok = True
_smb_ok = True
hostnames = {} hostnames = {}
queue = {} queue = {}
try: try:
@ -33,7 +33,10 @@ def _is_ip(s):
def write_host_cache(): def write_host_cache():
"""If possible, write our hosts file to disk so future connections
can reuse the hosts that we already found."""
tmpname = '%s.%d.tmp' % (CACHEFILE, os.getpid()) tmpname = '%s.%d.tmp' % (CACHEFILE, os.getpid())
global CACHE_WRITE_FAILED
try: try:
f = open(tmpname, 'wb') f = open(tmpname, 'wb')
for name, ip in sorted(hostnames.items()): for name, ip in sorted(hostnames.items()):
@ -41,7 +44,15 @@ def write_host_cache():
f.close() f.close()
os.chmod(tmpname, 384) # 600 in octal, 'rw-------' os.chmod(tmpname, 384) # 600 in octal, 'rw-------'
os.rename(tmpname, CACHEFILE) os.rename(tmpname, CACHEFILE)
finally: CACHE_WRITE_FAILED = False
except (OSError, IOError):
# Write message if we haven't yet or if we get a failure after
# a previous success.
if not CACHE_WRITE_FAILED:
log("Failed to write host cache to temporary file "
"%s and rename it to %s" % (tmpname, CACHEFILE))
CACHE_WRITE_FAILED = True
try: try:
os.unlink(tmpname) os.unlink(tmpname)
except BaseException: except BaseException:
@ -49,25 +60,34 @@ def write_host_cache():
def read_host_cache(): def read_host_cache():
"""If possible, read the cache file from disk to populate hosts that
were found in a previous sshuttle run."""
try: try:
f = open(CACHEFILE) f = open(CACHEFILE)
except IOError: except (OSError, IOError):
_, e = sys.exc_info()[:2] _, e = sys.exc_info()[:2]
if e.errno == errno.ENOENT: if e.errno == errno.ENOENT:
return return
else: else:
raise log("Failed to read existing host cache file %s on remote host"
% CACHEFILE)
return
for line in f: for line in f:
words = line.strip().split(',') words = line.strip().split(',')
if len(words) == 2: if len(words) == 2:
(name, ip) = words (name, ip) = words
name = re.sub(r'[^-\w\.]', '-', name).strip() name = re.sub(r'[^-\w\.]', '-', name).strip()
# Remove characters that shouldn't be in IP
ip = re.sub(r'[^0-9.]', '', ip).strip() ip = re.sub(r'[^0-9.]', '', ip).strip()
if name and ip: if name and ip:
found_host(name, ip) found_host(name, ip)
def found_host(name, ip): def found_host(name, ip):
"""The provided name maps to the given IP. Add the host to the
hostnames list, send the host to the sshuttle client via
stdout, and write the host to the cache file.
"""
hostname = re.sub(r'\..*', '', name) hostname = re.sub(r'\..*', '', name)
hostname = re.sub(r'[^-\w\.]', '_', hostname) hostname = re.sub(r'[^-\w\.]', '_', hostname)
if (ip.startswith('127.') or ip.startswith('255.') or if (ip.startswith('127.') or ip.startswith('255.') or
@ -86,29 +106,37 @@ def found_host(name, ip):
def _check_etc_hosts(): def _check_etc_hosts():
debug2(' > hosts') """If possible, read /etc/hosts to find hosts."""
for line in open('/etc/hosts'): filename = '/etc/hosts'
line = re.sub(r'#.*', '', line) debug2(' > Reading %s on remote host' % filename)
try:
for line in open(filename):
line = re.sub(r'#.*', '', line) # remove comments
words = line.strip().split() words = line.strip().split()
if not words: if not words:
continue continue
ip = words[0] ip = words[0]
names = words[1:]
if _is_ip(ip): if _is_ip(ip):
names = words[1:]
debug3('< %s %r' % (ip, names)) debug3('< %s %r' % (ip, names))
for n in names: for n in names:
check_host(n) check_host(n)
found_host(n, ip) found_host(n, ip)
except (OSError, IOError):
debug1("Failed to read %s on remote host" % filename)
def _check_revdns(ip): def _check_revdns(ip):
"""Use reverse DNS to try to get hostnames from an IP addresses."""
debug2(' > rev: %s' % ip) debug2(' > rev: %s' % ip)
try: try:
r = socket.gethostbyaddr(ip) r = socket.gethostbyaddr(ip)
debug3('< %s' % r[0]) debug3('< %s' % r[0])
check_host(r[0]) check_host(r[0])
found_host(r[0], ip) found_host(r[0], ip)
except (socket.herror, UnicodeError): except (OSError, socket.error, UnicodeError):
# This case is expected to occur regularly.
# debug3('< %s gethostbyaddr failed on remote host' % ip)
pass pass
@ -136,115 +164,23 @@ def _check_netstat():
log('%r failed: %r' % (argv, e)) log('%r failed: %r' % (argv, e))
return return
# The same IPs may appear multiple times. Consolidate them so the
# debug message doesn't print the same IP repeatedly.
ip_list = []
for ip in re.findall(r'\d+\.\d+\.\d+\.\d+', content): for ip in re.findall(r'\d+\.\d+\.\d+\.\d+', content):
if ip not in ip_list:
ip_list.append(ip)
for ip in sorted(ip_list):
debug3('< %s' % ip) debug3('< %s' % ip)
check_host(ip) check_host(ip)
def _check_smb(hostname):
return
global _smb_ok
if not _smb_ok:
return
debug2(' > smb: %s' % hostname)
argv = ['smbclient', '-U', '%', '-L', hostname]
try:
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null,
env=get_env())
lines = p.stdout.readlines()
p.wait()
except OSError:
_, e = sys.exc_info()[:2]
log('%r failed: %r' % (argv, e))
_smb_ok = False
return
lines.reverse()
# junk at top
while lines:
line = lines.pop().strip()
if re.match(r'Server\s+', line):
break
# server list section:
# Server Comment
# ------ -------
while lines:
line = lines.pop().strip()
if not line or re.match(r'-+\s+-+', line):
continue
if re.match(r'Workgroup\s+Master', line):
break
words = line.split()
hostname = words[0].lower()
debug3('< %s' % hostname)
check_host(hostname)
# workgroup list section:
# Workgroup Master
# --------- ------
while lines:
line = lines.pop().strip()
if re.match(r'-+\s+', line):
continue
if not line:
break
words = line.split()
(workgroup, hostname) = (words[0].lower(), words[1].lower())
debug3('< group(%s) -> %s' % (workgroup, hostname))
check_host(hostname)
check_workgroup(workgroup)
if lines:
assert(0)
def _check_nmb(hostname, is_workgroup, is_master):
return
global _nmb_ok
if not _nmb_ok:
return
debug2(' > n%d%d: %s' % (is_workgroup, is_master, hostname))
argv = ['nmblookup'] + ['-M'] * is_master + ['--', hostname]
try:
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null,
env=get_env)
lines = p.stdout.readlines()
rv = p.wait()
except OSError:
_, e = sys.exc_info()[:2]
log('%r failed: %r' % (argv, e))
_nmb_ok = False
return
if rv:
log('%r returned %d' % (argv, rv))
return
for line in lines:
m = re.match(r'(\d+\.\d+\.\d+\.\d+) (\w+)<\w\w>\n', line)
if m:
g = m.groups()
(ip, name) = (g[0], g[1].lower())
debug3('< %s -> %s' % (name, ip))
if is_workgroup:
_enqueue(_check_smb, ip)
else:
found_host(name, ip)
check_host(name)
def check_host(hostname): def check_host(hostname):
if _is_ip(hostname): if _is_ip(hostname):
_enqueue(_check_revdns, hostname) _enqueue(_check_revdns, hostname)
else: else:
_enqueue(_check_dns, hostname) _enqueue(_check_dns, hostname)
_enqueue(_check_smb, hostname)
_enqueue(_check_nmb, hostname, False, False)
def check_workgroup(hostname):
_enqueue(_check_nmb, hostname, True, False)
_enqueue(_check_nmb, hostname, True, True)
def _enqueue(op, *args): def _enqueue(op, *args):
@ -277,18 +213,22 @@ def hw_main(seed_hosts, auto_hosts):
_enqueue(_check_netstat) _enqueue(_check_netstat)
check_host('localhost') check_host('localhost')
check_host(socket.gethostname()) check_host(socket.gethostname())
check_workgroup('workgroup')
check_workgroup('-')
while 1: while 1:
now = time.time() now = time.time()
# For each item in the queue
for t, last_polled in list(queue.items()): for t, last_polled in list(queue.items()):
(op, args) = t (op, args) = t
if not _stdin_still_ok(0): if not _stdin_still_ok(0):
break break
# Determine if we need to run.
maxtime = POLL_TIME maxtime = POLL_TIME
# netstat runs more often than other jobs
if op == _check_netstat: if op == _check_netstat:
maxtime = NETSTAT_POLL_TIME maxtime = NETSTAT_POLL_TIME
# Check if this jobs needs to run.
if now - last_polled > maxtime: if now - last_polled > maxtime:
queue[t] = time.time() queue[t] = time.time()
op(*args) op(*args)
@ -298,5 +238,5 @@ def hw_main(seed_hosts, auto_hosts):
break break
# FIXME: use a smarter timeout based on oldest last_polled # FIXME: use a smarter timeout based on oldest last_polled
if not _stdin_still_ok(1): if not _stdin_still_ok(1): # sleeps for up to 1 second
break break

View File

@ -17,7 +17,7 @@ def ipt_chain_exists(family, table, name):
cmd = 'iptables' cmd = 'iptables'
else: else:
raise Exception('Unsupported family "%s"' % family_to_string(family)) raise Exception('Unsupported family "%s"' % family_to_string(family))
argv = [cmd, '-t', table, '-nL'] argv = [cmd, '-w', '-t', table, '-nL']
try: try:
output = ssubprocess.check_output(argv, env=get_env()) output = ssubprocess.check_output(argv, env=get_env())
for line in output.decode('ASCII').split('\n'): for line in output.decode('ASCII').split('\n'):
@ -29,9 +29,9 @@ def ipt_chain_exists(family, table, name):
def ipt(family, table, *args): def ipt(family, table, *args):
if family == socket.AF_INET6: if family == socket.AF_INET6:
argv = ['ip6tables', '-t', table] + list(args) argv = ['ip6tables', '-w', '-t', table] + list(args)
elif family == socket.AF_INET: elif family == socket.AF_INET:
argv = ['iptables', '-t', table] + list(args) argv = ['iptables', '-w', '-t', table] + list(args)
else: else:
raise Exception('Unsupported family "%s"' % family_to_string(family)) raise Exception('Unsupported family "%s"' % family_to_string(family))
debug1('%s' % ' '.join(argv)) debug1('%s' % ' '.join(argv))
@ -49,25 +49,3 @@ def nft(family, table, action, *args):
rv = ssubprocess.call(argv, env=get_env()) rv = ssubprocess.call(argv, env=get_env())
if rv: if rv:
raise Fatal('%r returned %d' % (argv, rv)) raise Fatal('%r returned %d' % (argv, rv))
_no_ttl_module = False
def ipt_ttl(family, *args):
global _no_ttl_module
if not _no_ttl_module:
# we avoid infinite loops by generating server-side connections
# with ttl 63. This makes the client side not recapture those
# connections, in case client == server.
try:
argsplus = list(args)
ipt(family, *argsplus)
except Fatal:
ipt(family, *args)
# we only get here if the non-ttl attempt succeeds
log('WARNING: your iptables is missing '
'the ttl module.')
_no_ttl_module = True
else:
ipt(family, *args)

View File

@ -91,7 +91,7 @@ class BaseMethod(object):
(key, self.name)) (key, self.name))
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
user): user, tmark):
raise NotImplementedError() raise NotImplementedError()
def restore_firewall(self, port, family, udp, user): def restore_firewall(self, port, family, udp, user):

View File

@ -177,7 +177,6 @@ class Method(BaseMethod):
sender.setsockopt(socket.SOL_IP, IP_BINDANY, 1) sender.setsockopt(socket.SOL_IP, IP_BINDANY, 1)
sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sender.setsockopt(socket.SOL_IP, socket.IP_TTL, 63)
sender.bind(srcip) sender.bind(srcip)
sender.sendto(data, dstip) sender.sendto(data, dstip)
sender.close() sender.close()
@ -189,7 +188,12 @@ class Method(BaseMethod):
# udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVDSTADDR, 1) # udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVDSTADDR, 1)
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
user, ttl): user, tmark):
# TODO: The ttl hack to allow the host and server to run on
# the same machine has been removed but this method hasn't
# been updated yet.
ttl = 63
# IPv6 not supported # IPv6 not supported
if family not in [socket.AF_INET]: if family not in [socket.AF_INET]:
raise Exception( raise Exception(

View File

@ -1,7 +1,7 @@
import socket import socket
from sshuttle.firewall import subnet_weight from sshuttle.firewall import subnet_weight
from sshuttle.helpers import family_to_string, which, debug2 from sshuttle.helpers import family_to_string, which, debug2
from sshuttle.linux import ipt, ipt_ttl, ipt_chain_exists, nonfatal from sshuttle.linux import ipt, ipt_chain_exists, nonfatal
from sshuttle.methods import BaseMethod from sshuttle.methods import BaseMethod
@ -13,23 +13,18 @@ class Method(BaseMethod):
# recently-started one will win (because we use "-I OUTPUT 1" instead of # recently-started one will win (because we use "-I OUTPUT 1" instead of
# "-A OUTPUT"). # "-A OUTPUT").
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
user, ttl): user, tmark):
# only ipv4 supported with NAT if family != socket.AF_INET and family != socket.AF_INET6:
if family != socket.AF_INET:
raise Exception( raise Exception(
'Address family "%s" unsupported by nat method_name' 'Address family "%s" unsupported by nat method_name'
% family_to_string(family)) % family_to_string(family))
if udp: if udp:
raise Exception("UDP not supported by nat method_name") raise Exception("UDP not supported by nat method_name")
table = "nat" table = "nat"
def _ipt(*args): def _ipt(*args):
return ipt(family, table, *args) return ipt(family, table, *args)
def _ipt_ttl(*args):
return ipt_ttl(family, table, *args)
def _ipm(*args): def _ipm(*args):
return ipt(family, "mangle", *args) return ipt(family, "mangle", *args)
@ -50,16 +45,11 @@ class Method(BaseMethod):
_ipt('-I', 'OUTPUT', '1', *args) _ipt('-I', 'OUTPUT', '1', *args)
_ipt('-I', 'PREROUTING', '1', *args) _ipt('-I', 'PREROUTING', '1', *args)
# This TTL hack allows the client and server to run on the
# same host. The connections the sshuttle server makes will
# have TTL set to 63.
_ipt_ttl('-A', chain, '-j', 'RETURN', '-m', 'ttl', '--ttl', '%s' % ttl)
# Redirect DNS traffic as requested. This includes routing traffic # Redirect DNS traffic as requested. This includes routing traffic
# to localhost DNS servers through sshuttle. # to localhost DNS servers through sshuttle.
for _, 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', chain, '-j', 'REDIRECT', _ipt('-A', chain, '-j', 'REDIRECT',
'--dest', '%s/32' % ip, '--dest', '%s' % ip,
'-p', 'udp', '-p', 'udp',
'--dport', '53', '--dport', '53',
'--to-ports', str(dnsport)) '--to-ports', str(dnsport))
@ -87,7 +77,7 @@ class Method(BaseMethod):
def restore_firewall(self, port, family, udp, user): def restore_firewall(self, port, family, udp, user):
# only ipv4 supported with NAT # only ipv4 supported with NAT
if family != socket.AF_INET: if family != socket.AF_INET and family != socket.AF_INET6:
raise Exception( raise Exception(
'Address family "%s" unsupported by nat method_name' 'Address family "%s" unsupported by nat method_name'
% family_to_string(family)) % family_to_string(family))
@ -99,9 +89,6 @@ class Method(BaseMethod):
def _ipt(*args): def _ipt(*args):
return ipt(family, table, *args) return ipt(family, table, *args)
def _ipt_ttl(*args):
return ipt_ttl(family, table, *args)
def _ipm(*args): def _ipm(*args):
return ipt(family, "mangle", *args) return ipt(family, "mangle", *args)
@ -123,6 +110,7 @@ class Method(BaseMethod):
def get_supported_features(self): def get_supported_features(self):
result = super(Method, self).get_supported_features() result = super(Method, self).get_supported_features()
result.user = True result.user = True
result.ipv6 = True
return result return result
def is_supported(self): def is_supported(self):

View File

@ -13,7 +13,7 @@ class Method(BaseMethod):
# recently-started one will win (because we use "-I OUTPUT 1" instead of # recently-started one will win (because we use "-I OUTPUT 1" instead of
# "-A OUTPUT"). # "-A OUTPUT").
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
user, ttl): user, tmark):
if udp: if udp:
raise Exception("UDP not supported by nft") raise Exception("UDP not supported by nft")
@ -45,14 +45,6 @@ class Method(BaseMethod):
else: else:
_nft('add rule', chain, 'meta', 'nfproto', '!=', 'ipv6', 'return') _nft('add rule', chain, 'meta', 'nfproto', '!=', 'ipv6', 'return')
# This TTL hack allows the client and server to run on the
# same host. The connections the sshuttle server makes will
# have TTL set to 63.
if family == socket.AF_INET:
_nft('add rule', chain, 'ip ttl == 63 return')
elif family == socket.AF_INET6:
_nft('add rule', chain, 'ip6 hoplimit == 63 return')
# Strings to use below to simplify our code # Strings to use below to simplify our code
if family == socket.AF_INET: if family == socket.AF_INET:
ip_version_l = 'ipv4' ip_version_l = 'ipv4'

View File

@ -11,8 +11,8 @@ from fcntl import ioctl
from ctypes import c_char, c_uint8, c_uint16, c_uint32, Union, Structure, \ from ctypes import c_char, c_uint8, c_uint16, c_uint32, Union, Structure, \
sizeof, addressof, memmove sizeof, addressof, memmove
from sshuttle.firewall import subnet_weight from sshuttle.firewall import subnet_weight
from sshuttle.helpers import debug1, debug2, debug3, Fatal, family_to_string, \ from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, \
get_env, which family_to_string, get_env, which
from sshuttle.methods import BaseMethod from sshuttle.methods import BaseMethod
@ -393,6 +393,10 @@ def pfctl(args, stdin=None):
env=get_env()) env=get_env())
o = p.communicate(stdin) o = p.communicate(stdin)
if p.returncode: if p.returncode:
log('%r returned %d, stdout and stderr follows: ' %
(argv, p.returncode))
log("stdout:\n%s" % o[0].decode("ascii"))
log("stderr:\n%s" % o[1].decode("ascii"))
raise Fatal('%r returned %d' % (argv, p.returncode)) raise Fatal('%r returned %d' % (argv, p.returncode))
return o return o
@ -444,7 +448,7 @@ class Method(BaseMethod):
return sock.getsockname() return sock.getsockname()
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
user, ttl): user, tmark):
if family not in [socket.AF_INET, socket.AF_INET6]: if family not in [socket.AF_INET, socket.AF_INET6]:
raise Exception( raise Exception(
'Address family "%s" unsupported by pf method_name' 'Address family "%s" unsupported by pf method_name'

View File

@ -1,7 +1,7 @@
import struct import struct
from sshuttle.firewall import subnet_weight from sshuttle.firewall import subnet_weight
from sshuttle.helpers import family_to_string from sshuttle.helpers import family_to_string
from sshuttle.linux import ipt, ipt_ttl, ipt_chain_exists from sshuttle.linux import ipt, ipt_chain_exists
from sshuttle.methods import BaseMethod from sshuttle.methods import BaseMethod
from sshuttle.helpers import debug1, debug2, debug3, Fatal, which from sshuttle.helpers import debug1, debug2, debug3, Fatal, which
@ -151,17 +151,7 @@ class Method(BaseMethod):
udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVORIGDSTADDR, 1) udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
user, ttl): user, tmark):
if self.firewall is None:
tmark = '1'
else:
tmark = self.firewall.tmark
self.setup_firewall_tproxy(port, dnsport, nslist, family, subnets, udp,
user, tmark)
def setup_firewall_tproxy(self, port, dnsport, nslist, family, subnets,
udp, user, tmark):
if family not in [socket.AF_INET, socket.AF_INET6]: if family not in [socket.AF_INET, socket.AF_INET6]:
raise Exception( raise Exception(
'Address family "%s" unsupported by tproxy method' 'Address family "%s" unsupported by tproxy method'
@ -172,9 +162,6 @@ class Method(BaseMethod):
def _ipt(*args): def _ipt(*args):
return ipt(family, table, *args) return ipt(family, table, *args)
def _ipt_ttl(*args):
return ipt_ttl(family, table, *args)
def _ipt_proto_ports(proto, fport, lport): def _ipt_proto_ports(proto, fport, lport):
return proto + ('--dport', '%d:%d' % (fport, lport)) \ return proto + ('--dport', '%d:%d' % (fport, lport)) \
if fport else proto if fport else proto
@ -192,8 +179,8 @@ class Method(BaseMethod):
_ipt('-F', divert_chain) _ipt('-F', divert_chain)
_ipt('-N', tproxy_chain) _ipt('-N', tproxy_chain)
_ipt('-F', tproxy_chain) _ipt('-F', tproxy_chain)
_ipt('-I', 'OUTPUT', tmark, '-j', mark_chain) _ipt('-I', 'OUTPUT', '1', '-j', mark_chain)
_ipt('-I', 'PREROUTING', tmark, '-j', tproxy_chain) _ipt('-I', 'PREROUTING', '1', '-j', tproxy_chain)
# Don't have packets sent to any of our local IP addresses go # Don't have packets sent to any of our local IP addresses go
# through the tproxy or mark chains. # through the tproxy or mark chains.
@ -224,7 +211,7 @@ class Method(BaseMethod):
'--dest', '%s/32' % ip, '--dest', '%s/32' % ip,
'-m', 'udp', '-p', 'udp', '--dport', '53') '-m', 'udp', '-p', 'udp', '--dport', '53')
_ipt('-A', tproxy_chain, '-j', 'TPROXY', _ipt('-A', tproxy_chain, '-j', 'TPROXY',
'--tproxy-mark', '0x'+tmark+'/0x'+tmark, '--tproxy-mark', tmark,
'--dest', '%s/32' % ip, '--dest', '%s/32' % ip,
'-m', 'udp', '-p', 'udp', '--dport', '53', '-m', 'udp', '-p', 'udp', '--dport', '53',
'--on-port', str(dnsport)) '--on-port', str(dnsport))
@ -249,7 +236,7 @@ class Method(BaseMethod):
'-m', 'tcp', '-m', 'tcp',
*tcp_ports) *tcp_ports)
_ipt('-A', tproxy_chain, '-j', 'TPROXY', _ipt('-A', tproxy_chain, '-j', 'TPROXY',
'--tproxy-mark', '0x'+tmark+'/0x'+tmark, '--tproxy-mark', tmark,
'--dest', '%s/%s' % (snet, swidth), '--dest', '%s/%s' % (snet, swidth),
'-m', 'tcp', '-m', 'tcp',
*(tcp_ports + ('--on-port', str(port)))) *(tcp_ports + ('--on-port', str(port))))
@ -273,7 +260,7 @@ class Method(BaseMethod):
'-m', 'udp', '-m', 'udp',
*udp_ports) *udp_ports)
_ipt('-A', tproxy_chain, '-j', 'TPROXY', _ipt('-A', tproxy_chain, '-j', 'TPROXY',
'--tproxy-mark', '0x'+tmark+'/0x'+tmark, '--tproxy-mark', tmark,
'--dest', '%s/%s' % (snet, swidth), '--dest', '%s/%s' % (snet, swidth),
'-m', 'udp', '-m', 'udp',
*(udp_ports + ('--on-port', str(port)))) *(udp_ports + ('--on-port', str(port))))
@ -289,9 +276,6 @@ class Method(BaseMethod):
def _ipt(*args): def _ipt(*args):
return ipt(family, table, *args) return ipt(family, table, *args)
def _ipt_ttl(*args):
return ipt_ttl(family, table, *args)
mark_chain = 'sshuttle-m-%s' % port mark_chain = 'sshuttle-m-%s' % port
tproxy_chain = 'sshuttle-t-%s' % port tproxy_chain = 'sshuttle-t-%s' % port
divert_chain = 'sshuttle-d-%s' % port divert_chain = 'sshuttle-d-%s' % port

View File

@ -132,6 +132,7 @@ def parse_ipport(s):
def parse_list(lst): def parse_list(lst):
"""Parse a comma separated string into a list."""
return re.split(r'[\s,]+', lst.strip()) if lst else [] return re.split(r'[\s,]+', lst.strip()) if lst else []
@ -172,7 +173,7 @@ class MyArgumentParser(ArgumentParser):
parser = MyArgumentParser( parser = MyArgumentParser(
prog="sshuttle", prog="sshuttle",
usage="%(prog)s [-l [ip:]port] [-r [user@]sshserver[:port]] <subnets...>", usage="%(prog)s [-l [ip:]port] -r [user@]sshserver[:port] <subnets...>",
fromfile_prefix_chars="@" fromfile_prefix_chars="@"
) )
parser.add_argument( parser.add_argument(
@ -220,6 +221,7 @@ parser.add_argument(
type=parse_list, type=parse_list,
help=""" help="""
capture and forward DNS requests made to the following servers capture and forward DNS requests made to the following servers
(comma separated)
""" """
) )
parser.add_argument( parser.add_argument(
@ -280,7 +282,7 @@ parser.add_argument(
action="count", action="count",
default=0, default=0,
help=""" help="""
increase debug message verbosity increase debug message verbosity (can be used more than once)
""" """
) )
parser.add_argument( parser.add_argument(
@ -387,14 +389,6 @@ parser.add_argument(
(internal use only) (internal use only)
""" """
) )
parser.add_argument(
"--ttl",
default="63",
help="""
Override the TTL for the connections made by the sshuttle server.
Default is 63.
"""
)
parser.add_argument( parser.add_argument(
"--hostwatch", "--hostwatch",
action="store_true", action="store_true",
@ -444,8 +438,9 @@ parser.add_argument(
parser.add_argument( parser.add_argument(
"-t", "--tmark", "-t", "--tmark",
metavar="[MARK]", metavar="[MARK]",
default="1", default="0x01",
help=""" help="""
transproxy optional traffic mark with provided MARK value tproxy optional traffic mark with provided MARK value in
hexadecimal (default '0x01')
""" """
) )

View File

@ -154,7 +154,7 @@ class Hostwatch:
class DnsProxy(Handler): class DnsProxy(Handler):
def __init__(self, mux, chan, request, to_nameserver, ttl): def __init__(self, mux, chan, request, to_nameserver):
Handler.__init__(self, []) Handler.__init__(self, [])
self.timeout = time.time() + 30 self.timeout = time.time() + 30
self.mux = mux self.mux = mux
@ -164,7 +164,6 @@ class DnsProxy(Handler):
self.peers = {} self.peers = {}
self.to_ns_peer = None self.to_ns_peer = None
self.to_ns_port = None self.to_ns_port = None
self.ttl = ttl
if to_nameserver is None: if to_nameserver is None:
self.to_nameserver = None self.to_nameserver = None
else: else:
@ -194,7 +193,6 @@ class DnsProxy(Handler):
family, sockaddr = self._addrinfo(peer, port) family, sockaddr = self._addrinfo(peer, port)
sock = socket.socket(family, socket.SOCK_DGRAM) sock = socket.socket(family, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_IP, socket.IP_TTL, self.ttl)
sock.connect(sockaddr) sock.connect(sockaddr)
self.peers[sock] = peer self.peers[sock] = peer
@ -243,15 +241,13 @@ class DnsProxy(Handler):
class UdpProxy(Handler): class UdpProxy(Handler):
def __init__(self, mux, chan, family, ttl): def __init__(self, mux, chan, family):
sock = socket.socket(family, socket.SOCK_DGRAM) sock = socket.socket(family, socket.SOCK_DGRAM)
Handler.__init__(self, [sock]) Handler.__init__(self, [sock])
self.timeout = time.time() + 30 self.timeout = time.time() + 30
self.mux = mux self.mux = mux
self.chan = chan self.chan = chan
self.sock = sock self.sock = sock
if family == socket.AF_INET:
self.sock.setsockopt(socket.SOL_IP, socket.IP_TTL, ttl)
def send(self, dstip, data): def send(self, dstip, data):
debug2('UDP: sending to %r port %d' % dstip) debug2('UDP: sending to %r port %d' % dstip)
@ -275,7 +271,7 @@ class UdpProxy(Handler):
def main(latency_control, latency_buffer_size, auto_hosts, to_nameserver, def main(latency_control, latency_buffer_size, auto_hosts, to_nameserver,
auto_nets, ttl, localhost_detector): auto_nets, localhost_detector):
try: try:
helpers.logprefix = ' s: ' helpers.logprefix = ' s: '
debug1('Starting server with Python version %s' debug1('Starting server with Python version %s'
@ -362,7 +358,7 @@ def main(latency_control, latency_buffer_size, auto_hosts, to_nameserver,
def dns_req(channel, data): def dns_req(channel, data):
debug2('Incoming DNS request channel=%d.' % channel) debug2('Incoming DNS request channel=%d.' % channel)
h = DnsProxy(mux, channel, data, to_nameserver, ttl) h = DnsProxy(mux, channel, data, to_nameserver)
handlers.append(h) handlers.append(h)
dnshandlers[channel] = h dnshandlers[channel] = h
mux.got_dns_req = dns_req mux.got_dns_req = dns_req
@ -393,7 +389,7 @@ def main(latency_control, latency_buffer_size, auto_hosts, to_nameserver,
raise Fatal('UDP connection channel %d already open' % raise Fatal('UDP connection channel %d already open' %
channel) channel)
else: else:
h = UdpProxy(mux, channel, family, ttl) h = UdpProxy(mux, channel, family)
handlers.append(h) handlers.append(h)
udphandlers[channel] = h udphandlers[channel] = h
mux.got_udp_open = udp_open mux.got_udp_open = udp_open

View File

@ -586,7 +586,7 @@ class MuxWrapper(SockWrapper):
def connect_dst(family, ip, port): def connect_dst(family, ip, port):
debug2('Connecting to %s:%d' % (ip, port)) debug2('Connecting to %s:%d' % (ip, port))
outsock = socket.socket(family) outsock = socket.socket(family)
outsock.setsockopt(socket.SOL_IP, socket.IP_TTL, 63)
return SockWrapper(outsock, outsock, return SockWrapper(outsock, outsock,
connect_to=(ip, port), connect_to=(ip, port),
peername='%s:%d' % (ip, port)) peername='%s:%d' % (ip, port))

View File

@ -19,9 +19,14 @@ Cmnd_Alias %(ca)s = /usr/bin/env PYTHONPATH=%(dist_packages)s %(py)s %(path)s *
%(user_name)s ALL=NOPASSWD: %(ca)s %(user_name)s ALL=NOPASSWD: %(ca)s
''' '''
warning_msg = "# WARNING: When you allow a user to run sshuttle as root,\n" \
"# they can then use sshuttle's --ssh-cmd option to run any\n" \
"# command as root.\n"
def build_config(user_name): def build_config(user_name):
content = template % { content = warning_msg
content += template % {
'ca': command_alias, 'ca': command_alias,
'dist_packages': path_to_dist_packages, 'dist_packages': path_to_dist_packages,
'py': sys.executable, 'py': sys.executable,
@ -42,6 +47,7 @@ def save_config(content, file_name):
process.stdin.write(content.encode()) process.stdin.write(content.encode())
streamdata = process.communicate()[0] streamdata = process.communicate()[0]
sys.stdout.write(streamdata.decode("ASCII"))
returncode = process.returncode returncode = process.returncode
if returncode: if returncode:
@ -61,4 +67,5 @@ def sudoers(user_name=None, no_modify=None, file_name=None):
sys.stdout.write(content) sys.stdout.write(content)
exit(0) exit(0)
else: else:
sys.stdout.write(warning_msg)
save_config(content, file_name) save_config(content, file_name)

View File

@ -15,7 +15,7 @@ NSLIST
{inet},1.2.3.33 {inet},1.2.3.33
{inet6},2404:6800:4004:80c::33 {inet6},2404:6800:4004:80c::33
PORTS 1024,1025,1026,1027 PORTS 1024,1025,1026,1027
GO 1 - GO 1 - 0x01
HOST 1.2.3.3,existing HOST 1.2.3.3,existing
""".format(inet=AF_INET, inet6=AF_INET6)) """.format(inet=AF_INET, inet6=AF_INET6))
stdout = Mock() stdout = Mock()
@ -100,7 +100,7 @@ def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts):
mock_get_method("not_auto").name = "test" mock_get_method("not_auto").name = "test"
mock_get_method.reset_mock() mock_get_method.reset_mock()
sshuttle.firewall.main("not_auto", False, 63) sshuttle.firewall.main("not_auto", False)
assert mock_rewrite_etc_hosts.mock_calls == [ assert mock_rewrite_etc_hosts.mock_calls == [
call({'1.2.3.3': 'existing'}, 1024), call({'1.2.3.3': 'existing'}, 1024),
@ -126,7 +126,7 @@ def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts):
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)], (AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
True, True,
None, None,
63), '0x01'),
call().setup_firewall( call().setup_firewall(
1025, 1027, 1025, 1027,
[(AF_INET, u'1.2.3.33')], [(AF_INET, u'1.2.3.33')],
@ -135,7 +135,7 @@ def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts):
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)], (AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
True, True,
None, None,
63), '0x01'),
call().restore_firewall(1024, AF_INET6, True, None), call().restore_firewall(1024, AF_INET6, True, None),
call().restore_firewall(1025, AF_INET, True, None), call().restore_firewall(1025, AF_INET, True, None),
] ]

View File

@ -11,7 +11,7 @@ from sshuttle.methods import get_method
def test_get_supported_features(): def test_get_supported_features():
method = get_method('nat') method = get_method('nat')
features = method.get_supported_features() features = method.get_supported_features()
assert not features.ipv6 assert features.ipv6
assert not features.udp assert not features.udp
assert features.dns assert features.dns
@ -85,27 +85,52 @@ def test_firewall_command():
@patch('sshuttle.methods.nat.ipt') @patch('sshuttle.methods.nat.ipt')
@patch('sshuttle.methods.nat.ipt_ttl')
@patch('sshuttle.methods.nat.ipt_chain_exists') @patch('sshuttle.methods.nat.ipt_chain_exists')
def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt): def test_setup_firewall(mock_ipt_chain_exists, mock_ipt):
mock_ipt_chain_exists.return_value = True mock_ipt_chain_exists.return_value = True
method = get_method('nat') method = get_method('nat')
assert method.name == 'nat' assert method.name == 'nat'
with pytest.raises(Exception) as excinfo: assert mock_ipt_chain_exists.mock_calls == []
assert mock_ipt.mock_calls == []
method.setup_firewall( method.setup_firewall(
1024, 1026, 1024, 1026,
[(AF_INET6, u'2404:6800:4004:80c::33')], [(AF_INET6, u'2404:6800:4004:80c::33')],
AF_INET6, AF_INET6,
[(AF_INET6, 64, False, u'2404:6800:4004:80c::', 0, 0), [(AF_INET6, 64, False, u'2404:6800:4004:80c::', 0, 0),
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)], (AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)],
True, False,
None, None,
63) '0x01')
assert str(excinfo.value) \
== 'Address family "AF_INET6" unsupported by nat method_name' assert mock_ipt_chain_exists.mock_calls == [
call(AF_INET6, 'nat', 'sshuttle-1024')
]
assert mock_ipt.mock_calls == [
call(AF_INET6, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1024'),
call(AF_INET6, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1024'),
call(AF_INET6, 'nat', '-F', 'sshuttle-1024'),
call(AF_INET6, 'nat', '-X', 'sshuttle-1024'),
call(AF_INET6, 'nat', '-N', 'sshuttle-1024'),
call(AF_INET6, 'nat', '-F', 'sshuttle-1024'),
call(AF_INET6, 'nat', '-I', 'OUTPUT', '1', '-j', 'sshuttle-1024'),
call(AF_INET6, 'nat', '-I', 'PREROUTING', '1', '-j', 'sshuttle-1024'),
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'REDIRECT',
'--dest', u'2404:6800:4004:80c::33', '-p', 'udp',
'--dport', '53', '--to-ports', '1026'),
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'RETURN',
'-m', 'addrtype', '--dst-type', 'LOCAL'),
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'RETURN',
'--dest', u'2404:6800:4004:80c::101f/128', '-p', 'tcp',
'--dport', '80:80'),
call(AF_INET6, 'nat', '-A', 'sshuttle-1024', '-j', 'REDIRECT',
'--dest', u'2404:6800:4004:80c::/64', '-p', 'tcp',
'--to-ports', '1024')
]
mock_ipt_chain_exists.reset_mock()
mock_ipt.reset_mock()
assert mock_ipt_chain_exists.mock_calls == [] assert mock_ipt_chain_exists.mock_calls == []
assert mock_ipt_ttl.mock_calls == []
assert mock_ipt.mock_calls == [] assert mock_ipt.mock_calls == []
with pytest.raises(Exception) as excinfo: with pytest.raises(Exception) as excinfo:
@ -117,10 +142,9 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)], (AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
True, True,
None, None,
63) '0x01')
assert str(excinfo.value) == 'UDP not supported by nat method_name' assert str(excinfo.value) == 'UDP not supported by nat method_name'
assert mock_ipt_chain_exists.mock_calls == [] assert mock_ipt_chain_exists.mock_calls == []
assert mock_ipt_ttl.mock_calls == []
assert mock_ipt.mock_calls == [] assert mock_ipt.mock_calls == []
method.setup_firewall( method.setup_firewall(
@ -131,14 +155,10 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
(AF_INET, 32, True, u'1.2.3.66', 8080, 8080)], (AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
False, False,
None, None,
63) '0x01')
assert mock_ipt_chain_exists.mock_calls == [ assert mock_ipt_chain_exists.mock_calls == [
call(AF_INET, 'nat', 'sshuttle-1025') call(AF_INET, 'nat', 'sshuttle-1025')
] ]
assert mock_ipt_ttl.mock_calls == [
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
'-m', 'ttl', '--ttl', '63')
]
assert mock_ipt.mock_calls == [ assert mock_ipt.mock_calls == [
call(AF_INET, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'), call(AF_INET, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
call(AF_INET, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'), call(AF_INET, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'),
@ -149,7 +169,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
call(AF_INET, 'nat', '-I', 'OUTPUT', '1', '-j', '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', '-I', 'PREROUTING', '1', '-j', 'sshuttle-1025'),
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT', call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT',
'--dest', u'1.2.3.33/32', '-p', 'udp', '--dest', u'1.2.3.33', '-p', 'udp',
'--dport', '53', '--to-ports', '1027'), '--dport', '53', '--to-ports', '1027'),
call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN', call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
'-m', 'addrtype', '--dst-type', 'LOCAL'), '-m', 'addrtype', '--dst-type', 'LOCAL'),
@ -160,20 +180,33 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
'--to-ports', '1025') '--to-ports', '1025')
] ]
mock_ipt_chain_exists.reset_mock() mock_ipt_chain_exists.reset_mock()
mock_ipt_ttl.reset_mock()
mock_ipt.reset_mock() mock_ipt.reset_mock()
method.restore_firewall(1025, AF_INET, False, None) method.restore_firewall(1025, AF_INET, False, None)
assert mock_ipt_chain_exists.mock_calls == [ assert mock_ipt_chain_exists.mock_calls == [
call(AF_INET, 'nat', 'sshuttle-1025') call(AF_INET, 'nat', 'sshuttle-1025')
] ]
assert mock_ipt_ttl.mock_calls == []
assert mock_ipt.mock_calls == [ assert mock_ipt.mock_calls == [
call(AF_INET, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'), call(AF_INET, 'nat', '-D', 'OUTPUT', '-j',
call(AF_INET, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'), 'sshuttle-1025'),
call(AF_INET, 'nat', '-D', 'PREROUTING', '-j',
'sshuttle-1025'),
call(AF_INET, 'nat', '-F', 'sshuttle-1025'), call(AF_INET, 'nat', '-F', 'sshuttle-1025'),
call(AF_INET, 'nat', '-X', 'sshuttle-1025') call(AF_INET, 'nat', '-X', 'sshuttle-1025')
] ]
mock_ipt_chain_exists.reset_mock() mock_ipt_chain_exists.reset_mock()
mock_ipt_ttl.reset_mock() mock_ipt.reset_mock()
method.restore_firewall(1025, AF_INET6, False, None)
assert mock_ipt_chain_exists.mock_calls == [
call(AF_INET6, 'nat', 'sshuttle-1025')
]
assert mock_ipt.mock_calls == [
call(AF_INET6, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
call(AF_INET6, 'nat', '-D', 'PREROUTING', '-j',
'sshuttle-1025'),
call(AF_INET6, 'nat', '-F', 'sshuttle-1025'),
call(AF_INET6, 'nat', '-X', 'sshuttle-1025')
]
mock_ipt_chain_exists.reset_mock()
mock_ipt.reset_mock() mock_ipt.reset_mock()

View File

@ -187,7 +187,7 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)], (AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
False, False,
None, None,
63) '0x01')
assert mock_ioctl.mock_calls == [ assert mock_ioctl.mock_calls == [
call(mock_pf_get_dev(), 0xC4704433, ANY), call(mock_pf_get_dev(), 0xC4704433, ANY),
call(mock_pf_get_dev(), 0xCC20441A, ANY), call(mock_pf_get_dev(), 0xCC20441A, ANY),
@ -227,7 +227,7 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
(AF_INET, 32, True, u'1.2.3.66', 80, 80)], (AF_INET, 32, True, u'1.2.3.66', 80, 80)],
True, True,
None, None,
63) '0x01')
assert str(excinfo.value) == 'UDP not supported by pf method_name' assert str(excinfo.value) == 'UDP not supported by pf method_name'
assert mock_pf_get_dev.mock_calls == [] assert mock_pf_get_dev.mock_calls == []
assert mock_ioctl.mock_calls == [] assert mock_ioctl.mock_calls == []
@ -241,7 +241,7 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
(AF_INET, 32, True, u'1.2.3.66', 80, 80)], (AF_INET, 32, True, u'1.2.3.66', 80, 80)],
False, False,
None, None,
63) '0x01')
assert mock_ioctl.mock_calls == [ assert mock_ioctl.mock_calls == [
call(mock_pf_get_dev(), 0xC4704433, ANY), call(mock_pf_get_dev(), 0xC4704433, ANY),
call(mock_pf_get_dev(), 0xCC20441A, ANY), call(mock_pf_get_dev(), 0xCC20441A, ANY),
@ -302,7 +302,7 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl,
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)], (AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
False, False,
None, None,
63) '0x01')
assert mock_pfctl.mock_calls == [ assert mock_pfctl.mock_calls == [
call('-s all'), call('-s all'),
@ -335,7 +335,7 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl,
(AF_INET, 32, True, u'1.2.3.66', 80, 80)], (AF_INET, 32, True, u'1.2.3.66', 80, 80)],
True, True,
None, None,
63) '0x01')
assert str(excinfo.value) == 'UDP not supported by pf method_name' assert str(excinfo.value) == 'UDP not supported by pf method_name'
assert mock_pf_get_dev.mock_calls == [] assert mock_pf_get_dev.mock_calls == []
assert mock_ioctl.mock_calls == [] assert mock_ioctl.mock_calls == []
@ -349,7 +349,7 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl,
(AF_INET, 32, True, u'1.2.3.66', 80, 80)], (AF_INET, 32, True, u'1.2.3.66', 80, 80)],
False, False,
None, None,
63) '0x01')
assert mock_ioctl.mock_calls == [ assert mock_ioctl.mock_calls == [
call(mock_pf_get_dev(), 0xC4704433, ANY), call(mock_pf_get_dev(), 0xC4704433, ANY),
call(mock_pf_get_dev(), 0xCBE0441A, ANY), call(mock_pf_get_dev(), 0xCBE0441A, ANY),
@ -408,7 +408,7 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)], (AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
False, False,
None, None,
63) '0x01')
assert mock_ioctl.mock_calls == [ assert mock_ioctl.mock_calls == [
call(mock_pf_get_dev(), 0xcd60441a, ANY), call(mock_pf_get_dev(), 0xcd60441a, ANY),
@ -445,7 +445,7 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
(AF_INET, 32, True, u'1.2.3.66', 80, 80)], (AF_INET, 32, True, u'1.2.3.66', 80, 80)],
True, True,
None, None,
63) '0x01')
assert str(excinfo.value) == 'UDP not supported by pf method_name' assert str(excinfo.value) == 'UDP not supported by pf method_name'
assert mock_pf_get_dev.mock_calls == [] assert mock_pf_get_dev.mock_calls == []
assert mock_ioctl.mock_calls == [] assert mock_ioctl.mock_calls == []
@ -459,7 +459,7 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
(AF_INET, 32, True, u'1.2.3.66', 80, 80)], (AF_INET, 32, True, u'1.2.3.66', 80, 80)],
False, False,
None, None,
63) '0x01')
assert mock_ioctl.mock_calls == [ assert mock_ioctl.mock_calls == [
call(mock_pf_get_dev(), 0xcd60441a, ANY), call(mock_pf_get_dev(), 0xcd60441a, ANY),
call(mock_pf_get_dev(), 0xcd60441a, ANY), call(mock_pf_get_dev(), 0xcd60441a, ANY),

View File

@ -92,9 +92,8 @@ def test_firewall_command():
@patch('sshuttle.methods.tproxy.ipt') @patch('sshuttle.methods.tproxy.ipt')
@patch('sshuttle.methods.tproxy.ipt_ttl')
@patch('sshuttle.methods.tproxy.ipt_chain_exists') @patch('sshuttle.methods.tproxy.ipt_chain_exists')
def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt): def test_setup_firewall(mock_ipt_chain_exists, mock_ipt):
mock_ipt_chain_exists.return_value = True mock_ipt_chain_exists.return_value = True
method = get_method('tproxy') method = get_method('tproxy')
assert method.name == 'tproxy' assert method.name == 'tproxy'
@ -109,13 +108,12 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
(AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)], (AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
True, True,
None, None,
63) '0x01')
assert mock_ipt_chain_exists.mock_calls == [ assert mock_ipt_chain_exists.mock_calls == [
call(AF_INET6, 'mangle', 'sshuttle-m-1024'), call(AF_INET6, 'mangle', 'sshuttle-m-1024'),
call(AF_INET6, 'mangle', 'sshuttle-t-1024'), call(AF_INET6, 'mangle', 'sshuttle-t-1024'),
call(AF_INET6, 'mangle', 'sshuttle-d-1024') call(AF_INET6, 'mangle', 'sshuttle-d-1024')
] ]
assert mock_ipt_ttl.mock_calls == []
assert mock_ipt.mock_calls == [ assert mock_ipt.mock_calls == [
call(AF_INET6, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1024'), call(AF_INET6, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1024'),
call(AF_INET6, 'mangle', '-F', 'sshuttle-m-1024'), call(AF_INET6, 'mangle', '-F', 'sshuttle-m-1024'),
@ -139,17 +137,17 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN', call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
'-m', 'addrtype', '--dst-type', 'LOCAL'), '-m', 'addrtype', '--dst-type', 'LOCAL'),
call(AF_INET6, 'mangle', '-A', 'sshuttle-d-1024', '-j', 'MARK', call(AF_INET6, 'mangle', '-A', 'sshuttle-d-1024', '-j', 'MARK',
'--set-mark', '1'), '--set-mark', '0x01'),
call(AF_INET6, 'mangle', '-A', 'sshuttle-d-1024', '-j', 'ACCEPT'), call(AF_INET6, 'mangle', '-A', 'sshuttle-d-1024', '-j', 'ACCEPT'),
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket', call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket',
'-j', 'sshuttle-d-1024', '-m', 'tcp', '-p', 'tcp'), '-j', 'sshuttle-d-1024', '-m', 'tcp', '-p', 'tcp'),
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket', call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket',
'-j', 'sshuttle-d-1024', '-m', 'udp', '-p', 'udp'), '-j', 'sshuttle-d-1024', '-m', 'udp', '-p', 'udp'),
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK', call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
'--set-mark', '1', '--dest', u'2404:6800:4004:80c::33/32', '--set-mark', '0x01', '--dest', u'2404:6800:4004:80c::33/32',
'-m', 'udp', '-p', 'udp', '--dport', '53'), '-m', 'udp', '-p', 'udp', '--dport', '53'),
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY', call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
'--tproxy-mark', '0x1/0x1', '--tproxy-mark', '0x01',
'--dest', u'2404:6800:4004:80c::33/32', '--dest', u'2404:6800:4004:80c::33/32',
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1026'), '-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1026'),
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN', call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
@ -165,22 +163,23 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
'--dest', u'2404:6800:4004:80c::101f/128', '--dest', u'2404:6800:4004:80c::101f/128',
'-m', 'udp', '-p', 'udp', '--dport', '8080:8080'), '-m', 'udp', '-p', 'udp', '--dport', '8080:8080'),
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK', call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
'--set-mark', '1', '--dest', u'2404:6800:4004:80c::/64', '--set-mark', '0x01', '--dest', u'2404:6800:4004:80c::/64',
'-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000'), '-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000'),
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY', call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
'--tproxy-mark', '0x1/0x1', '--dest', u'2404:6800:4004:80c::/64', '--tproxy-mark', '0x01', '--dest',
u'2404:6800:4004:80c::/64',
'-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000', '-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000',
'--on-port', '1024'), '--on-port', '1024'),
call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK', call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
'--set-mark', '1', '--dest', u'2404:6800:4004:80c::/64', '--set-mark', '0x01', '--dest', u'2404:6800:4004:80c::/64',
'-m', 'udp', '-p', 'udp', '--dport', '8000:9000'), '-m', 'udp', '-p', 'udp', '--dport', '8000:9000'),
call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY', call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
'--tproxy-mark', '0x1/0x1', '--dest', u'2404:6800:4004:80c::/64', '--tproxy-mark', '0x01', '--dest',
u'2404:6800:4004:80c::/64',
'-m', 'udp', '-p', 'udp', '--dport', '8000:9000', '-m', 'udp', '-p', 'udp', '--dport', '8000:9000',
'--on-port', '1024') '--on-port', '1024')
] ]
mock_ipt_chain_exists.reset_mock() mock_ipt_chain_exists.reset_mock()
mock_ipt_ttl.reset_mock()
mock_ipt.reset_mock() mock_ipt.reset_mock()
method.restore_firewall(1025, AF_INET6, True, None) method.restore_firewall(1025, AF_INET6, True, None)
@ -189,7 +188,6 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
call(AF_INET6, 'mangle', 'sshuttle-t-1025'), call(AF_INET6, 'mangle', 'sshuttle-t-1025'),
call(AF_INET6, 'mangle', 'sshuttle-d-1025') call(AF_INET6, 'mangle', 'sshuttle-d-1025')
] ]
assert mock_ipt_ttl.mock_calls == []
assert mock_ipt.mock_calls == [ assert mock_ipt.mock_calls == [
call(AF_INET6, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'), call(AF_INET6, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
call(AF_INET6, 'mangle', '-F', 'sshuttle-m-1025'), call(AF_INET6, 'mangle', '-F', 'sshuttle-m-1025'),
@ -201,7 +199,6 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
call(AF_INET6, 'mangle', '-X', 'sshuttle-d-1025') call(AF_INET6, 'mangle', '-X', 'sshuttle-d-1025')
] ]
mock_ipt_chain_exists.reset_mock() mock_ipt_chain_exists.reset_mock()
mock_ipt_ttl.reset_mock()
mock_ipt.reset_mock() mock_ipt.reset_mock()
# IPV4 # IPV4
@ -214,13 +211,12 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
(AF_INET, 32, True, u'1.2.3.66', 80, 80)], (AF_INET, 32, True, u'1.2.3.66', 80, 80)],
True, True,
None, None,
63) '0x01')
assert mock_ipt_chain_exists.mock_calls == [ assert mock_ipt_chain_exists.mock_calls == [
call(AF_INET, 'mangle', 'sshuttle-m-1025'), call(AF_INET, 'mangle', 'sshuttle-m-1025'),
call(AF_INET, 'mangle', 'sshuttle-t-1025'), call(AF_INET, 'mangle', 'sshuttle-t-1025'),
call(AF_INET, 'mangle', 'sshuttle-d-1025') call(AF_INET, 'mangle', 'sshuttle-d-1025')
] ]
assert mock_ipt_ttl.mock_calls == []
assert mock_ipt.mock_calls == [ assert mock_ipt.mock_calls == [
call(AF_INET, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'), call(AF_INET, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
call(AF_INET, 'mangle', '-F', 'sshuttle-m-1025'), call(AF_INET, 'mangle', '-F', 'sshuttle-m-1025'),
@ -244,17 +240,17 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN', call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
'-m', 'addrtype', '--dst-type', 'LOCAL'), '-m', 'addrtype', '--dst-type', 'LOCAL'),
call(AF_INET, 'mangle', '-A', 'sshuttle-d-1025', call(AF_INET, 'mangle', '-A', 'sshuttle-d-1025',
'-j', 'MARK', '--set-mark', '1'), '-j', 'MARK', '--set-mark', '0x01'),
call(AF_INET, 'mangle', '-A', 'sshuttle-d-1025', '-j', 'ACCEPT'), call(AF_INET, 'mangle', '-A', 'sshuttle-d-1025', '-j', 'ACCEPT'),
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket', call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket',
'-j', 'sshuttle-d-1025', '-m', 'tcp', '-p', 'tcp'), '-j', 'sshuttle-d-1025', '-m', 'tcp', '-p', 'tcp'),
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket', call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket',
'-j', 'sshuttle-d-1025', '-m', 'udp', '-p', 'udp'), '-j', 'sshuttle-d-1025', '-m', 'udp', '-p', 'udp'),
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK', call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
'--set-mark', '1', '--dest', u'1.2.3.33/32', '--set-mark', '0x01', '--dest', u'1.2.3.33/32',
'-m', 'udp', '-p', 'udp', '--dport', '53'), '-m', 'udp', '-p', 'udp', '--dport', '53'),
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY', call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
'--tproxy-mark', '0x1/0x1', '--dest', u'1.2.3.33/32', '--tproxy-mark', '0x01', '--dest', u'1.2.3.33/32',
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1027'), '-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1027'),
call(AF_INET, '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', '--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp',
@ -269,20 +265,19 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
'--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp', '--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp',
'--dport', '80:80'), '--dport', '80:80'),
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK', call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
'--set-mark', '1', '--dest', u'1.2.3.0/24', '--set-mark', '0x01', '--dest', u'1.2.3.0/24',
'-m', 'tcp', '-p', 'tcp'), '-m', 'tcp', '-p', 'tcp'),
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY', call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
'--tproxy-mark', '0x1/0x1', '--dest', u'1.2.3.0/24', '--tproxy-mark', '0x01', '--dest', u'1.2.3.0/24',
'-m', 'tcp', '-p', 'tcp', '--on-port', '1025'), '-m', 'tcp', '-p', 'tcp', '--on-port', '1025'),
call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK', call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
'--set-mark', '1', '--dest', u'1.2.3.0/24', '--set-mark', '0x01', '--dest', u'1.2.3.0/24',
'-m', 'udp', '-p', 'udp'), '-m', 'udp', '-p', 'udp'),
call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY', call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'TPROXY',
'--tproxy-mark', '0x1/0x1', '--dest', u'1.2.3.0/24', '--tproxy-mark', '0x01', '--dest', u'1.2.3.0/24',
'-m', 'udp', '-p', 'udp', '--on-port', '1025') '-m', 'udp', '-p', 'udp', '--on-port', '1025')
] ]
mock_ipt_chain_exists.reset_mock() mock_ipt_chain_exists.reset_mock()
mock_ipt_ttl.reset_mock()
mock_ipt.reset_mock() mock_ipt.reset_mock()
method.restore_firewall(1025, AF_INET, True, None) method.restore_firewall(1025, AF_INET, True, None)
@ -291,7 +286,6 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
call(AF_INET, 'mangle', 'sshuttle-t-1025'), call(AF_INET, 'mangle', 'sshuttle-t-1025'),
call(AF_INET, 'mangle', 'sshuttle-d-1025') call(AF_INET, 'mangle', 'sshuttle-d-1025')
] ]
assert mock_ipt_ttl.mock_calls == []
assert mock_ipt.mock_calls == [ assert mock_ipt.mock_calls == [
call(AF_INET, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'), call(AF_INET, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
call(AF_INET, 'mangle', '-F', 'sshuttle-m-1025'), call(AF_INET, 'mangle', '-F', 'sshuttle-m-1025'),
@ -303,5 +297,4 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
call(AF_INET, 'mangle', '-X', 'sshuttle-d-1025') call(AF_INET, 'mangle', '-X', 'sshuttle-d-1025')
] ]
mock_ipt_chain_exists.reset_mock() mock_ipt_chain_exists.reset_mock()
mock_ipt_ttl.reset_mock()
mock_ipt.reset_mock() mock_ipt.reset_mock()