diff -pruN 1.0.5-1/bin/sudoers-add 1.1.0-1/bin/sudoers-add
--- 1.0.5-1/bin/sudoers-add	2019-12-12 21:16:46.000000000 +0000
+++ 1.1.0-1/bin/sudoers-add	2022-01-27 22:07:24.000000000 +0000
@@ -50,6 +50,14 @@ if [ "$FILE_NAME" == "" ]; then
 	exit 1
 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
 umask 077
 TEMP_FILE=$(mktemp)
@@ -62,9 +70,9 @@ visudo_code=$?
 rm "$TEMP_FILE"
 
 if [ $visudo_code -eq 0 ]; then
-	echo "$CONTENT" > "/etc/sudoers.d/$FILE_NAME"
-	chmod 0440 "/etc/sudoers.d/$FILE_NAME"
-	echo "The sudoers file /etc/sudoers.d/$FILE_NAME has been successfully created!"
+	echo "$CONTENT" > "$FILE_NAME"
+	chmod 0440 "$FILE_NAME"
+	echo "The sudoers file $FILE_NAME has been successfully created!"
 
 	exit 0
 else
diff -pruN 1.0.5-1/CHANGES.rst 1.1.0-1/CHANGES.rst
--- 1.0.5-1/CHANGES.rst	2020-12-28 23:34:47.000000000 +0000
+++ 1.1.0-1/CHANGES.rst	2022-01-27 22:27:41.000000000 +0000
@@ -1,12 +1,9 @@
 ==========
 Change log
 ==========
-All notable changes to this project will be documented in this file. The format
-is based on `Keep a Changelog`_ and this project
-adheres to `Semantic Versioning`_.
+Release notes now moved to https://github.com/sshuttle/sshuttle/releases/
 
-.. _`Keep a Changelog`: http://keepachangelog.com/
-.. _`Semantic Versioning`: http://semver.org/
+These are the old release notes.
 
 
 1.0.5 - 2020-12-29
@@ -133,7 +130,7 @@ Fixed
 
 Added
 ~~~~~
-* doas support as replacmeent for sudo on OpenBSD.
+* doas support as replacement for sudo on OpenBSD.
 * Added ChromeOS section to documentation (#262)
 * Add --no-sudo-pythonpath option
 
diff -pruN 1.0.5-1/debian/changelog 1.1.0-1/debian/changelog
--- 1.0.5-1/debian/changelog	2020-12-29 00:00:34.000000000 +0000
+++ 1.1.0-1/debian/changelog	2022-01-27 22:57:26.000000000 +0000
@@ -1,3 +1,10 @@
+sshuttle (1.1.0-1) unstable; urgency=medium
+
+  * New upstream version.
+  * Should work with sudo use_pty option. Closes: #1003154.
+
+ -- Brian May <bam@debian.org>  Fri, 28 Jan 2022 09:57:26 +1100
+
 sshuttle (1.0.5-1) unstable; urgency=medium
 
   * New upstream version.
diff -pruN 1.0.5-1/debian/watch 1.1.0-1/debian/watch
--- 1.0.5-1/debian/watch	2020-06-05 07:56:00.000000000 +0000
+++ 1.1.0-1/debian/watch	2022-01-27 22:40:31.000000000 +0000
@@ -1,3 +1,3 @@
-version=3
-opts=uversionmangle=s/(rc|a|b|c)/~$1/,pgpsigurlmangle=s/$/.asc/ \
-https://pypi.debian.net/sshuttle/sshuttle-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz)))
+version=4
+opts=filenamemangle=s/.+\/v?(\d\S+)\.tar\.gz/sshuttle-$1\.tar\.gz/ \
+  https://github.com/sshuttle/sshuttle/tags .*/v?(\d\S+)\.tar\.gz
diff -pruN 1.0.5-1/docs/chromeos.rst 1.1.0-1/docs/chromeos.rst
--- 1.0.5-1/docs/chromeos.rst	2018-10-07 00:30:41.000000000 +0000
+++ 1.1.0-1/docs/chromeos.rst	2022-01-27 22:07:24.000000000 +0000
@@ -9,4 +9,3 @@ stretch/Debian 9 VM, you can then instal
 it just works, as do xterms and ssvncviewer etc.
 
 https://www.reddit.com/r/Crostini/wiki/getstarted/crostini-setup-guide
-
diff -pruN 1.0.5-1/docs/how-it-works.rst 1.1.0-1/docs/how-it-works.rst
--- 1.0.5-1/docs/how-it-works.rst	2017-02-20 06:13:02.000000000 +0000
+++ 1.1.0-1/docs/how-it-works.rst	2022-01-27 22:07:24.000000000 +0000
@@ -34,4 +34,3 @@ sshuttle assembles the TCP stream locall
 an ssh session, and disassembles it back into packets at the other end.  So
 it never ends up doing TCP-over-TCP.  It's just data-over-TCP, which is
 safe.
-
diff -pruN 1.0.5-1/docs/index.rst 1.1.0-1/docs/index.rst
--- 1.0.5-1/docs/index.rst	2017-02-20 06:13:02.000000000 +0000
+++ 1.1.0-1/docs/index.rst	2022-01-27 22:07:24.000000000 +0000
@@ -26,4 +26,3 @@ Indices and tables
 
 * :ref:`genindex`
 * :ref:`search`
-
diff -pruN 1.0.5-1/docs/installation.rst 1.1.0-1/docs/installation.rst
--- 1.0.5-1/docs/installation.rst	2020-08-12 22:01:33.000000000 +0000
+++ 1.1.0-1/docs/installation.rst	2022-01-27 22:07:24.000000000 +0000
@@ -5,7 +5,7 @@ Installation
 
       pip install sshuttle
 
-- Debain package manager::
+- Debian package manager::
 
       sudo apt install sshuttle
 
diff -pruN 1.0.5-1/docs/manpage.rst 1.1.0-1/docs/manpage.rst
--- 1.0.5-1/docs/manpage.rst	2020-12-27 23:46:37.000000000 +0000
+++ 1.1.0-1/docs/manpage.rst	2022-01-27 22:07:24.000000000 +0000
@@ -4,14 +4,14 @@ sshuttle
 
 Synopsis
 --------
-**sshuttle** [*options*] [**-r** *[username@]sshserver[:port]*] \<*subnets* ...\>
+**sshuttle** [*options*] **-r** *[username@]sshserver[:port]* \<*subnets* ...\>
 
 
 Description
 -----------
 :program:`sshuttle` allows you to create a VPN connection from your
-machine to any remote server that you can connect to via
-ssh, as long as that server has python 3.6 or higher.
+machine to any remote server that you can connect to via ssh, as long
+as that server has a sufficiently new Python installation.
 
 To work, you must have root access on the local machine,
 but you can have a normal account on the server.
@@ -31,22 +31,23 @@ Options
 .. option:: <subnets>
 
     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
-    single IP address), 1.2.3.4/32 (equivalent to 1.2.3.4),
-    1.2.3.0/24 (a 24-bit subnet, ie. with a 255.255.255.0
-    netmask), and 0/0 ('just route everything through the
-    VPN'). Any of the previous examples are also valid if you append
-    a port or a port range, so 1.2.3.4:8000 will only tunnel traffic
-    that has as the destination port 8000 of 1.2.3.4 and
-    1.2.3.0/24:8000-9000 will tunnel traffic going to any port between
-    8000 and 9000 (inclusive) for all IPs in the 1.2.3.0/24 subnet.
-    A hostname can be provided instead of an IP address. If the
-    hostname resolves to multiple IPs, all of the IPs are included.
-    If a width is provided with a hostname that the width is applied
-    to all of the hostnames IPs (if they are all either IPv4 or IPv6).
-    Widths cannot be supplied to hostnames that resolve to both IPv4
-    and IPv6. Valid examples are example.com, example.com:8000,
-    example.com/24, example.com/24:8000 and example.com:8000-9000.
+    ``a.b.c.d[/width][port[-port]]``. Valid examples are 1.2.3.4 (a
+    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 netmask).
+    Specify subnets 0/0 to match all IPv4 addresses and ::/0 to match
+    all IPv6 addresses. Any of the previous examples are also valid if
+    you append a port or a port range, so 1.2.3.4:8000 will only
+    tunnel traffic that has as the destination port 8000 of 1.2.3.4
+    and 1.2.3.0/24:8000-9000 will tunnel traffic going to any port
+    between 8000 and 9000 (inclusive) for all IPs in the 1.2.3.0/24
+    subnet. A hostname can be provided instead of an IP address. If
+    the hostname resolves to multiple IPs, all of the IPs are
+    included. If a width is provided with a hostname, the width is
+    applied to all of the hostnames IPs (if they are all either IPv4
+    or IPv6). Widths cannot be supplied to hostnames that resolve to
+    both IPv4 and IPv6. Valid examples are example.com,
+    example.com:8000, example.com/24, example.com/24:8000 and
+    example.com:8000-9000.
 
 .. option:: --method <auto|nat|nft|tproxy|pf|ipfw>
 
@@ -68,8 +69,8 @@ Options
     You can use any name resolving to an IP address of the machine running
     :program:`sshuttle`, e.g. ``--listen localhost``.
 
-    For the nft, tproxy and pf methods this can be an IPv6 address. Use 
-    this option with comma separated values if required, to provide both 
+    For the nft, tproxy and pf methods this can be an IPv6 address. Use
+    this option with comma separated values if required, to provide both
     IPv4 and IPv6 addresses, e.g. ``--listen 127.0.0.1:0,[::1]:0``.
 
 .. option:: -H, --auto-hosts
@@ -88,6 +89,13 @@ Options
     few subnets over the VPN, you probably would prefer to
     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
 
     In addition to the subnets provided on the command
@@ -104,7 +112,7 @@ Options
 
     Capture local DNS requests and forward to the remote DNS
     server. All queries to any of the local system's DNS
-    servers (/etc/resolv.conf and, if it exists, 
+    servers (/etc/resolv.conf and, if it exists,
     /run/systemd/resolve/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,
@@ -141,7 +149,10 @@ Options
     The remote hostname and optional username and ssh
     port number to use for connecting to the remote server.
     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>
 
@@ -174,7 +185,7 @@ Options
 
     A comma-separated list of hostnames to use to
     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
     if you use this option to give it a few names to start
     from.
@@ -274,9 +285,10 @@ Options
     Set the file name for the sudoers.d file to be added. Default is
     "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
 
@@ -305,54 +317,107 @@ Arguments read from a file must be one p
     --option2
     value2
 
+The configuration file supports comments for human-readable
+annotations. For example::
+
+    # company-internal API
+    8.8.8.8/32
+    # home IoT
+    192.168.63.0/24
+
 
 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.
-    Listening on ('0.0.0.0', 12300).
+    $ sshuttle -r example.com -x example.com:22 0/0
+
+    Starting sshuttle proxy (version ...).
     [local sudo] Password:
-    firewall manager ready.
-    c : connecting to server...
-     s: available routes:
-     s:   192.168.42.0/24
-    c : connected.
-    firewall manager: starting transproxy.
-    c : Accept: 192.168.42.106:50035 -> 192.168.42.121:139.
-    c : Accept: 192.168.42.121:47523 -> 77.141.99.22:443.
-        ...etc...
+    fw: Starting firewall with Python version 3.9.5
+    fw: ready method name nat.
+    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 :   (<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
-    firewall manager: undoing changes.
-    KeyboardInterrupt
     c : Keyboard interrupt: exiting.
-    c : SW#8:192.168.42.121:47523: deleting
-    c : SW#6:192.168.42.106:50035: deleting
+    c : SW'unknown':Mux#1: deleting (1 remain)
+    c : SW#7:192.168.42.121:60554: deleting (0 remain)
 
-Test connection to a remote server, with automatic hostname
-and subnet guessing::
 
-    $ sshuttle -vNHr example.org
+Connect to a remote server, with automatic hostname
+and subnet guessing::
 
-    Starting sshuttle proxy.
-    Listening on ('0.0.0.0', 12300).
-    firewall manager ready.
-    c : connecting to server...
+    $ sshuttle -vNHr example.com -x example.com:22
+    Starting sshuttle proxy (version ...).
+    [local sudo] Password:
+    fw: Starting firewall with Python version 3.9.5
+    fw: ready method name nat.
+    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:   77.141.99.0/24
-    c : connected.
-    c : seed_hosts: []
-    firewall manager: starting transproxy.
-    hostwatch: Found: testbox1: 1.2.3.4
-    hostwatch: Found: mytest2: 5.6.7.8
-    hostwatch: Found: domaincontroller: 99.1.2.3
+    fw: setting up.
+    fw: iptables -w -t nat -N sshuttle-12300
+    fw: iptables -w -t nat -F sshuttle-12300
+    ...
     c : Accept: 192.168.42.121:60554 -> 77.141.99.22:22.
     ^C
-    firewall manager: undoing changes.
     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::
 
@@ -376,9 +441,7 @@ Example configuration file::
 Discussion
 ----------
 When it starts, :program:`sshuttle` creates an ssh session to the
-server specified by the ``-r`` option.  If ``-r`` is omitted,
-it will start both its client and server locally, which is
-sometimes useful for testing.
+server specified by the ``-r`` option.
 
 After connecting to the remote server, :program:`sshuttle` uploads its
 (python) source code to the remote end and executes it
diff -pruN 1.0.5-1/docs/requirements.rst 1.1.0-1/docs/requirements.rst
--- 1.0.5-1/docs/requirements.rst	2020-12-16 09:09:55.000000000 +0000
+++ 1.1.0-1/docs/requirements.rst	2022-01-27 22:07:24.000000000 +0000
@@ -15,10 +15,12 @@ Supports:
 
 * IPv4 TCP
 * IPv4 DNS
+* IPv6 TCP
+* IPv6 DNS
 
 Requires:
 
-* iptables DNAT, REDIRECT, and ttl modules.
+* iptables DNAT and REDIRECT modules. ip6tables for IPv6.
 
 Linux with nft method
 ~~~~~~~~~~~~~~~~~~~~~
@@ -38,11 +40,11 @@ Linux with TPROXY method
 Supports:
 
 * IPv4 TCP
-* IPv4 UDP (requires ``recvmsg`` - see below)
-* IPv6 DNS (requires ``recvmsg`` - see below)
+* IPv4 UDP
+* IPv4 DNS
 * IPv6 TCP
-* IPv6 UDP (requires ``recvmsg`` - see below)
-* IPv6 DNS (requires ``recvmsg`` - see below)
+* IPv6 UDP
+* IPv6 DNS
 
 
 MacOS / FreeBSD / OpenBSD / pfSense
@@ -79,7 +81,7 @@ Additional Suggested Software
 - If you are using systemd, sshuttle can notify it when the connection to
   the remote end is established and the firewall rules are installed. For
   this feature to work you must configure the process start-up type for the
-  sshuttle service unit to notify, as shown in the example below. 
+  sshuttle service unit to notify, as shown in the example below.
 
 .. code-block:: ini
    :emphasize-lines: 6
@@ -87,10 +89,10 @@ Additional Suggested Software
    [Unit]
    Description=sshuttle
    After=network.target
-   
+
    [Service]
    Type=notify
    ExecStart=/usr/bin/sshuttle --dns --remote <user>@<server> <subnets...>
-   
+
    [Install]
    WantedBy=multi-user.target
diff -pruN 1.0.5-1/docs/tproxy.rst 1.1.0-1/docs/tproxy.rst
--- 1.0.5-1/docs/tproxy.rst	2020-12-27 23:46:37.000000000 +0000
+++ 1.1.0-1/docs/tproxy.rst	2022-01-27 22:07:24.000000000 +0000
@@ -1,6 +1,6 @@
 TPROXY
 ======
-TPROXY is the only method that has full support of IPv6 and UDP.
+TPROXY is the only method that supports UDP.
 
 There are some things you need to consider for TPROXY to work:
 
@@ -11,25 +11,21 @@ There are some things you need to consid
       ip rule add fwmark {TMARK} lookup 100
       ip -6 route add local default dev lo table 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
   routes manually. e.g. by adding ``'::/0'`` to the end of the command line.
 
 - The client needs to be run as root. e.g.::
 
-      sudo SSH_AUTH_SOCK="$SSH_AUTH_SOCK" $HOME/tree/sshuttle.tproxy/sshuttle  --method=tproxy ...
+      sudo SSH_AUTH_SOCK="$SSH_AUTH_SOCK" $HOME/tree/sshuttle.tproxy/sshuttle --method=tproxy ...
 
 - You may need to exclude the IP address of the server you are connecting to.
   Otherwise sshuttle may attempt to intercept the ssh packets, which will not
   work. Use the ``--exclude`` parameter for this.
 
-- Similarly, UDP return packets (including DNS) could get intercepted and
-  bounced back. This is the case if you have a broad subnet such as
-  ``0.0.0.0/0`` or ``::/0`` that includes the IP address of the client. Use the
-  ``--exclude`` parameter for this.
-
 - You need the ``--method=tproxy`` parameter, as above.
 
 - The routes for the outgoing packets must already exist. For example, if your
diff -pruN 1.0.5-1/docs/trivia.rst 1.1.0-1/docs/trivia.rst
--- 1.0.5-1/docs/trivia.rst	2017-02-20 06:13:02.000000000 +0000
+++ 1.1.0-1/docs/trivia.rst	2022-01-27 22:07:24.000000000 +0000
@@ -33,4 +33,3 @@ That project I did for Slipstream was wh
 the concepts of Fast Forward, Double Vision, and Tunnel Vision into a single
 program that was the best of all worlds.  And here we are, at last.
 You're welcome.
-
diff -pruN 1.0.5-1/docs/usage.rst 1.1.0-1/docs/usage.rst
--- 1.0.5-1/docs/usage.rst	2020-07-12 09:06:37.000000000 +0000
+++ 1.1.0-1/docs/usage.rst	2022-01-27 22:07:24.000000000 +0000
@@ -11,6 +11,10 @@ Forward all traffic::
     sshuttle -r username@sshserver 0.0.0.0/0
 
 - 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
   the :option:`sshuttle --method` parameter.
@@ -47,7 +51,7 @@ were right there.  And if your "client"
 your local network can make connections to your remote network.
 
 You don't need to install sshuttle on the remote server;
-the remote server just needs to have python available. 
+the remote server just needs to have python available.
 sshuttle will automatically upload and run its source code
 to the remote python interpreter.
 
@@ -67,7 +71,7 @@ admin access on the server.
 
 Sudoers File
 ------------
-sshuttle can auto-generate the proper sudoers.d file using the current user 
+sshuttle can auto-generate the proper sudoers.d file using the current user
 for Linux and OSX. Doing this will allow sshuttle to run without asking for
 the local sudo password and to give users who do not have sudo access
 ability to run sshuttle::
@@ -79,7 +83,7 @@ it is needed.
 
 A costume user or group can be set with the :
 option:`sshuttle --sudoers --sudoers-username {user_descriptor}` option. Valid
-values for this vary based on how your system is configured. Values such as 
+values for this vary based on how your system is configured. Values such as
 usernames, groups pre-pended with `%` and sudoers user aliases will work. See
 the sudoers manual for more information on valid user specif actions.
 The options must be used with `--sudoers`::
@@ -95,7 +99,7 @@ access to sshuttle. The default is `sshu
   sshuttle --sudoer --sudoers-filename sshuttle_auto_tommy
 
 You can also see what configuration will be added to your system without
-modifying anything. This can be helpfull is the auto feature does not work, or
+modifying anything. This can be helpful if the auto feature does not work, or
 you want more control. This option also works with `--sudoers-username`.
 `--sudoers-filename` has no effect with this option::
 
diff -pruN 1.0.5-1/docs/windows.rst 1.1.0-1/docs/windows.rst
--- 1.0.5-1/docs/windows.rst	2017-02-20 06:13:02.000000000 +0000
+++ 1.1.0-1/docs/windows.rst	2022-01-27 22:07:24.000000000 +0000
@@ -16,4 +16,4 @@ Assuming the VM has the IP 192.168.1.200
 configure that in Vagrant), we can then ask Windows to route all its traffic
 via the VM by running the following in cmd.exe with admin right::
 
-     route add 0.0.0.0 mask 0.0.0.0 192.168.1.200
+    route add 0.0.0.0 mask 0.0.0.0 192.168.1.200
diff -pruN 1.0.5-1/.github/dependabot.yml 1.1.0-1/.github/dependabot.yml
--- 1.0.5-1/.github/dependabot.yml	1970-01-01 00:00:00.000000000 +0000
+++ 1.1.0-1/.github/dependabot.yml	2022-01-27 22:07:24.000000000 +0000
@@ -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
diff -pruN 1.0.5-1/.github/workflows/pythonpackage.yml 1.1.0-1/.github/workflows/pythonpackage.yml
--- 1.0.5-1/.github/workflows/pythonpackage.yml	2020-12-28 00:00:20.000000000 +0000
+++ 1.1.0-1/.github/workflows/pythonpackage.yml	2022-01-27 22:07:24.000000000 +0000
@@ -5,11 +5,11 @@ name: Python package
 
 on:
   push:
-    branches: [ master, tproxy_mark_param ]
+    branches: [ master ]
   pull_request:
-    branches: [ master, tproxy_mark_param ]
+    branches: [ master ]
   workflow_dispatch:
-    branches: [ tproxy_mark_param ]
+    branches: [ master ]
 
 jobs:
   build:
@@ -17,12 +17,12 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: [3.6, 3.7, 3.8, 3.9]
+        python-version: [3.6, 3.7, 3.8, 3.9, "3.10"]
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v2.4.0
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v2.3.1
       with:
         python-version: ${{ matrix.python-version }}
     - name: Install dependencies
diff -pruN 1.0.5-1/PKG-INFO 1.1.0-1/PKG-INFO
--- 1.0.5-1/PKG-INFO	2020-12-28 23:39:03.986590600 +0000
+++ 1.1.0-1/PKG-INFO	2022-01-27 22:37:51.935803700 +0000
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: sshuttle
-Version: 1.0.5
+Version: 1.1.0
 Summary: Full-featured" VPN over an SSH tunnel
 Home-page: https://github.com/sshuttle/sshuttle
 Author: Brian May
@@ -32,7 +32,7 @@ Description: sshuttle: where transparent
         - You can't use openssh's PermitTunnel feature because
           it's disabled by default on openssh servers; plus it does
           TCP-over-TCP, which has `terrible performance`_.
-          
+        
         .. _terrible performance: https://sshuttle.readthedocs.io/en/stable/how-it-works.html
         
         Obtaining sshuttle
@@ -45,7 +45,7 @@ Description: sshuttle: where transparent
         - Debian stretch or later::
         
               apt-get install sshuttle
-              
+        
         - Arch Linux::
         
               pacman -S sshuttle
@@ -54,6 +54,14 @@ Description: sshuttle: where transparent
         
               dnf install sshuttle
         
+        - openSUSE::
+        
+              zypper in sshuttle
+        
+        - Gentoo::
+        
+              emerge -av net-proxy/sshuttle
+        
         - NixOS::
         
               nix-env -iA nixos.sshuttle
@@ -75,6 +83,11 @@ Description: sshuttle: where transparent
               # pkg
               pkg install py36-sshuttle
         
+        - macOS, via MacPorts::
+        
+              sudo port selfupdate
+              sudo port install sshuttle
+        
         It is also possible to install into a virtualenv as a non-root user.
         
         - From PyPI::
@@ -111,7 +124,7 @@ Description: sshuttle: where transparent
         
         Running as a service
         --------------------
-        Sshuttle can also be run as a service and configured using a config management system: 
+        Sshuttle can also be run as a service and configured using a config management system:
         https://medium.com/@mike.reider/using-sshuttle-as-a-service-bec2684a65fe
         
 Keywords: ssh vpn
diff -pruN 1.0.5-1/README.rst 1.1.0-1/README.rst
--- 1.0.5-1/README.rst	2020-08-12 22:01:33.000000000 +0000
+++ 1.1.0-1/README.rst	2022-01-27 22:07:24.000000000 +0000
@@ -24,7 +24,7 @@ common case:
 - You can't use openssh's PermitTunnel feature because
   it's disabled by default on openssh servers; plus it does
   TCP-over-TCP, which has `terrible performance`_.
-  
+
 .. _terrible performance: https://sshuttle.readthedocs.io/en/stable/how-it-works.html
 
 Obtaining sshuttle
@@ -37,7 +37,7 @@ Obtaining sshuttle
 - Debian stretch or later::
 
       apt-get install sshuttle
-      
+
 - Arch Linux::
 
       pacman -S sshuttle
@@ -46,6 +46,14 @@ Obtaining sshuttle
 
       dnf install sshuttle
 
+- openSUSE::
+
+      zypper in sshuttle
+
+- Gentoo::
+
+      emerge -av net-proxy/sshuttle
+
 - NixOS::
 
       nix-env -iA nixos.sshuttle
@@ -67,6 +75,11 @@ Obtaining sshuttle
       # pkg
       pkg install py36-sshuttle
 
+- macOS, via MacPorts::
+
+      sudo port selfupdate
+      sudo port install sshuttle
+
 It is also possible to install into a virtualenv as a non-root user.
 
 - From PyPI::
@@ -103,5 +116,5 @@ https://sshuttle.readthedocs.org/en/late
 
 Running as a service
 --------------------
-Sshuttle can also be run as a service and configured using a config management system: 
+Sshuttle can also be run as a service and configured using a config management system:
 https://medium.com/@mike.reider/using-sshuttle-as-a-service-bec2684a65fe
diff -pruN 1.0.5-1/.readthedocs.yaml 1.1.0-1/.readthedocs.yaml
--- 1.0.5-1/.readthedocs.yaml	1970-01-01 00:00:00.000000000 +0000
+++ 1.1.0-1/.readthedocs.yaml	2022-01-27 22:07:24.000000000 +0000
@@ -0,0 +1,15 @@
+version: 2
+
+build:
+  os: ubuntu-20.04
+  tools:
+    python: "3.9"
+
+sphinx:
+   configuration: docs/conf.py
+
+python:
+   install:
+   - requirements: requirements.txt
+   - method: setuptools
+     path: .
diff -pruN 1.0.5-1/requirements-tests.txt 1.1.0-1/requirements-tests.txt
--- 1.0.5-1/requirements-tests.txt	2020-12-16 09:16:00.000000000 +0000
+++ 1.1.0-1/requirements-tests.txt	2022-01-27 22:07:24.000000000 +0000
@@ -1,7 +1,5 @@
 -r requirements.txt
-attrs==20.3.0
-pytest==6.2.1
-pytest-cov==2.10.1
-mock==4.0.3
-flake8==3.8.4
-pyflakes==2.2.0
+pytest==6.2.5
+pytest-cov==3.0.0
+flake8==4.0.1
+pyflakes==2.4.0
diff -pruN 1.0.5-1/requirements.txt 1.1.0-1/requirements.txt
--- 1.0.5-1/requirements.txt	2020-12-16 09:08:57.000000000 +0000
+++ 1.1.0-1/requirements.txt	2022-01-27 22:07:24.000000000 +0000
@@ -1,2 +1,2 @@
-setuptools-scm==5.0.1
-psutil
+setuptools-scm==6.4.2
+Sphinx==4.3.2
diff -pruN 1.0.5-1/run 1.1.0-1/run
--- 1.0.5-1/run	2020-05-28 21:45:10.000000000 +0000
+++ 1.1.0-1/run	2022-01-27 22:07:24.000000000 +0000
@@ -1,7 +1,7 @@
 #!/usr/bin/env sh
 set -e
-export PYTHONPATH="$(dirname $0):$PYTHONPATH"
-export PATH="$(dirname $0)/bin:$PATH"
+export PYTHONPATH="$(dirname "$0"):$PYTHONPATH"
+export PATH="$(dirname "$0")/bin:$PATH"
 
 python_best_version() {
   if [ -x "$(command -v python3)" ] &&
diff -pruN 1.0.5-1/setup.py 1.1.0-1/setup.py
--- 1.0.5-1/setup.py	2020-12-27 23:59:40.000000000 +0000
+++ 1.1.0-1/setup.py	2022-01-27 22:07:24.000000000 +0000
@@ -63,13 +63,11 @@ setup(
     },
     python_requires='>=3.6',
     install_requires=[
-        'psutil',
     ],
     tests_require=[
         'pytest',
         'pytest-cov',
         'pytest-runner',
-        'mock',
         'flake8',
     ],
     keywords="ssh vpn",
diff -pruN 1.0.5-1/sshuttle/assembler.py 1.1.0-1/sshuttle/assembler.py
--- 1.0.5-1/sshuttle/assembler.py	2020-12-16 09:08:57.000000000 +0000
+++ 1.1.0-1/sshuttle/assembler.py	2022-01-27 22:07:24.000000000 +0000
@@ -1,8 +1,12 @@
 import sys
 import zlib
 import types
+import platform
 
 verbosity = verbosity  # noqa: F821 must be a previously defined global
+if verbosity > 0:
+    sys.stderr.write(' s: Running server on remote host with %s (version %s)\n'
+                     % (sys.executable, platform.python_version()))
 z = zlib.decompressobj()
 while 1:
     name = sys.stdin.readline().strip()
@@ -14,7 +18,7 @@ while 1:
             name = name.decode("ASCII")
         nbytes = int(sys.stdin.readline())
         if verbosity >= 2:
-            sys.stderr.write(' s: assembling %r (%d bytes)\n'
+            sys.stderr.write(' s: assembling %r (%d bytes)\r\n'
                              % (name, nbytes))
         content = z.decompress(sys.stdin.read(nbytes))
 
@@ -40,5 +44,6 @@ sshuttle.helpers.verbose = verbosity
 
 import sshuttle.cmdline_options as options  # noqa: E402
 from sshuttle.server import main  # noqa: E402
-main(options.latency_control, options.auto_hosts, options.to_nameserver,
+main(options.latency_control, options.latency_buffer_size,
+     options.auto_hosts, options.to_nameserver,
      options.auto_nets)
diff -pruN 1.0.5-1/sshuttle/client.py 1.1.0-1/sshuttle/client.py
--- 1.0.5-1/sshuttle/client.py	2020-12-27 23:46:37.000000000 +0000
+++ 1.1.0-1/sshuttle/client.py	2022-01-27 22:07:24.000000000 +0000
@@ -6,7 +6,6 @@ import subprocess as ssubprocess
 import os
 import sys
 import platform
-import psutil
 
 import sshuttle.helpers as helpers
 import sshuttle.ssnet as ssnet
@@ -23,28 +22,17 @@ try:
 except ImportError:
     getpwnam = None
 
-try:
-    # try getting recvmsg from python
-    import socket as pythonsocket
-    getattr(pythonsocket.socket, "recvmsg")
-    socket = pythonsocket
-except AttributeError:
-    # try getting recvmsg from socket_ext library
-    try:
-        import socket_ext
-        getattr(socket_ext.socket, "recvmsg")
-        socket = socket_ext
-    except ImportError:
-        import socket
+import socket
 
 _extra_fd = os.open(os.devnull, os.O_RDONLY)
 
 
 def got_signal(signum, frame):
-    log('exiting on signal %d\n' % signum)
+    log('exiting on signal %d' % signum)
     sys.exit(1)
 
 
+# Filename of the pidfile created by the sshuttle client.
 _pidname = None
 
 
@@ -57,7 +45,7 @@ def check_daemon(pidfile):
         if e.errno == errno.ENOENT:
             return  # no pidfile, ok
         else:
-            raise Fatal("c : can't read %s: %s" % (_pidname, e))
+            raise Fatal("can't read %s: %s" % (_pidname, e))
     if not oldpid:
         os.unlink(_pidname)
         return  # invalid pidfile, ok
@@ -80,13 +68,25 @@ def check_daemon(pidfile):
 
 
 def daemonize():
+    # Try to open the pidfile prior to forking. If there is a problem,
+    # the client can then exit with a proper exit status code and
+    # message.
+    try:
+        outfd = os.open(_pidname, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o666)
+    except PermissionError:
+        # User will have to look in syslog for error message since
+        # --daemon implies --syslog, all output gets redirected to
+        # syslog.
+        raise Fatal("failed to create/write pidfile %s" % _pidname)
+
+    # Create a daemon process with a new session id.
     if os.fork():
         os._exit(0)
     os.setsid()
     if os.fork():
         os._exit(0)
 
-    outfd = os.open(_pidname, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o666)
+    # Write pid to the pidfile.
     try:
         os.write(outfd, b'%d\n' % os.getpid())
     finally:
@@ -152,7 +152,7 @@ class MultiListener:
             try:
                 self.v4.listen(backlog)
             except socket.error as e:
-                # on some systems v4 bind will fail if the v6 suceeded,
+                # on some systems v4 bind will fail if the v6 succeeded,
                 # in this case the v6 socket will receive v4 too.
                 if e.errno == errno.EADDRINUSE and self.v6:
                     self.v4 = None
@@ -177,19 +177,19 @@ class MultiListener:
         assert(self.bind_called)
         if self.v6:
             listenip = self.v6.getsockname()
-            debug1('%s listening on %r.\n' % (what, listenip))
-            debug2('%s listening with %r.\n' % (what, self.v6))
+            debug1('%s listening on %r.' % (what, listenip))
+            debug2('%s listening with %r.' % (what, self.v6))
         if self.v4:
             listenip = self.v4.getsockname()
-            debug1('%s listening on %r.\n' % (what, listenip))
-            debug2('%s listening with %r.\n' % (what, self.v4))
+            debug1('%s listening on %r.' % (what, listenip))
+            debug2('%s listening with %r.' % (what, self.v4))
 
 
 class FirewallClient:
 
     def __init__(self, method_name, sudo_pythonpath):
         self.auto_nets = []
-        python_path = os.path.dirname(os.path.dirname(__file__))
+
         argvbase = ([sys.executable, sys.argv[0]] +
                     ['-v'] * (helpers.verbose or 0) +
                     ['--method', method_name] +
@@ -197,54 +197,101 @@ class FirewallClient:
         if ssyslog._p:
             argvbase += ['--syslog']
 
-        # Determine how to prefix the command in order to elevate privileges.
-        if platform.platform().startswith('OpenBSD'):
-            elev_prefix = ['doas']  # OpenBSD uses built in `doas`
-        else:
-            elev_prefix = ['sudo', '-p', '[local sudo] Password: ']
-
-        # Look for binary and switch to absolute path if we can find
-        # it.
-        path = which(elev_prefix[0])
-        if path:
-            elev_prefix[0] = path
-
-        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.
-        # Instead, attach a *bidirectional* socket to its stdout, and use
-        # that for talking in both directions.
-        (s1, s2) = socket.socketpair()
-
-        def setup():
-            # run in the child process
-            s2.close()
-        if os.getuid() == 0:
-            argv_tries = argv_tries[-1:]  # last entry only
+        # A list of commands that we can try to run to start the firewall.
+        argv_tries = []
+
+        if os.getuid() == 0:  # No need to elevate privileges
+            argv_tries.append(argvbase)
+        else:
+            # Linux typically uses sudo; OpenBSD uses doas. However, some
+            # Linux distributions are starting to use doas.
+            sudo_cmd = ['sudo', '-p', '[local sudo] Password: ']+argvbase
+            doas_cmd = ['doas']+argvbase
+
+            # For clarity, try to replace executable name with the
+            # full path.
+            doas_path = which("doas")
+            if doas_path:
+                doas_cmd[0] = doas_path
+            sudo_path = which("sudo")
+            if sudo_path:
+                sudo_cmd[0] = sudo_path
+
+            # sudo_pythonpath indicates if we should set the
+            # PYTHONPATH environment variable when elevating
+            # privileges. This can be adjusted with the
+            # --no-sudo-pythonpath option.
+            if sudo_pythonpath:
+                pp_prefix = ['/usr/bin/env',
+                             'PYTHONPATH=%s' %
+                             os.path.dirname(os.path.dirname(__file__))]
+                sudo_cmd = pp_prefix + sudo_cmd
+                doas_cmd = pp_prefix + doas_cmd
+
+            # If we can find doas and not sudo or if we are on
+            # OpenBSD, try using doas first.
+            if (doas_path and not sudo_path) or \
+               platform.platform().startswith('OpenBSD'):
+                argv_tries = [doas_cmd, sudo_cmd, argvbase]
+            else:
+                argv_tries = [sudo_cmd, doas_cmd, argvbase]
+
+        # Try all commands in argv_tries in order. If a command
+        # produces an error, try the next one. If command is
+        # successful, set 'success' variable and break.
+        success = False
         for argv in argv_tries:
+            # 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. Instead, attach a
+            # *bidirectional* socket to its stdout, and use that for
+            # talking in both directions.
+            (s1, s2) = socket.socketpair()
+
+            def setup():
+                # run in the child process
+                s2.close()
+
             try:
-                if argv[0] == 'su':
-                    sys.stderr.write('[local su] ')
+                debug1("Starting firewall manager with command: %r" % argv)
                 self.p = ssubprocess.Popen(argv, stdout=s1, preexec_fn=setup)
                 # No env: Talking to `FirewallClient.start`, which has no i18n.
-                break
             except OSError as e:
-                log('Spawning firewall manager: %r\n' % argv)
-                raise Fatal(e)
-        self.argv = argv
-        s1.close()
-        self.pfile = s2.makefile('rwb')
-        line = self.pfile.readline()
-        self.check()
-        if line[0:5] != b'READY':
-            raise Fatal('%r expected READY, got %r' % (self.argv, line))
-        method_name = line[6:-1]
-        self.method = get_method(method_name.decode("ASCII"))
-        self.method.set_firewall(self)
+                # This exception will occur if the program isn't
+                # present or isn't executable.
+                debug1('Unable to start firewall manager. Popen failed. '
+                       'Command=%r Exception=%s' % (argv, e))
+                continue
+
+            self.argv = argv
+            s1.close()
+            self.pfile = s2.makefile('rwb')
+            line = self.pfile.readline()
+
+            rv = self.p.poll()   # Check if process is still running
+            if rv:
+                # We might get here if program runs and exits before
+                # outputting anything. For example, someone might have
+                # entered the wrong password to elevate privileges.
+                debug1('Unable to start firewall manager. '
+                       'Process exited too early. '
+                       '%r returned %d' % (self.argv, rv))
+                continue
+
+            if line[0:5] != b'READY':
+                debug1('Unable to start firewall manager. '
+                       'Expected READY, got %r. '
+                       'Command=%r' % (line, self.argv))
+                continue
+
+            method_name = line[6:-1]
+            self.method = get_method(method_name.decode("ASCII"))
+            self.method.set_firewall(self)
+            success = True
+            break
+
+        if not success:
+            raise Fatal("All attempts to elevate privileges failed.")
 
     def setup(self, subnets_include, subnets_exclude, nslist,
               redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp,
@@ -297,7 +344,8 @@ class FirewallClient:
         else:
             user = b'%d' % self.user
 
-        self.pfile.write(b'GO %d %s\n' % (udp, user))
+        self.pfile.write(b'GO %d %s %s %d\n' %
+                         (udp, user, bytes(self.tmark, 'ascii'), os.getpid()))
         self.pfile.flush()
 
         line = self.pfile.readline()
@@ -326,23 +374,23 @@ def expire_connections(now, mux):
     remove = []
     for chan, timeout in dnsreqs.items():
         if timeout < now:
-            debug3('expiring dnsreqs channel=%d\n' % chan)
+            debug3('expiring dnsreqs channel=%d' % chan)
             remove.append(chan)
             del mux.channels[chan]
     for chan in remove:
         del dnsreqs[chan]
-    debug3('Remaining DNS requests: %d\n' % len(dnsreqs))
+    debug3('Remaining DNS requests: %d' % len(dnsreqs))
 
     remove = []
     for peer, (chan, timeout) in udp_by_src.items():
         if timeout < now:
-            debug3('expiring UDP channel channel=%d peer=%r\n' % (chan, peer))
+            debug3('expiring UDP channel channel=%d peer=%r' % (chan, peer))
             mux.send(chan, ssnet.CMD_UDP_CLOSE, b'')
             remove.append(peer)
             del mux.channels[chan]
     for peer in remove:
         del udp_by_src[peer]
-    debug3('Remaining UDP channels: %d\n' % len(udp_by_src))
+    debug3('Remaining UDP channels: %d' % len(udp_by_src))
 
 
 def onaccept_tcp(listener, method, mux, handlers):
@@ -351,7 +399,7 @@ def onaccept_tcp(listener, method, mux,
         sock, srcip = listener.accept()
     except socket.error as e:
         if e.args[0] in [errno.EMFILE, errno.ENFILE]:
-            debug1('Rejected incoming connection: too many open files!\n')
+            debug1('Rejected incoming connection: too many open files!')
             # free up an fd so we can eat the connection
             os.close(_extra_fd)
             try:
@@ -364,15 +412,15 @@ def onaccept_tcp(listener, method, mux,
             raise
 
     dstip = method.get_tcp_dstip(sock)
-    debug1('Accept TCP: %s:%r -> %s:%r.\n' % (srcip[0], srcip[1],
-                                              dstip[0], dstip[1]))
+    debug1('Accept TCP: %s:%r -> %s:%r.' % (srcip[0], srcip[1],
+                                            dstip[0], dstip[1]))
     if dstip[1] == sock.getsockname()[1] and islocal(dstip[0], sock.family):
-        debug1("-- ignored: that's my address!\n")
+        debug1("-- ignored: that's my address!")
         sock.close()
         return
     chan = mux.next_channel()
     if not chan:
-        log('warning: too many open channels.  Discarded connection.\n')
+        log('warning: too many open channels.  Discarded connection.')
         sock.close()
         return
     mux.send(chan, ssnet.CMD_TCP_CONNECT, b'%d,%s,%d' %
@@ -385,7 +433,7 @@ def onaccept_tcp(listener, method, mux,
 def udp_done(chan, data, method, sock, dstip):
     (src, srcport, data) = data.split(b",", 2)
     srcip = (src, int(srcport))
-    debug3('doing send from %r to %r\n' % (srcip, dstip,))
+    debug3('doing send from %r to %r' % (srcip, dstip,))
     method.send_udp(sock, srcip, dstip, data)
 
 
@@ -395,7 +443,7 @@ def onaccept_udp(listener, method, mux,
     if t is None:
         return
     srcip, dstip, data = t
-    debug1('Accept UDP: %r -> %r.\n' % (srcip, dstip,))
+    debug1('Accept UDP: %r -> %r.' % (srcip, dstip,))
     if srcip in udp_by_src:
         chan, _ = udp_by_src[srcip]
     else:
@@ -412,7 +460,7 @@ def onaccept_udp(listener, method, mux,
 
 
 def dns_done(chan, data, method, sock, srcip, dstip, mux):
-    debug3('dns_done: channel=%d src=%r dst=%r\n' % (chan, srcip, dstip))
+    debug3('dns_done: channel=%d src=%r dst=%r' % (chan, srcip, dstip))
     del mux.channels[chan]
     del dnsreqs[chan]
     method.send_udp(sock, srcip, dstip, data)
@@ -427,9 +475,9 @@ def ondns(listener, method, mux, handler
     # dstip is None if we are using a method where we can't determine
     # the destination IP of the DNS request that we captured from the client.
     if dstip is None:
-        debug1('DNS request from %r: %d bytes\n' % (srcip, len(data)))
+        debug1('DNS request from %r: %d bytes' % (srcip, len(data)))
     else:
-        debug1('DNS request from %r to %r: %d bytes\n' %
+        debug1('DNS request from %r to %r: %d bytes' %
                (srcip, dstip, len(data)))
     chan = mux.next_channel()
     dnsreqs[chan] = now + 30
@@ -440,30 +488,31 @@ def ondns(listener, method, mux, handler
 
 
 def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
-          python, latency_control,
+          python, latency_control, latency_buffer_size,
           dns_listener, seed_hosts, auto_hosts, auto_nets, daemon,
           to_nameserver):
 
     helpers.logprefix = 'c : '
-    debug1('Starting client with Python version %s\n'
+    debug1('Starting client with Python version %s'
            % platform.python_version())
 
     method = fw.method
 
     handlers = []
-    debug1('Connecting to server...\n')
+    debug1('Connecting to server...')
 
     try:
         (serverproc, serversock) = ssh.connect(
             ssh_cmd, remotename, python,
             stderr=ssyslog._p and ssyslog._p.stdin,
             options=dict(latency_control=latency_control,
+                         latency_buffer_size=latency_buffer_size,
                          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("c : failed to establish ssh session (1)")
+            raise Fatal("failed to establish ssh session (1)")
         else:
             raise
     mux = Mux(serversock.makefile("rb"), serversock.makefile("wb"))
@@ -481,22 +530,99 @@ def _main(tcp_listener, udp_listener, fw
         initstring = serversock.recv(len(expected))
     except socket.error as e:
         if e.args[0] == errno.ECONNRESET:
-            raise Fatal("c : failed to establish ssh session (2)")
+            raise Fatal("failed to establish ssh session (2)")
         else:
             raise
 
+    # Returns None if process is still running (or returns exit code)
     rv = serverproc.poll()
-    if rv:
-        raise Fatal('c : server died with error code %d' % rv)
+    if rv is not None:
+        errmsg = "server died with error code %d\n" % rv
+
+        # Our fatal exceptions return exit code 99
+        if rv == 99:
+            errmsg += "This error code likely means that python started and " \
+                "the sshuttle server started. However, the sshuttle server " \
+                "may have raised a 'Fatal' exception after it started."
+        elif rv == 98:
+            errmsg += "This error code likely means that we were able to " \
+                "run python on the server, but that the program continued " \
+                "to the line after we call python's exec() to execute " \
+                "sshuttle's server code. Try specifying the python " \
+                "executable to user on the server by passing --python " \
+                "to sshuttle."
+
+        # This error should only be possible when --python is not specified.
+        elif rv == 97 and not python:
+            errmsg += "This error code likely means that either we " \
+                "couldn't find python3 or python in the PATH on the " \
+                "server or that we do not have permission to run 'exec' in " \
+                "the /bin/sh shell on the server. Try specifying the " \
+                "python executable to use on the server by passing " \
+                "--python to sshuttle."
+
+        # POSIX sh standards says error code 127 is used when you try
+        # to execute a program that does not exist. See section 2.8.2
+        # of
+        # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_08
+        elif rv == 127:
+            if python:
+                errmsg += "This error code likely means that we were not " \
+                    "able to execute the python executable that specified " \
+                    "with --python. You specified '%s'.\n" % python
+                if python.startswith("/"):
+                    errmsg += "\nTip for users in a restricted shell on the " \
+                        "server: The server may refuse to run programs " \
+                        "specified with an absolute path. Try specifying " \
+                        "just the name of the python executable. However, " \
+                        "if python is not in your PATH and you cannot " \
+                        "run programs specified with an absolute path, " \
+                        "it is possible that sshuttle will not work."
+            else:
+                errmsg += "This error code likely means that we were unable " \
+                    "to execute /bin/sh on the remote server. This can " \
+                    "happen if /bin/sh does not exist on the server or if " \
+                    "you are in a restricted shell that does not allow you " \
+                    "to run programs specified with an absolute path. " \
+                    "Try rerunning sshuttle with the --python parameter."
+
+        # When the redirected subnet includes the remote ssh host, the
+        # firewall rules can interrupt the ssh connection to the
+        # remote machine. This issue impacts some Linux machines. The
+        # user sees that the server dies with a broken pipe error and
+        # code 255.
+        #
+        # The solution to this problem is to exclude the remote
+        # server.
+        #
+        # There are many github issues from users encountering this
+        # problem. Most of the discussion on the topic is here:
+        # https://github.com/sshuttle/sshuttle/issues/191
+        elif rv == 255:
+            errmsg += "It might be possible to resolve this error by " \
+                "excluding the server that you are ssh'ing to. For example, " \
+                "if you are running 'sshuttle -v -r example.com 0/0' to " \
+                "redirect all traffic through example.com, then try " \
+                "'sshuttle -v -r example.com -x example.com 0/0' to " \
+                "exclude redirecting the connection to example.com itself " \
+                "(i.e., sshuttle's firewall rules may be breaking the " \
+                "ssh connection that it previously established). " \
+                "Alternatively, you may be able to use 'sshuttle -v -r " \
+                "example.com -x example.com:22 0/0' to redirect " \
+                "everything except ssh connections between your machine " \
+                "and example.com."
+
+        raise Fatal(errmsg)
 
     if initstring != expected:
-        raise Fatal('c : expected server init string %r; got %r'
+        raise Fatal('expected server init string %r; got %r'
                     % (expected, initstring))
-    log('Connected to server.\n')
+    log('Connected to server.')
     sys.stdout.flush()
+
     if daemon:
         daemonize()
-        log('daemonizing (%s).\n' % _pidname)
+        log('daemonizing (%s).' % _pidname)
 
     def onroutes(routestr):
         if auto_nets:
@@ -508,11 +634,11 @@ def _main(tcp_listener, udp_listener, fw
                 width = int(width)
                 ip = ip.decode("ASCII")
                 if family == socket.AF_INET6 and tcp_listener.v6 is None:
-                    debug2("Ignored auto net %d/%s/%d\n" % (family, ip, width))
+                    debug2("Ignored auto net %d/%s/%d" % (family, ip, width))
                 if family == socket.AF_INET and tcp_listener.v4 is None:
-                    debug2("Ignored auto net %d/%s/%d\n" % (family, ip, width))
+                    debug2("Ignored auto net %d/%s/%d" % (family, ip, width))
                 else:
-                    debug2("Adding auto net %d/%s/%d\n" % (family, ip, width))
+                    debug2("Adding auto net %d/%s/%d" % (family, ip, width))
                     fw.auto_nets.append((family, ip, width, 0, 0))
 
         # we definitely want to do this *after* starting ssh, or we might end
@@ -532,7 +658,7 @@ def _main(tcp_listener, udp_listener, fw
         sdnotify.send(sdnotify.ready(), sdnotify.status('Connected'))
 
     def onhostlist(hostlist):
-        debug2('got host list: %r\n' % hostlist)
+        debug2('got host list: %r' % hostlist)
         for line in hostlist.strip().split():
             if line:
                 name, ip = line.split(b',', 1)
@@ -548,7 +674,7 @@ def _main(tcp_listener, udp_listener, fw
         dns_listener.add_handler(handlers, ondns, method, mux)
 
     if seed_hosts is not None:
-        debug1('seed_hosts: %r\n' % seed_hosts)
+        debug1('seed_hosts: %r' % seed_hosts)
         mux.send(0, ssnet.CMD_HOST_REQ, str.encode('\n'.join(seed_hosts)))
 
     def check_ssh_alive():
@@ -556,7 +682,9 @@ def _main(tcp_listener, udp_listener, fw
             # poll() won't tell us when process exited since the
             # process is no longer our child (it returns 0 all the
             # 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.' %
                             serverproc.pid)
         else:
@@ -574,40 +702,47 @@ def _main(tcp_listener, udp_listener, fw
 
 
 def main(listenip_v6, listenip_v4,
-         ssh_cmd, remotename, python, latency_control, dns, nslist,
+         ssh_cmd, remotename, python, latency_control,
+         latency_buffer_size, dns, nslist,
          method_name, seed_hosts, auto_hosts, auto_nets,
          subnets_include, subnets_exclude, daemon, to_nameserver, pidfile,
          user, sudo_pythonpath, tmark):
 
     if not remotename:
-        print("WARNING: You must specify -r/--remote to securely route "
-              "traffic to a remote machine. Running without -r/--remote "
-              "is only recommended for testing.")
+        raise Fatal("You must use -r/--remote to specify a remote "
+                    "host to route traffic through.")
 
     if daemon:
         try:
             check_daemon(pidfile)
         except Fatal as e:
-            log("%s\n" % e)
+            log("%s" % e)
             return 5
-    debug1('Starting sshuttle proxy (version %s).\n' % __version__)
+    debug1('Starting sshuttle proxy (version %s).' % __version__)
     helpers.logprefix = 'c : '
 
     fw = FirewallClient(method_name, sudo_pythonpath)
 
-    # If --dns is used, store the IP addresses that the client
-    # normally uses for DNS lookups in nslist. The firewall needs to
-    # redirect packets outgoing to this server to the remote host
+    # nslist is the list of name severs to intercept. If --dns is
+    # used, we add all DNS servers in resolv.conf. Otherwise, the list
+    # 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.
     if dns:
         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:
             to_nameserver = "%s@%s" % tuple(to_nameserver[1:])
-    else:
-        # option doesn't make sense if we aren't proxying dns
+    else:  # if we are not intercepting DNS traffic
+        # ...and the user specified a server to send DNS traffic to.
         if to_nameserver and len(to_nameserver) > 0:
-            print("WARNING: --to-ns option is ignored because --dns was not "
-                  "used.")
+            print("WARNING: --to-ns option is ignored unless "
+                  "--dns or --ns-hosts is used.")
         to_nameserver = None
 
     # Get family specific subnet lists. Also, the user may not specify
@@ -643,14 +778,14 @@ def main(listenip_v6, listenip_v4,
     #    "auto" when listen address is unspecified.
     #    The user specified address if provided by user
     if listenip_v6 is None:
-        debug1("IPv6 disabled by --disable-ipv6\n")
+        debug1("IPv6 disabled by --disable-ipv6")
     if listenip_v6 == "auto":
         if avail.ipv6:
-            debug1("IPv6 enabled: Using default IPv6 listen address ::1\n")
+            debug1("IPv6 enabled: Using default IPv6 listen address ::1")
             listenip_v6 = ('::1', 0)
         else:
             debug1("IPv6 disabled since it isn't supported by method "
-                   "%s.\n" % fw.method.name)
+                   "%s." % fw.method.name)
             listenip_v6 = None
 
     # Make final decision about enabling IPv6:
@@ -722,9 +857,9 @@ def main(listenip_v6, listenip_v4,
                 msg += "(available)"
             else:
                 msg += "(not available with %s method)" % fw.method.name
-        debug1(msg + "\n")
+        debug1(msg)
 
-    debug1("Method: %s\n" % fw.method.name)
+    debug1("Method: %s" % fw.method.name)
     feature_status("IPv4", required.ipv4, avail.ipv4)
     feature_status("IPv6", required.ipv6, avail.ipv6)
     feature_status("UDP ", required.udp, avail.udp)
@@ -744,20 +879,20 @@ def main(listenip_v6, listenip_v4,
     # because we do that below when we have identified the ports to
     # listen on.
     debug1("Subnets to forward through remote host (type, IP, cidr mask "
-           "width, startPort, endPort):\n")
+           "width, startPort, endPort):")
     for i in subnets_include:
-        debug1("  "+str(i)+"\n")
+        debug1("  "+str(i))
     if auto_nets:
         debug1("NOTE: Additional subnets to forward may be added below by "
-               "--auto-nets.\n")
-    debug1("Subnets to exclude from forwarding:\n")
+               "--auto-nets.")
+    debug1("Subnets to exclude from forwarding:")
     for i in subnets_exclude:
-        debug1("  "+str(i)+"\n")
+        debug1("  "+str(i))
     if required.dns:
         debug1("DNS requests normally directed at these servers will be "
-               "redirected to remote:\n")
+               "redirected to remote:")
         for i in nslist:
-            debug1("  "+str(i)+"\n")
+            debug1("  "+str(i))
 
     if listenip_v6 and listenip_v6[1] and listenip_v4 and listenip_v4[1]:
         # if both ports given, no need to search for a spare port
@@ -775,7 +910,7 @@ def main(listenip_v6, listenip_v4,
     redirectport_v4 = 0
     bound = False
     for port in ports:
-        debug2('Trying to bind redirector on port %d\n' % port)
+        debug2('Trying to bind redirector on port %d' % port)
         tcp_listener = MultiListener()
 
         if required.udp:
@@ -830,7 +965,7 @@ def main(listenip_v6, listenip_v4,
         # search for spare port for DNS
         ports = range(12300, 9000, -1)
         for port in ports:
-            debug2('Trying to bind DNS redirector on port %d\n' % port)
+            debug2('Trying to bind DNS redirector on port %d' % port)
             if port in used_ports:
                 continue
 
@@ -908,8 +1043,9 @@ def main(listenip_v6, listenip_v4,
     # 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, to_nameserver)
+                     python, latency_control, latency_buffer_size,
+                     dns_listener, seed_hosts, auto_hosts, auto_nets,
+                     daemon, to_nameserver)
     finally:
         try:
             if daemon:
diff -pruN 1.0.5-1/sshuttle/cmdline.py 1.1.0-1/sshuttle/cmdline.py
--- 1.0.5-1/sshuttle/cmdline.py	2020-12-27 23:46:37.000000000 +0000
+++ 1.1.0-1/sshuttle/cmdline.py	2022-01-27 22:07:24.000000000 +0000
@@ -17,11 +17,11 @@ def main():
     if opt.sudoers or opt.sudoers_no_modify:
         if platform.platform().startswith('OpenBSD'):
             log('Automatic sudoers does not work on BSD')
-            exit(1)
+            return 1
 
         if not opt.sudoers_filename:
-            log('--sudoers-file must be set or omited.')
-            exit(1)
+            log('--sudoers-file must be set or omitted.')
+            return 1
 
         sudoers(
             user_name=opt.sudoers_user,
@@ -85,6 +85,13 @@ def main():
                 ipport_v4 = "auto"
                 # parse_ipport6('[::1]:0')
                 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:
                 ssyslog.start_syslog()
                 ssyslog.close_stdin()
@@ -95,6 +102,7 @@ def main():
                                       remotename,
                                       opt.python,
                                       opt.latency_control,
+                                      opt.latency_buffer_size,
                                       opt.dns,
                                       nslist,
                                       opt.method,
@@ -117,9 +125,9 @@ def main():
             return return_code
 
     except Fatal as e:
-        log('fatal: %s\n' % e)
+        log('fatal: %s' % e)
         return 99
     except KeyboardInterrupt:
         log('\n')
-        log('Keyboard interrupt: exiting.\n')
+        log('Keyboard interrupt: exiting.')
         return 1
diff -pruN 1.0.5-1/sshuttle/firewall.py 1.1.0-1/sshuttle/firewall.py
--- 1.0.5-1/sshuttle/firewall.py	2020-12-27 23:46:37.000000000 +0000
+++ 1.1.0-1/sshuttle/firewall.py	2022-01-27 22:07:24.000000000 +0000
@@ -5,13 +5,15 @@ import sys
 import os
 import platform
 import traceback
+import subprocess as ssubprocess
 
 import sshuttle.ssyslog as ssyslog
 import sshuttle.helpers as helpers
-from sshuttle.helpers import debug1, debug2, Fatal
+from sshuttle.helpers import log, debug1, debug2, Fatal
 from sshuttle.methods import get_auto_method, get_method
 
 HOSTSFILE = '/etc/hosts'
+sshuttle_pid = None
 
 
 def rewrite_etc_hosts(hostmap, port):
@@ -51,26 +53,51 @@ def rewrite_etc_hosts(hostmap, port):
 def restore_etc_hosts(hostmap, port):
     # Only restore if we added hosts to /etc/hosts previously.
     if len(hostmap) > 0:
-        debug2('undoing /etc/hosts changes.\n')
+        debug2('undoing /etc/hosts changes.')
         rewrite_etc_hosts({}, port)
 
 
+def firewall_exit(signum, frame):
+    # The typical sshuttle exit is that the main sshuttle process
+    # exits, closes file descriptors it uses, and the firewall process
+    # notices that it can't read from stdin anymore and exits
+    # (cleaning up firewall rules).
+    #
+    # However, in some cases, Ctrl+C might get sent to the firewall
+    # process. This might caused if someone manually tries to kill the
+    # firewall process, or if sshuttle was started using sudo's use_pty option
+    # and they try to exit by pressing Ctrl+C. Here, we forward the
+    # Ctrl+C/SIGINT to the main sshuttle process which should trigger
+    # the typical exit process as described above.
+    global sshuttle_pid
+    if sshuttle_pid:
+        debug1("Relaying SIGINT to sshuttle process %d\n" % sshuttle_pid)
+        os.kill(sshuttle_pid, signal.SIGINT)
+
+
 # Isolate function that needs to be replaced for tests
 def setup_daemon():
     if os.getuid() != 0:
-        raise Fatal('fw: '
-                    'You must be root (or enable su/sudo) to set the firewall')
+        raise Fatal('You must be root (or enable su/sudo) to set the firewall')
 
     # don't disappear if our controlling terminal or stdout/stderr
     # disappears; we still have to clean up.
     signal.signal(signal.SIGHUP, signal.SIG_IGN)
     signal.signal(signal.SIGPIPE, signal.SIG_IGN)
-    signal.signal(signal.SIGTERM, signal.SIG_IGN)
-    signal.signal(signal.SIGINT, signal.SIG_IGN)
+    signal.signal(signal.SIGTERM, firewall_exit)
+    signal.signal(signal.SIGINT, firewall_exit)
 
-    # ctrl-c shouldn't be passed along to me.  When the main sshuttle dies,
-    # I'll die automatically.
-    os.setsid()
+    # Calling setsid() here isn't strictly necessary. However, it forces
+    # Ctrl+C to get sent to the main sshuttle process instead of to
+    # the firewall process---which is our preferred way to shutdown.
+    # Nonetheless, if the firewall process receives a SIGTERM/SIGINT
+    # signal, it will relay a SIGINT to the main sshuttle process
+    # automatically.
+    try:
+        os.setsid()
+    except OSError:
+        # setsid() fails if sudo is configured with the use_pty option.
+        pass
 
     # because of limitations of the 'su' command, the *real* stdin/stdout
     # are both attached to stdout initially.  Clone stdout into stdin so we
@@ -90,19 +117,50 @@ def subnet_weight(s):
     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
+
+    p = None
+    if helpers.which("resolvectl"):
+        debug2("Flushing systemd's DNS resolver cache: "
+               "resolvectl flush-caches")
+        p = 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")
+        p = ssubprocess.Popen(["systemd-resolve", "--flush-caches"],
+                              stdout=ssubprocess.PIPE, env=helpers.get_env())
+
+    if p:
+        # Wait so flush is finished and process doesn't show up as defunct.
+        rv = p.wait()
+        if rv != 0:
+            log("Received non-zero return code %d when flushing DNS resolver "
+                "cache." % rv)
+
+
 # This is some voodoo for setting up the kernel's transparent
 # proxying stuff.  If subnets is empty, we just delete our sshuttle rules;
 # otherwise we delete it, then make them from scratch.
 #
 # This code is supposed to clean up after itself by deleting its rules on
 # 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
+# supersede it in the transproxy list, at least, so the leftover rules
 # are hopefully harmless.
 def main(method_name, syslog):
+    helpers.logprefix = 'fw: '
     stdin, stdout = setup_daemon()
     hostmap = {}
-    helpers.logprefix = 'fw: '
-    debug1('Starting firewall with Python version %s\n'
+    debug1('Starting firewall with Python version %s'
            % platform.python_version())
 
     if method_name == "auto":
@@ -119,7 +177,7 @@ def main(method_name, syslog):
                     "Check that the appropriate programs are in your "
                     "PATH." % method_name)
 
-    debug1('ready method name %s.\n' % method.name)
+    debug1('ready method name %s.' % method.name)
     stdout.write('READY %s\n' % method.name)
     stdout.flush()
 
@@ -136,14 +194,14 @@ def main(method_name, syslog):
     while 1:
         line = stdin.readline(128)
         if not line:
-            raise Fatal('fw: expected route but got %r' % line)
+            raise Fatal('expected route but got %r' % line)
         elif line.startswith("NSLIST\n"):
             break
         try:
             (family, width, exclude, ip, fport, lport) = \
                     line.strip().split(',', 5)
         except BaseException:
-            raise Fatal('fw: expected route or NSLIST but got %r' % line)
+            raise Fatal('expected route or NSLIST but got %r' % line)
         subnets.append((
             int(family),
             int(width),
@@ -151,31 +209,31 @@ def main(method_name, syslog):
             ip,
             int(fport),
             int(lport)))
-    debug2('Got subnets: %r\n' % subnets)
+    debug2('Got subnets: %r' % subnets)
 
     nslist = []
     if line != 'NSLIST\n':
-        raise Fatal('fw: expected NSLIST but got %r' % line)
+        raise Fatal('expected NSLIST but got %r' % line)
     while 1:
         line = stdin.readline(128)
         if not line:
-            raise Fatal('fw: expected nslist but got %r' % line)
+            raise Fatal('expected nslist but got %r' % line)
         elif line.startswith("PORTS "):
             break
         try:
             (family, ip) = line.strip().split(',', 1)
         except BaseException:
-            raise Fatal('fw: expected nslist or PORTS but got %r' % line)
+            raise Fatal('expected nslist or PORTS but got %r' % line)
         nslist.append((int(family), ip))
-        debug2('Got partial nslist: %r\n' % nslist)
-    debug2('Got nslist: %r\n' % nslist)
+        debug2('Got partial nslist: %r' % nslist)
+    debug2('Got nslist: %r' % nslist)
 
     if not line.startswith('PORTS '):
-        raise Fatal('fw: expected PORTS but got %r' % line)
+        raise Fatal('expected PORTS but got %r' % line)
     _, _, ports = line.partition(" ")
     ports = ports.split(",")
     if len(ports) != 4:
-        raise Fatal('fw: expected 4 ports but got %d' % len(ports))
+        raise Fatal('expected 4 ports but got %d' % len(ports))
     port_v6 = int(ports[0])
     port_v4 = int(ports[1])
     dnsport_v6 = int(ports[2])
@@ -190,21 +248,24 @@ def main(method_name, syslog):
     assert(dnsport_v4 >= 0)
     assert(dnsport_v4 <= 65535)
 
-    debug2('Got ports: %d,%d,%d,%d\n'
+    debug2('Got ports: %d,%d,%d,%d'
            % (port_v6, port_v4, dnsport_v6, dnsport_v4))
 
     line = stdin.readline(128)
     if not line:
-        raise Fatal('fw: expected GO but got %r' % line)
+        raise Fatal('expected GO but got %r' % line)
     elif not line.startswith("GO "):
-        raise Fatal('fw: expected GO but got %r' % line)
+        raise Fatal('expected GO but got %r' % line)
 
     _, _, args = line.partition(" ")
-    udp, user = args.strip().split(" ", 1)
+    global sshuttle_pid
+    udp, user, tmark, sshuttle_pid = args.strip().split(" ", 3)
     udp = bool(int(udp))
+    sshuttle_pid = int(sshuttle_pid)
     if user == '-':
         user = None
-    debug2('Got udp: %r, user: %r\n' % (udp, user))
+    debug2('Got udp: %r, user: %r, tmark: %s, sshuttle_pid: %d' %
+           (udp, user, tmark, sshuttle_pid))
 
     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]
@@ -212,22 +273,23 @@ def main(method_name, syslog):
     nslist_v4 = [i for i in nslist if i[0] == socket.AF_INET]
 
     try:
-        debug1('setting up.\n')
+        debug1('setting up.')
 
         if subnets_v6 or nslist_v6:
-            debug2('setting up IPv6.\n')
+            debug2('setting up IPv6.')
             method.setup_firewall(
                 port_v6, dnsport_v6, nslist_v6,
                 socket.AF_INET6, subnets_v6, udp,
-                user)
+                user, tmark)
 
         if subnets_v4 or nslist_v4:
-            debug2('setting up IPv4.\n')
+            debug2('setting up IPv4.')
             method.setup_firewall(
                 port_v4, dnsport_v4, nslist_v4,
                 socket.AF_INET, subnets_v4, udp,
-                user)
+                user, tmark)
 
+        flush_systemd_dns_cache()
         stdout.write('STARTED\n')
 
         try:
@@ -245,40 +307,38 @@ def main(method_name, syslog):
             if line.startswith('HOST '):
                 (name, ip) = line[5:].strip().split(',', 1)
                 hostmap[name] = ip
-                debug2('setting up /etc/hosts.\n')
+                debug2('setting up /etc/hosts.')
                 rewrite_etc_hosts(hostmap, port_v6 or port_v4)
             elif line:
                 if not method.firewall_command(line):
-                    raise Fatal('fw: expected command, got %r' % line)
+                    raise Fatal('expected command, got %r' % line)
             else:
                 break
     finally:
         try:
-            debug1('undoing changes.\n')
+            debug1('undoing changes.')
         except BaseException:
             debug2('An error occurred, ignoring it.')
 
         try:
             if subnets_v6 or nslist_v6:
-                debug2('undoing IPv6 changes.\n')
+                debug2('undoing IPv6 changes.')
                 method.restore_firewall(port_v6, socket.AF_INET6, udp, user)
         except BaseException:
             try:
-                debug1("Error trying to undo IPv6 firewall.\n")
-                for line in traceback.format_exc().splitlines():
-                    debug1("---> %s\n" % line)
+                debug1("Error trying to undo IPv6 firewall.")
+                debug1(traceback.format_exc())
             except BaseException:
                 debug2('An error occurred, ignoring it.')
 
         try:
             if subnets_v4 or nslist_v4:
-                debug2('undoing IPv4 changes.\n')
+                debug2('undoing IPv4 changes.')
                 method.restore_firewall(port_v4, socket.AF_INET, udp, user)
         except BaseException:
             try:
-                debug1("Error trying to undo IPv4 firewall.\n")
-                for line in traceback.format_exc().splitlines():
-                    debug1("---> %s\n" % line)
+                debug1("Error trying to undo IPv4 firewall.")
+                debug1(traceback.format_exc())
             except BaseException:
                 debug2('An error occurred, ignoring it.')
 
@@ -287,8 +347,16 @@ def main(method_name, syslog):
             restore_etc_hosts(hostmap, port_v6 or port_v4)
         except BaseException:
             try:
-                debug1("Error trying to undo /etc/hosts changes.\n")
-                for line in traceback.format_exc().splitlines():
-                    debug1("---> %s\n" % line)
+                debug1("Error trying to undo /etc/hosts changes.")
+                debug1(traceback.format_exc())
             except BaseException:
                 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.")
diff -pruN 1.0.5-1/sshuttle/helpers.py 1.1.0-1/sshuttle/helpers.py
--- 1.0.5-1/sshuttle/helpers.py	2020-12-16 09:08:57.000000000 +0000
+++ 1.1.0-1/sshuttle/helpers.py	2022-01-27 22:07:24.000000000 +0000
@@ -15,14 +15,22 @@ def log(s):
     global logprefix
     try:
         sys.stdout.flush()
-        if s.find("\n") != -1:
-            prefix = logprefix
-            s = s.rstrip("\n")
-            for line in s.split("\n"):
-                sys.stderr.write(prefix + line + "\n")
-                prefix = "---> "
-        else:
-            sys.stderr.write(logprefix + s)
+        # Put newline at end of string if line doesn't have one.
+        if not s.endswith("\n"):
+            s = s+"\n"
+
+        prefix = logprefix
+        s = s.rstrip("\n")
+        for line in s.split("\n"):
+            # We output with \r\n instead of \n because when we use
+            # sudo with the use_pty option, the firewall process, the
+            # other processes printing to the terminal will have the
+            # \n move to the next line, but they will fail to reset
+            # cursor to the beginning of the line. Printing output
+            # with \r\n endings fixes that problem and does not appear
+            # to cause problems elsewhere.
+            sys.stderr.write(prefix + line + "\r\n")
+            prefix = "    "
         sys.stderr.flush()
     except IOError:
         # this could happen if stderr gets forcibly disconnected, eg. because
@@ -91,11 +99,11 @@ def resolvconf_nameservers(systemd_resol
                 words = line.lower().split()
                 if len(words) >= 2 and words[0] == 'nameserver':
                     this_file_nsservers.append(family_ip_tuple(words[1]))
-            debug2("Found DNS servers in %s: %s\n" %
+            debug2("Found DNS servers in %s: %s" %
                    (f, [n[1] for n in this_file_nsservers]))
             nsservers += this_file_nsservers
         except OSError as e:
-            debug3("Failed to read %s when looking for DNS servers: %s\n" %
+            debug3("Failed to read %s when looking for DNS servers: %s" %
                    (f, e.strerror))
 
     return nsservers
@@ -215,7 +223,7 @@ def which(file, mode=os.F_OK | os.X_OK):
     path = get_path()
     rv = _which(file, mode, path)
     if rv:
-        debug2("which() found '%s' at %s\n" % (file, rv))
+        debug2("which() found '%s' at %s" % (file, rv))
     else:
-        debug2("which() could not find '%s' in %s\n" % (file, path))
+        debug2("which() could not find '%s' in %s" % (file, path))
     return rv
diff -pruN 1.0.5-1/sshuttle/hostwatch.py 1.1.0-1/sshuttle/hostwatch.py
--- 1.0.5-1/sshuttle/hostwatch.py	2020-12-16 09:08:57.000000000 +0000
+++ 1.1.0-1/sshuttle/hostwatch.py	2022-01-27 22:07:24.000000000 +0000
@@ -15,16 +15,16 @@ POLL_TIME = 60 * 15
 NETSTAT_POLL_TIME = 30
 CACHEFILE = os.path.expanduser('~/.sshuttle.hosts')
 
+# Have we already failed to write CACHEFILE?
+CACHE_WRITE_FAILED = False
 
-_nmb_ok = True
-_smb_ok = True
 hostnames = {}
 queue = {}
 try:
     null = open(os.devnull, 'wb')
 except IOError:
     _, e = sys.exc_info()[:2]
-    log('warning: %s\n' % e)
+    log('warning: %s' % e)
     null = os.popen("sh -c 'while read x; do :; done'", 'wb', 4096)
 
 
@@ -33,7 +33,10 @@ def _is_ip(s):
 
 
 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())
+    global CACHE_WRITE_FAILED
     try:
         f = open(tmpname, 'wb')
         for name, ip in sorted(hostnames.items()):
@@ -41,7 +44,15 @@ def write_host_cache():
         f.close()
         os.chmod(tmpname, 384)  # 600 in octal, 'rw-------'
         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:
             os.unlink(tmpname)
         except BaseException:
@@ -49,25 +60,34 @@ def write_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:
         f = open(CACHEFILE)
-    except IOError:
+    except (OSError, IOError):
         _, e = sys.exc_info()[:2]
         if e.errno == errno.ENOENT:
             return
         else:
-            raise
+            log("Failed to read existing host cache file %s on remote host"
+                % CACHEFILE)
+            return
     for line in f:
         words = line.strip().split(',')
         if len(words) == 2:
             (name, ip) = words
             name = re.sub(r'[^-\w\.]', '-', name).strip()
+            # Remove characters that shouldn't be in IP
             ip = re.sub(r'[^0-9.]', '', ip).strip()
             if name and ip:
                 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'[^-\w\.]', '_', hostname)
     if (ip.startswith('127.') or ip.startswith('255.') or
@@ -80,43 +100,51 @@ def found_host(name, ip):
     oldip = hostnames.get(name)
     if oldip != ip:
         hostnames[name] = ip
-        debug1('Found: %s: %s\n' % (name, ip))
+        debug1('Found: %s: %s' % (name, ip))
         sys.stdout.write('%s,%s\n' % (name, ip))
         write_host_cache()
 
 
 def _check_etc_hosts():
-    debug2(' > hosts\n')
-    for line in open('/etc/hosts'):
-        line = re.sub(r'#.*', '', line)
-        words = line.strip().split()
-        if not words:
-            continue
-        ip = words[0]
-        names = words[1:]
-        if _is_ip(ip):
-            debug3('<    %s %r\n' % (ip, names))
-            for n in names:
-                check_host(n)
-                found_host(n, ip)
+    """If possible, read /etc/hosts to find hosts."""
+    filename = '/etc/hosts'
+    debug2(' > Reading %s on remote host' % filename)
+    try:
+        for line in open(filename):
+            line = re.sub(r'#.*', '', line)  # remove comments
+            words = line.strip().split()
+            if not words:
+                continue
+            ip = words[0]
+            if _is_ip(ip):
+                names = words[1:]
+                debug3('<    %s %r' % (ip, names))
+                for n in names:
+                    check_host(n)
+                    found_host(n, ip)
+    except (OSError, IOError):
+        debug1("Failed to read %s on remote host" % filename)
 
 
 def _check_revdns(ip):
-    debug2(' > rev: %s\n' % ip)
+    """Use reverse DNS to try to get hostnames from an IP addresses."""
+    debug2(' > rev: %s' % ip)
     try:
         r = socket.gethostbyaddr(ip)
-        debug3('<    %s\n' % r[0])
+        debug3('<    %s' % r[0])
         check_host(r[0])
         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
 
 
 def _check_dns(hostname):
-    debug2(' > dns: %s\n' % hostname)
+    debug2(' > dns: %s' % hostname)
     try:
         ip = socket.gethostbyname(hostname)
-        debug3('<    %s\n' % ip)
+        debug3('<    %s' % ip)
         check_host(ip)
         found_host(hostname, ip)
     except (socket.gaierror, UnicodeError):
@@ -124,7 +152,7 @@ def _check_dns(hostname):
 
 
 def _check_netstat():
-    debug2(' > netstat\n')
+    debug2(' > netstat')
     argv = ['netstat', '-n']
     try:
         p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null,
@@ -133,104 +161,19 @@ def _check_netstat():
         p.wait()
     except OSError:
         _, e = sys.exc_info()[:2]
-        log('%r failed: %r\n' % (argv, e))
+        log('%r failed: %r' % (argv, e))
         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):
-        debug3('<    %s\n' % ip)
-        check_host(ip)
-
-
-def _check_smb(hostname):
-    return
-    global _smb_ok
-    if not _smb_ok:
-        return
-    debug2(' > smb: %s\n' % 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\n' % (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\n' % 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\n' % (workgroup, hostname))
-        check_host(hostname)
-        check_workgroup(workgroup)
-
-    if lines:
-        assert(0)
-
+        if ip not in ip_list:
+            ip_list.append(ip)
 
-def _check_nmb(hostname, is_workgroup, is_master):
-    return
-    global _nmb_ok
-    if not _nmb_ok:
-        return
-    debug2(' > n%d%d: %s\n' % (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\n' % (argv, e))
-        _nmb_ok = False
-        return
-    if rv:
-        log('%r returned %d\n' % (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\n' % (name, ip))
-            if is_workgroup:
-                _enqueue(_check_smb, ip)
-            else:
-                found_host(name, ip)
-                check_host(name)
+    for ip in sorted(ip_list):
+        debug3('<    %s' % ip)
+        check_host(ip)
 
 
 def check_host(hostname):
@@ -238,13 +181,6 @@ def check_host(hostname):
         _enqueue(_check_revdns, hostname)
     else:
         _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):
@@ -263,12 +199,9 @@ def _stdin_still_ok(timeout):
 
 
 def hw_main(seed_hosts, auto_hosts):
-    if helpers.verbose >= 2:
-        helpers.logprefix = 'HH: '
-    else:
-        helpers.logprefix = 'hostwatch: '
+    helpers.logprefix = 'HH: '
 
-    debug1('Starting hostwatch with Python version %s\n'
+    debug1('Starting hostwatch with Python version %s'
            % platform.python_version())
 
     for h in seed_hosts:
@@ -280,18 +213,22 @@ def hw_main(seed_hosts, auto_hosts):
         _enqueue(_check_netstat)
         check_host('localhost')
         check_host(socket.gethostname())
-        check_workgroup('workgroup')
-        check_workgroup('-')
 
     while 1:
         now = time.time()
+        # For each item in the queue
         for t, last_polled in list(queue.items()):
             (op, args) = t
             if not _stdin_still_ok(0):
                 break
+
+            # Determine if we need to run.
             maxtime = POLL_TIME
+            # netstat runs more often than other jobs
             if op == _check_netstat:
                 maxtime = NETSTAT_POLL_TIME
+
+            # Check if this jobs needs to run.
             if now - last_polled > maxtime:
                 queue[t] = time.time()
                 op(*args)
@@ -301,5 +238,5 @@ def hw_main(seed_hosts, auto_hosts):
                 break
 
         # 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
diff -pruN 1.0.5-1/sshuttle/linux.py 1.1.0-1/sshuttle/linux.py
--- 1.0.5-1/sshuttle/linux.py	2020-12-16 09:08:57.000000000 +0000
+++ 1.1.0-1/sshuttle/linux.py	2022-01-27 22:07:24.000000000 +0000
@@ -7,7 +7,7 @@ def nonfatal(func, *args):
     try:
         func(*args)
     except Fatal as e:
-        log('fw: error: %s\n' % e)
+        log('error: %s' % e)
 
 
 def ipt_chain_exists(family, table, name):
@@ -17,27 +17,27 @@ def ipt_chain_exists(family, table, name
         cmd = 'iptables'
     else:
         raise Exception('Unsupported family "%s"' % family_to_string(family))
-    argv = [cmd, '-t', table, '-nL']
+    argv = [cmd, '-w', '-t', table, '-nL']
     try:
         output = ssubprocess.check_output(argv, env=get_env())
         for line in output.decode('ASCII').split('\n'):
             if line.startswith('Chain %s ' % name):
                 return True
     except ssubprocess.CalledProcessError as e:
-        raise Fatal('fw: %r returned %d' % (argv, e.returncode))
+        raise Fatal('%r returned %d' % (argv, e.returncode))
 
 
 def ipt(family, table, *args):
     if family == socket.AF_INET6:
-        argv = ['ip6tables', '-t', table] + list(args)
+        argv = ['ip6tables', '-w', '-t', table] + list(args)
     elif family == socket.AF_INET:
-        argv = ['iptables', '-t', table] + list(args)
+        argv = ['iptables', '-w', '-t', table] + list(args)
     else:
         raise Exception('Unsupported family "%s"' % family_to_string(family))
-    debug1('%s\n' % ' '.join(argv))
+    debug1('%s' % ' '.join(argv))
     rv = ssubprocess.call(argv, env=get_env())
     if rv:
-        raise Fatal('fw: %r returned %d' % (argv, rv))
+        raise Fatal('%r returned %d' % (argv, rv))
 
 
 def nft(family, table, action, *args):
@@ -45,29 +45,7 @@ def nft(family, table, action, *args):
         argv = ['nft', action, 'inet', table] + list(args)
     else:
         raise Exception('Unsupported family "%s"' % family_to_string(family))
-    debug1('%s\n' % ' '.join(argv))
+    debug1('%s' % ' '.join(argv))
     rv = ssubprocess.call(argv, env=get_env())
     if rv:
-        raise Fatal('fw: %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('fw: WARNING: your iptables is missing '
-                'the ttl module.\n')
-            _no_ttl_module = True
-    else:
-        ipt(family, *args)
+        raise Fatal('%r returned %d' % (argv, rv))
diff -pruN 1.0.5-1/sshuttle/methods/__init__.py 1.1.0-1/sshuttle/methods/__init__.py
--- 1.0.5-1/sshuttle/methods/__init__.py	2020-12-27 23:46:37.000000000 +0000
+++ 1.1.0-1/sshuttle/methods/__init__.py	2022-01-27 22:07:24.000000000 +0000
@@ -66,7 +66,7 @@ class BaseMethod(object):
 
     @staticmethod
     def recv_udp(udp_listener, bufsize):
-        debug3('Accept UDP using recvfrom.\n')
+        debug3('Accept UDP using recvfrom.')
         data, srcip = udp_listener.recvfrom(bufsize)
         return (srcip, None, data)
 
@@ -87,11 +87,11 @@ class BaseMethod(object):
         for key in ["udp", "dns", "ipv6", "ipv4", "user"]:
             if getattr(features, key) and not getattr(avail, key):
                 raise Fatal(
-                    "Feature %s not supported with method %s.\n" %
+                    "Feature %s not supported with method %s." %
                     (key, self.name))
 
     def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
-                       user):
+                       user, tmark):
         raise NotImplementedError()
 
     def restore_firewall(self, port, family, udp, user):
@@ -108,13 +108,13 @@ def get_method(method_name):
 
 
 def get_auto_method():
-    debug3("Selecting a method automatically...\n")
+    debug3("Selecting a method automatically...")
     # Try these methods, in order:
     methods_to_try = ["nat", "nft", "pf", "ipfw"]
     for m in methods_to_try:
         method = get_method(m)
         if method.is_supported():
-            debug3("Method '%s' was automatically selected.\n" % m)
+            debug3("Method '%s' was automatically selected." % m)
             return method
 
     raise Fatal("Unable to automatically find a supported method. Check that "
diff -pruN 1.0.5-1/sshuttle/methods/ipfw.py 1.1.0-1/sshuttle/methods/ipfw.py
--- 1.0.5-1/sshuttle/methods/ipfw.py	2020-12-27 23:46:37.000000000 +0000
+++ 1.1.0-1/sshuttle/methods/ipfw.py	2022-01-27 22:07:24.000000000 +0000
@@ -4,72 +4,40 @@ from sshuttle.methods import BaseMethod
 from sshuttle.helpers import log, debug1, debug2, debug3, \
     Fatal, family_to_string, get_env, which
 
-recvmsg = None
-try:
-    # try getting recvmsg from python
-    import socket as pythonsocket
-    getattr(pythonsocket.socket, "recvmsg")
-    socket = pythonsocket
-    recvmsg = "python"
-except AttributeError:
-    # try getting recvmsg from socket_ext library
-    try:
-        import socket_ext
-        getattr(socket_ext.socket, "recvmsg")
-        socket = socket_ext
-        recvmsg = "socket_ext"
-    except ImportError:
-        import socket
+import socket
 
 IP_BINDANY = 24
 IP_RECVDSTADDR = 7
 SOL_IPV6 = 41
 IPV6_RECVDSTADDR = 74
 
-if recvmsg == "python":
-    def recv_udp(listener, bufsize):
-        debug3('Accept UDP python using recvmsg.\n')
-        data, ancdata, _, srcip = listener.recvmsg(4096,
-                                                   socket.CMSG_SPACE(4))
-        dstip = None
-        for cmsg_level, cmsg_type, cmsg_data in ancdata:
-            if cmsg_level == socket.SOL_IP and cmsg_type == IP_RECVDSTADDR:
-                port = 53
-                ip = socket.inet_ntop(socket.AF_INET, cmsg_data[0:4])
-                dstip = (ip, port)
-                break
-        return (srcip, dstip, data)
-elif recvmsg == "socket_ext":
-    def recv_udp(listener, bufsize):
-        debug3('Accept UDP using socket_ext recvmsg.\n')
-        srcip, data, adata, _ = listener.recvmsg((bufsize,),
-                                                 socket.CMSG_SPACE(4))
-        dstip = None
-        for a in adata:
-            if a.cmsg_level == socket.SOL_IP and a.cmsg_type == IP_RECVDSTADDR:
-                port = 53
-                ip = socket.inet_ntop(socket.AF_INET, a.cmsg_data[0:4])
-                dstip = (ip, port)
-                break
-        return (srcip, dstip, data[0])
-else:
-    def recv_udp(listener, bufsize):
-        debug3('Accept UDP using recvfrom.\n')
-        data, srcip = listener.recvfrom(bufsize)
-        return (srcip, None, data)
+
+def recv_udp(listener, bufsize):
+    debug3('Accept UDP python using recvmsg.')
+    data, ancdata, _, srcip = listener.recvmsg(4096,
+                                               socket.CMSG_SPACE(4))
+    dstip = None
+    for cmsg_level, cmsg_type, cmsg_data in ancdata:
+        if cmsg_level == socket.SOL_IP and cmsg_type == IP_RECVDSTADDR:
+            port = 53
+            ip = socket.inet_ntop(socket.AF_INET, cmsg_data[0:4])
+            dstip = (ip, port)
+            break
+    return (srcip, dstip, data)
 
 
 def ipfw_rule_exists(n):
-    argv = ['ipfw', 'list']
+    argv = ['ipfw', 'list', '%d' % n]
     p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=get_env())
 
     found = False
     for line in p.stdout:
         if line.startswith(b'%05d ' % n):
-            if not ('ipttl 63' in line or 'check-state' in line):
-                log('non-sshuttle ipfw rule: %r\n' % line.strip())
+            if 'check-state :sshuttle' not in line:
+                log('non-sshuttle ipfw rule: %r' % line.strip())
                 raise Fatal('non-sshuttle ipfw rule #%d already exists!' % n)
             found = True
+            break
     rv = p.wait()
     if rv:
         raise Fatal('%r returned %d' % (argv, rv))
@@ -96,7 +64,7 @@ def _fill_oldctls(prefix):
 
 def _sysctl_set(name, val):
     argv = ['sysctl', '-w', '%s=%s' % (name, val)]
-    debug1('>> %s\n' % ' '.join(argv))
+    debug1('>> %s' % ' '.join(argv))
     return ssubprocess.call(argv, stdout=open(os.devnull, 'w'), env=get_env())
     # No env: No output. (Or error that won't be parsed.)
 
@@ -111,13 +79,13 @@ def sysctl_set(name, val, permanent=Fals
     if not _oldctls:
         _fill_oldctls(PREFIX)
     if not (name in _oldctls):
-        debug1('>> No such sysctl: %r\n' % name)
+        debug1('>> No such sysctl: %r' % name)
         return False
     oldval = _oldctls[name]
     if val != oldval:
         rv = _sysctl_set(name, val)
         if rv == 0 and permanent:
-            debug1('>>   ...saving permanently in /etc/sysctl.conf\n')
+            debug1('>>   ...saving permanently in /etc/sysctl.conf')
             f = open('/etc/sysctl.conf', 'a')
             f.write('\n'
                     '# Added by sshuttle\n'
@@ -130,7 +98,7 @@ def sysctl_set(name, val, permanent=Fals
 
 def ipfw(*args):
     argv = ['ipfw', '-q'] + list(args)
-    debug1('>> %s\n' % ' '.join(argv))
+    debug1('>> %s' % ' '.join(argv))
     rv = ssubprocess.call(argv, env=get_env())
     # No env: No output. (Or error that won't be parsed.)
     if rv:
@@ -139,7 +107,7 @@ def ipfw(*args):
 
 def ipfw_noexit(*args):
     argv = ['ipfw', '-q'] + list(args)
-    debug1('>> %s\n' % ' '.join(argv))
+    debug1('>> %s' % ' '.join(argv))
     ssubprocess.call(argv, env=get_env())
     # No env: No output. (Or error that won't be parsed.)
 
@@ -161,7 +129,7 @@ class Method(BaseMethod):
         if not dstip:
             debug1(
                    "-- ignored UDP from %r: "
-                   "couldn't determine destination IP address\n" % (srcip,))
+                   "couldn't determine destination IP address" % (srcip,))
             return None
         return srcip, dstip, data
 
@@ -169,15 +137,14 @@ class Method(BaseMethod):
         if not srcip:
             debug1(
                "-- ignored UDP to %r: "
-               "couldn't determine source IP address\n" % (dstip,))
+               "couldn't determine source IP address" % (dstip,))
             return
 
-        # debug3('Sending SRC: %r DST: %r\n' % (srcip, dstip))
+        # debug3('Sending SRC: %r DST: %r' % (srcip, dstip))
         sender = socket.socket(sock.family, socket.SOCK_DGRAM)
         sender.setsockopt(socket.SOL_IP, IP_BINDANY, 1)
         sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
         sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
-        sender.setsockopt(socket.SOL_IP, socket.IP_TTL, 63)
         sender.bind(srcip)
         sender.sendto(data, dstip)
         sender.close()
@@ -189,7 +156,7 @@ class Method(BaseMethod):
         #     udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVDSTADDR, 1)
 
     def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
-                       user):
+                       user, tmark):
         # IPv6 not supported
         if family not in [socket.AF_INET]:
             raise Exception(
@@ -207,8 +174,7 @@ class Method(BaseMethod):
         if subnets or dnsport:
             sysctl_set('net.inet.ip.fw.enable', 1)
 
-        ipfw('add', '1', 'check-state', 'ip',
-             'from', 'any', 'to', 'any')
+        ipfw('add', '1', 'check-state', ':sshuttle')
 
         ipfw('add', '1', 'skipto', '2',
              'tcp',
@@ -216,7 +182,7 @@ class Method(BaseMethod):
         ipfw('add', '1', 'fwd', '127.0.0.1,%d' % port,
              'tcp',
              'from', 'any', 'to', 'table(126)',
-             'not', 'ipttl', '63', 'keep-state', 'setup')
+             'setup', 'keep-state', ':sshuttle')
 
         ipfw_noexit('table', '124', 'flush')
         dnscount = 0
@@ -227,26 +193,24 @@ class Method(BaseMethod):
             ipfw('add', '1', 'fwd', '127.0.0.1,%d' % dnsport,
                  'udp',
                  'from', 'any', 'to', 'table(124)',
-                 'not', 'ipttl', '63')
+                 'keep-state', ':sshuttle')
         ipfw('add', '1', 'allow',
              'udp',
-             'from', 'any', 'to', 'any',
-             'ipttl', '63')
+             'from', 'any', 'to', 'any')
 
         if subnets:
             # create new subnet entries
-            for _, swidth, sexclude, snet in sorted(subnets,
-                                                    key=lambda s: s[1],
-                                                    reverse=True):
+            for _, swidth, sexclude, snet, fport, lport \
+                    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))
+                else:
+                    ipfw('table', '126', 'add', '%s/%s' % (snet, swidth))
 
     def restore_firewall(self, port, family, udp, user):
         if family not in [socket.AF_INET]:
             raise Exception(
-                'Address family "%s" unsupported by tproxy method'
+                'Address family "%s" unsupported by ipfw method'
                 % family_to_string(family))
 
         ipfw_noexit('delete', '1')
@@ -258,5 +222,5 @@ class Method(BaseMethod):
         if which("ipfw"):
             return True
         debug2("ipfw method not supported because 'ipfw' command is "
-               "missing.\n")
+               "missing.")
         return False
diff -pruN 1.0.5-1/sshuttle/methods/nat.py 1.1.0-1/sshuttle/methods/nat.py
--- 1.0.5-1/sshuttle/methods/nat.py	2020-12-27 23:46:37.000000000 +0000
+++ 1.1.0-1/sshuttle/methods/nat.py	2022-01-27 22:07:24.000000000 +0000
@@ -1,7 +1,7 @@
 import socket
 from sshuttle.firewall import subnet_weight
 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
 
 
@@ -13,23 +13,18 @@ class Method(BaseMethod):
     # 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):
-        # only ipv4 supported with NAT
-        if family != socket.AF_INET:
+                       user, tmark):
+        if family != socket.AF_INET and family != socket.AF_INET6:
             raise Exception(
                 'Address family "%s" unsupported by nat method_name'
                 % family_to_string(family))
         if udp:
             raise Exception("UDP not supported by nat method_name")
-
         table = "nat"
 
         def _ipt(*args):
             return ipt(family, table, *args)
 
-        def _ipt_ttl(*args):
-            return ipt_ttl(family, table, *args)
-
         def _ipm(*args):
             return ipt(family, "mangle", *args)
 
@@ -50,16 +45,11 @@ class Method(BaseMethod):
         _ipt('-I', 'OUTPUT', '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', '63')
-
         # Redirect DNS traffic as requested. This includes routing traffic
         # to localhost DNS servers through sshuttle.
         for _, ip in [i for i in nslist if i[0] == family]:
             _ipt('-A', chain, '-j', 'REDIRECT',
-                 '--dest', '%s/32' % ip,
+                 '--dest', '%s' % ip,
                  '-p', 'udp',
                  '--dport', '53',
                  '--to-ports', str(dnsport))
@@ -87,7 +77,7 @@ class Method(BaseMethod):
 
     def restore_firewall(self, port, family, udp, user):
         # only ipv4 supported with NAT
-        if family != socket.AF_INET:
+        if family != socket.AF_INET and family != socket.AF_INET6:
             raise Exception(
                 'Address family "%s" unsupported by nat method_name'
                 % family_to_string(family))
@@ -99,9 +89,6 @@ class Method(BaseMethod):
         def _ipt(*args):
             return ipt(family, table, *args)
 
-        def _ipt_ttl(*args):
-            return ipt_ttl(family, table, *args)
-
         def _ipm(*args):
             return ipt(family, "mangle", *args)
 
@@ -123,11 +110,12 @@ class Method(BaseMethod):
     def get_supported_features(self):
         result = super(Method, self).get_supported_features()
         result.user = True
+        result.ipv6 = True
         return result
 
     def is_supported(self):
         if which("iptables"):
             return True
         debug2("nat method not supported because 'iptables' command "
-               "is missing.\n")
+               "is missing.")
         return False
diff -pruN 1.0.5-1/sshuttle/methods/nft.py 1.1.0-1/sshuttle/methods/nft.py
--- 1.0.5-1/sshuttle/methods/nft.py	2020-12-27 23:46:37.000000000 +0000
+++ 1.1.0-1/sshuttle/methods/nft.py	2022-01-27 22:07:24.000000000 +0000
@@ -13,7 +13,7 @@ class Method(BaseMethod):
     # 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):
+                       user, tmark):
         if udp:
             raise Exception("UDP not supported by nft")
 
@@ -45,14 +45,6 @@ class Method(BaseMethod):
         else:
             _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
         if family == socket.AF_INET:
             ip_version_l = 'ipv4'
@@ -118,5 +110,5 @@ class Method(BaseMethod):
     def is_supported(self):
         if which("nft"):
             return True
-        debug2("nft method not supported because 'nft' command is missing.\n")
+        debug2("nft method not supported because 'nft' command is missing.")
         return False
diff -pruN 1.0.5-1/sshuttle/methods/pf.py 1.1.0-1/sshuttle/methods/pf.py
--- 1.0.5-1/sshuttle/methods/pf.py	2020-12-27 23:46:37.000000000 +0000
+++ 1.1.0-1/sshuttle/methods/pf.py	2022-01-27 22:07:24.000000000 +0000
@@ -11,8 +11,8 @@ from fcntl import ioctl
 from ctypes import c_char, c_uint8, c_uint16, c_uint32, Union, Structure, \
     sizeof, addressof, memmove
 from sshuttle.firewall import subnet_weight
-from sshuttle.helpers import debug1, debug2, debug3, Fatal, family_to_string, \
-    get_env, which
+from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, \
+     family_to_string, get_env, which
 from sshuttle.methods import BaseMethod
 
 
@@ -273,7 +273,7 @@ class OpenBsd(Generic):
     def add_anchors(self, anchor):
         # before adding anchors and rules we must override the skip lo
         # that comes by default in openbsd pf.conf so the rules we will add,
-        # which rely on translating/filtering  packets on lo, can work
+        # which rely on translating/filtering packets on lo, can work
         if self.has_skip_loopback():
             pfctl('-f /dev/stdin', b'match on lo\n')
         super(OpenBsd, self).add_anchors(anchor)
@@ -353,7 +353,7 @@ class Darwin(FreeBsd):
     def add_anchors(self, anchor):
         # before adding anchors and rules we must override the skip lo
         # that in some cases ends up in the chain so the rules we will add,
-        # which rely on translating/filtering  packets on lo, can work
+        # which rely on translating/filtering packets on lo, can work
         if self.has_skip_loopback():
             pfctl('-f /dev/stdin', b'pass on lo\n')
         super(Darwin, self).add_anchors(anchor)
@@ -386,13 +386,17 @@ else:
 
 def pfctl(args, stdin=None):
     argv = ['pfctl'] + shlex.split(args)
-    debug1('>> %s\n' % ' '.join(argv))
+    debug1('>> %s' % ' '.join(argv))
     p = ssubprocess.Popen(argv, stdin=ssubprocess.PIPE,
                           stdout=ssubprocess.PIPE,
                           stderr=ssubprocess.PIPE,
                           env=get_env())
     o = p.communicate(stdin)
     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))
 
     return o
@@ -444,7 +448,7 @@ class Method(BaseMethod):
         return sock.getsockname()
 
     def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
-                       user):
+                       user, tmark):
         if family not in [socket.AF_INET, socket.AF_INET6]:
             raise Exception(
                 'Address family "%s" unsupported by pf method_name'
@@ -495,5 +499,5 @@ class Method(BaseMethod):
     def is_supported(self):
         if which("pfctl"):
             return True
-        debug2("pf method not supported because 'pfctl' command is missing.\n")
+        debug2("pf method not supported because 'pfctl' command is missing.")
         return False
diff -pruN 1.0.5-1/sshuttle/methods/tproxy.py 1.1.0-1/sshuttle/methods/tproxy.py
--- 1.0.5-1/sshuttle/methods/tproxy.py	2020-12-27 23:46:37.000000000 +0000
+++ 1.1.0-1/sshuttle/methods/tproxy.py	2022-01-27 22:07:24.000000000 +0000
@@ -1,26 +1,12 @@
 import struct
 from sshuttle.firewall import subnet_weight
 from sshuttle.helpers import family_to_string
-from sshuttle.linux import ipt, ipt_ttl, ipt_chain_exists
+from sshuttle.linux import ipt, ipt_chain_exists
 from sshuttle.methods import BaseMethod
 from sshuttle.helpers import debug1, debug2, debug3, Fatal, which
 
-recvmsg = None
-try:
-    # try getting recvmsg from python
-    import socket as pythonsocket
-    getattr(pythonsocket.socket, "recvmsg")
-    socket = pythonsocket
-    recvmsg = "python"
-except AttributeError:
-    # try getting recvmsg from socket_ext library
-    try:
-        import socket_ext
-        getattr(socket_ext.socket, "recvmsg")
-        socket = socket_ext
-        recvmsg = "socket_ext"
-    except ImportError:
-        import socket
+import socket
+import os
 
 
 IP_TRANSPARENT = 19
@@ -30,75 +16,37 @@ SOL_IPV6 = 41
 IPV6_ORIGDSTADDR = 74
 IPV6_RECVORIGDSTADDR = IPV6_ORIGDSTADDR
 
-if recvmsg == "python":
-    def recv_udp(listener, bufsize):
-        debug3('Accept UDP python using recvmsg.\n')
-        data, ancdata, _, srcip = listener.recvmsg(
-            4096, socket.CMSG_SPACE(24))
-        dstip = None
-        family = None
-        for cmsg_level, cmsg_type, cmsg_data in ancdata:
-            if cmsg_level == socket.SOL_IP and cmsg_type == IP_ORIGDSTADDR:
-                family, port = struct.unpack('=HH', cmsg_data[0:4])
-                port = socket.htons(port)
-                if family == socket.AF_INET:
-                    start = 4
-                    length = 4
-                else:
-                    raise Fatal("Unsupported socket type '%s'" % family)
-                ip = socket.inet_ntop(family, cmsg_data[start:start + length])
-                dstip = (ip, port)
-                break
-            elif cmsg_level == SOL_IPV6 and cmsg_type == IPV6_ORIGDSTADDR:
-                family, port = struct.unpack('=HH', cmsg_data[0:4])
-                port = socket.htons(port)
-                if family == socket.AF_INET6:
-                    start = 8
-                    length = 16
-                else:
-                    raise Fatal("Unsupported socket type '%s'" % family)
-                ip = socket.inet_ntop(family, cmsg_data[start:start + length])
-                dstip = (ip, port)
-                break
-        return (srcip, dstip, data)
-elif recvmsg == "socket_ext":
-    def recv_udp(listener, bufsize):
-        debug3('Accept UDP using socket_ext recvmsg.\n')
-        srcip, data, adata, _ = listener.recvmsg(
-            (bufsize,), socket.CMSG_SPACE(24))
-        dstip = None
-        family = None
-        for a in adata:
-            if a.cmsg_level == socket.SOL_IP and a.cmsg_type == IP_ORIGDSTADDR:
-                family, port = struct.unpack('=HH', a.cmsg_data[0:4])
-                port = socket.htons(port)
-                if family == socket.AF_INET:
-                    start = 4
-                    length = 4
-                else:
-                    raise Fatal("Unsupported socket type '%s'" % family)
-                ip = socket.inet_ntop(
-                    family, a.cmsg_data[start:start + length])
-                dstip = (ip, port)
-                break
-            elif a.cmsg_level == SOL_IPV6 and a.cmsg_type == IPV6_ORIGDSTADDR:
-                family, port = struct.unpack('=HH', a.cmsg_data[0:4])
-                port = socket.htons(port)
-                if family == socket.AF_INET6:
-                    start = 8
-                    length = 16
-                else:
-                    raise Fatal("Unsupported socket type '%s'" % family)
-                ip = socket.inet_ntop(
-                    family, a.cmsg_data[start:start + length])
-                dstip = (ip, port)
-                break
-        return (srcip, dstip, data[0])
-else:
-    def recv_udp(listener, bufsize):
-        debug3('Accept UDP using recvfrom.\n')
-        data, srcip = listener.recvfrom(bufsize)
-        return (srcip, None, data)
+
+def recv_udp(listener, bufsize):
+    debug3('Accept UDP python using recvmsg.')
+    data, ancdata, _, srcip = listener.recvmsg(
+        4096, socket.CMSG_SPACE(24))
+    dstip = None
+    family = None
+    for cmsg_level, cmsg_type, cmsg_data in ancdata:
+        if cmsg_level == socket.SOL_IP and cmsg_type == IP_ORIGDSTADDR:
+            family, port = struct.unpack('=HH', cmsg_data[0:4])
+            port = socket.htons(port)
+            if family == socket.AF_INET:
+                start = 4
+                length = 4
+            else:
+                raise Fatal("Unsupported socket type '%s'" % family)
+            ip = socket.inet_ntop(family, cmsg_data[start:start + length])
+            dstip = (ip, port)
+            break
+        elif cmsg_level == SOL_IPV6 and cmsg_type == IPV6_ORIGDSTADDR:
+            family, port = struct.unpack('=HH', cmsg_data[0:4])
+            port = socket.htons(port)
+            if family == socket.AF_INET6:
+                start = 8
+                length = 16
+            else:
+                raise Fatal("Unsupported socket type '%s'" % family)
+            ip = socket.inet_ntop(family, cmsg_data[start:start + length])
+            dstip = (ip, port)
+            break
+    return (srcip, dstip, data)
 
 
 class Method(BaseMethod):
@@ -106,12 +54,8 @@ class Method(BaseMethod):
     def get_supported_features(self):
         result = super(Method, self).get_supported_features()
         result.ipv6 = True
-        if recvmsg is None:
-            result.udp = False
-            result.dns = False
-        else:
-            result.udp = True
-            result.dns = True
+        result.udp = True
+        result.dns = True
         return result
 
     def get_tcp_dstip(self, sock):
@@ -126,6 +70,15 @@ class Method(BaseMethod):
             return None
         return srcip, dstip, data
 
+    def setsockopt_error(self, e):
+        """The tproxy method needs root permissions to successfully
+        set the IP_TRANSPARENT option on sockets. This method is
+        called when we receive a PermissionError when trying to do
+        so."""
+        raise Fatal("Insufficient permissions for tproxy method.\n"
+                    "Your effective UID is %d, not 0. Try rerunning as root.\n"
+                    % os.geteuid())
+
     def send_udp(self, sock, srcip, dstip, data):
         if not srcip:
             debug1(
@@ -134,16 +87,26 @@ class Method(BaseMethod):
             return
         sender = socket.socket(sock.family, socket.SOCK_DGRAM)
         sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-        sender.setsockopt(socket.SOL_IP, IP_TRANSPARENT, 1)
+        try:
+            sender.setsockopt(socket.SOL_IP, IP_TRANSPARENT, 1)
+        except PermissionError as e:
+            self.setsockopt_error(e)
         sender.bind(srcip)
         sender.sendto(data, dstip)
         sender.close()
 
     def setup_tcp_listener(self, tcp_listener):
-        tcp_listener.setsockopt(socket.SOL_IP, IP_TRANSPARENT, 1)
+        try:
+            tcp_listener.setsockopt(socket.SOL_IP, IP_TRANSPARENT, 1)
+        except PermissionError as e:
+            self.setsockopt_error(e)
 
     def setup_udp_listener(self, udp_listener):
-        udp_listener.setsockopt(socket.SOL_IP, IP_TRANSPARENT, 1)
+        try:
+            udp_listener.setsockopt(socket.SOL_IP, IP_TRANSPARENT, 1)
+        except PermissionError as e:
+            self.setsockopt_error(e)
+
         if udp_listener.v4 is not None:
             udp_listener.v4.setsockopt(
                 socket.SOL_IP, IP_RECVORIGDSTADDR, 1)
@@ -151,17 +114,7 @@ class Method(BaseMethod):
             udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
 
     def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
-                       user):
-        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):
+                       user, tmark):
         if family not in [socket.AF_INET, socket.AF_INET6]:
             raise Exception(
                 'Address family "%s" unsupported by tproxy method'
@@ -172,9 +125,6 @@ class Method(BaseMethod):
         def _ipt(*args):
             return ipt(family, table, *args)
 
-        def _ipt_ttl(*args):
-            return ipt_ttl(family, table, *args)
-
         def _ipt_proto_ports(proto, fport, lport):
             return proto + ('--dport', '%d:%d' % (fport, lport)) \
                     if fport else proto
@@ -192,8 +142,24 @@ class Method(BaseMethod):
         _ipt('-F', divert_chain)
         _ipt('-N', tproxy_chain)
         _ipt('-F', tproxy_chain)
-        _ipt('-I', 'OUTPUT', tmark, '-j', mark_chain)
-        _ipt('-I', 'PREROUTING', tmark, '-j', tproxy_chain)
+        _ipt('-I', 'OUTPUT', '1', '-j', mark_chain)
+        _ipt('-I', 'PREROUTING', '1', '-j', tproxy_chain)
+
+        # Don't have packets sent to any of our local IP addresses go
+        # through the tproxy or mark chains.
+        #
+        # Without this fix, if a large subnet is redirected through
+        # sshuttle (i.e., 0/0), then the user may be unable to receive
+        # UDP responses or connect to their own machine using an IP
+        # besides (127.0.0.1). Prior to including these lines, the
+        # documentation reminded the user to use -x to exclude their
+        # own IP addresses to receive UDP responses if they are
+        # redirecting a large subnet through sshuttle (i.e., 0/0).
+        _ipt('-A', tproxy_chain, '-j', 'RETURN', '-m', 'addrtype',
+             '--dst-type', 'LOCAL')
+        _ipt('-A', mark_chain, '-j', 'RETURN', '-m', 'addrtype',
+             '--dst-type', 'LOCAL')
+
         _ipt('-A', divert_chain, '-j', 'MARK', '--set-mark', tmark)
         _ipt('-A', divert_chain, '-j', 'ACCEPT')
         _ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain,
@@ -208,7 +174,7 @@ class Method(BaseMethod):
                  '--dest', '%s/32' % ip,
                  '-m', 'udp', '-p', 'udp', '--dport', '53')
             _ipt('-A', tproxy_chain, '-j', 'TPROXY',
-                 '--tproxy-mark', '0x'+tmark+'/0x'+tmark,
+                 '--tproxy-mark', tmark,
                  '--dest', '%s/32' % ip,
                  '-m', 'udp', '-p', 'udp', '--dport', '53',
                  '--on-port', str(dnsport))
@@ -233,7 +199,7 @@ class Method(BaseMethod):
                      '-m', 'tcp',
                      *tcp_ports)
                 _ipt('-A', tproxy_chain, '-j', 'TPROXY',
-                     '--tproxy-mark', '0x'+tmark+'/0x'+tmark,
+                     '--tproxy-mark', tmark,
                      '--dest', '%s/%s' % (snet, swidth),
                      '-m', 'tcp',
                      *(tcp_ports + ('--on-port', str(port))))
@@ -257,7 +223,7 @@ class Method(BaseMethod):
                          '-m', 'udp',
                          *udp_ports)
                     _ipt('-A', tproxy_chain, '-j', 'TPROXY',
-                         '--tproxy-mark', '0x'+tmark+'/0x'+tmark,
+                         '--tproxy-mark', tmark,
                          '--dest', '%s/%s' % (snet, swidth),
                          '-m', 'udp',
                          *(udp_ports + ('--on-port', str(port))))
@@ -273,9 +239,6 @@ class Method(BaseMethod):
         def _ipt(*args):
             return ipt(family, table, *args)
 
-        def _ipt_ttl(*args):
-            return ipt_ttl(family, table, *args)
-
         mark_chain = 'sshuttle-m-%s' % port
         tproxy_chain = 'sshuttle-t-%s' % port
         divert_chain = 'sshuttle-d-%s' % port
diff -pruN 1.0.5-1/sshuttle/options.py 1.1.0-1/sshuttle/options.py
--- 1.0.5-1/sshuttle/options.py	2020-12-27 23:46:37.000000000 +0000
+++ 1.1.0-1/sshuttle/options.py	2022-01-27 22:07:24.000000000 +0000
@@ -132,6 +132,7 @@ def parse_ipport(s):
 
 
 def parse_list(lst):
+    """Parse a comma separated string into a list."""
     return re.split(r'[\s,]+', lst.strip()) if lst else []
 
 
@@ -146,9 +147,33 @@ class Concat(Action):
         setattr(namespace, self.dest, curr_value + values)
 
 
-parser = ArgumentParser(
+# Override one function in the ArgumentParser so that we can have
+# better control for how we parse files containing arguments. We
+# expect one argument per line, but strip whitespace/quotes from the
+# beginning/end of the lines.
+class MyArgumentParser(ArgumentParser):
+    def convert_arg_line_to_args(self, arg_line):
+        # Ignore comments
+        if arg_line.startswith("#"):
+            return []
+
+        # strip whitespace at beginning and end of line
+        arg_line = arg_line.strip()
+
+        # When copying parameters from the command line to a file,
+        # some users might copy the quotes they used on the command
+        # line into the config file. We ignore these if the line
+        # starts and ends with the same quote.
+        if arg_line.startswith("'") and arg_line.endswith("'") or \
+           arg_line.startswith('"') and arg_line.endswith('"'):
+            arg_line = arg_line[1:-1]
+
+        return [arg_line]
+
+
+parser = MyArgumentParser(
     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(
@@ -196,6 +221,7 @@ parser.add_argument(
     type=parse_list,
     help="""
     capture and forward DNS requests made to the following servers
+    (comma separated)
     """
 )
 parser.add_argument(
@@ -256,7 +282,7 @@ parser.add_argument(
     action="count",
     default=0,
     help="""
-    increase debug message verbosity
+    increase debug message verbosity (can be used more than once)
     """
 )
 parser.add_argument(
@@ -412,8 +438,9 @@ parser.add_argument(
 parser.add_argument(
     "-t", "--tmark",
     metavar="[MARK]",
-    default="1",
+    default="0x01",
     help="""
-    transproxy optional traffic mark with provided MARK value
+    tproxy optional traffic mark with provided MARK value in
+    hexadecimal (default '0x01')
     """
 )
diff -pruN 1.0.5-1/sshuttle/sdnotify.py 1.1.0-1/sshuttle/sdnotify.py
--- 1.0.5-1/sshuttle/sdnotify.py	2020-12-16 09:08:57.000000000 +0000
+++ 1.1.0-1/sshuttle/sdnotify.py	2022-01-27 22:07:24.000000000 +0000
@@ -26,7 +26,7 @@ def _notify(message):
     try:
         sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
     except (OSError, IOError) as e:
-        debug1("Error creating socket to notify systemd: %s\n" % e)
+        debug1("Error creating socket to notify systemd: %s" % e)
         return False
 
     if not message:
@@ -37,7 +37,7 @@ def _notify(message):
     try:
         return (sock.sendto(message, addr) > 0)
     except (OSError, IOError) as e:
-        debug1("Error notifying systemd: %s\n" % e)
+        debug1("Error notifying systemd: %s" % e)
         return False
 
 
diff -pruN 1.0.5-1/sshuttle/server.py 1.1.0-1/sshuttle/server.py
--- 1.0.5-1/sshuttle/server.py	2020-12-16 09:08:57.000000000 +0000
+++ 1.1.0-1/sshuttle/server.py	2022-01-27 22:07:24.000000000 +0000
@@ -5,7 +5,6 @@ import traceback
 import time
 import sys
 import os
-import platform
 
 
 import sshuttle.ssnet as ssnet
@@ -96,7 +95,7 @@ def _list_routes(argv, extract_route):
             (socket.AF_INET, socket.inet_ntoa(struct.pack('!I', ip)), width))
     rv = p.wait()
     if rv != 0:
-        log('WARNING: %r returned %d\n' % (argv, rv))
+        log('WARNING: %r returned %d' % (argv, rv))
 
     return routes
 
@@ -108,7 +107,7 @@ def list_routes():
         routes = _list_routes(['netstat', '-rn'], _route_netstat)
     else:
         log('WARNING: Neither "ip" nor "netstat" were found on the server. '
-            '--auto-nets feature will not work.\n')
+            '--auto-nets feature will not work.')
         routes = []
 
     for (family, ip, width) in routes:
@@ -135,7 +134,7 @@ def start_hostwatch(seed_hosts, auto_hos
                 s1.close()
                 rv = hostwatch.hw_main(seed_hosts, auto_hosts) or 0
             except Exception:
-                log('%s\n' % _exc_dump())
+                log('%s' % _exc_dump())
                 rv = 98
         finally:
             os._exit(rv)
@@ -191,12 +190,11 @@ class DnsProxy(Handler):
 
         family, sockaddr = self._addrinfo(peer, port)
         sock = socket.socket(family, socket.SOCK_DGRAM)
-        sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 63)
         sock.connect(sockaddr)
 
         self.peers[sock] = peer
 
-        debug2('DNS: sending to %r:%d (try %d)\n' % (peer, port, self.tries))
+        debug2('DNS: sending to %r:%d (try %d)' % (peer, port, self.tries))
         try:
             sock.send(self.request)
             self.socks.append(sock)
@@ -206,11 +204,11 @@ class DnsProxy(Handler):
                 # might have been spurious; try again.
                 # Note: these errors sometimes are reported by recv(),
                 # and sometimes by send().  We have to catch both.
-                debug2('DNS send to %r: %s\n' % (peer, e))
+                debug2('DNS send to %r: %s' % (peer, e))
                 self.try_send()
                 return
             else:
-                log('DNS send to %r: %s\n' % (peer, e))
+                log('DNS send to %r: %s' % (peer, e))
                 return
 
     def callback(self, sock):
@@ -227,13 +225,13 @@ class DnsProxy(Handler):
                 # might have been spurious; try again.
                 # Note: these errors sometimes are reported by recv(),
                 # and sometimes by send().  We have to catch both.
-                debug2('DNS recv from %r: %s\n' % (peer, e))
+                debug2('DNS recv from %r: %s' % (peer, e))
                 self.try_send()
                 return
             else:
-                log('DNS recv from %r: %s\n' % (peer, e))
+                log('DNS recv from %r: %s' % (peer, e))
                 return
-        debug2('DNS response: %d bytes\n' % len(data))
+        debug2('DNS response: %d bytes' % len(data))
         self.mux.send(self.chan, ssnet.CMD_DNS_RESPONSE, data)
         self.ok = False
 
@@ -247,16 +245,14 @@ class UdpProxy(Handler):
         self.mux = mux
         self.chan = chan
         self.sock = sock
-        if family == socket.AF_INET:
-            self.sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 63)
 
     def send(self, dstip, data):
-        debug2(' s: UDP: sending to %r port %d\n' % dstip)
+        debug2('UDP: sending to %r port %d' % dstip)
         try:
             self.sock.sendto(data, dstip)
         except socket.error:
             _, e = sys.exc_info()[:2]
-            log(' s: UDP send to %r port %d: %s\n' % (dstip[0], dstip[1], e))
+            log('UDP send to %r port %d: %s' % (dstip[0], dstip[1], e))
             return
 
     def callback(self, sock):
@@ -264,147 +260,157 @@ class UdpProxy(Handler):
             data, peer = sock.recvfrom(4096)
         except socket.error:
             _, e = sys.exc_info()[:2]
-            log(' s: UDP recv from %r port %d: %s\n' % (peer[0], peer[1], e))
+            log('UDP recv from %r port %d: %s' % (peer[0], peer[1], e))
             return
-        debug2(' s: UDP response: %d bytes\n' % len(data))
+        debug2('UDP response: %d bytes' % len(data))
         hdr = b("%s,%r," % (peer[0], peer[1]))
         self.mux.send(self.chan, ssnet.CMD_UDP_DATA, hdr + data)
 
 
-def main(latency_control, auto_hosts, to_nameserver, auto_nets):
-    debug1(' s: Starting server with Python version %s\n'
-           % platform.python_version())
-
-    helpers.logprefix = ' s: '
-    debug1('latency control setting = %r\n' % latency_control)
-
-    # synchronization header
-    sys.stdout.write('\0\0SSHUTTLE0001')
-    sys.stdout.flush()
-
-    handlers = []
-    mux = Mux(sys.stdin, sys.stdout)
-    handlers.append(mux)
-
-    debug1('auto-nets:' + str(auto_nets) + '\n')
-    if auto_nets:
-        routes = list(list_routes())
-        debug1('available routes:\n')
+def main(latency_control, latency_buffer_size, auto_hosts, to_nameserver,
+         auto_nets):
+    try:
+        helpers.logprefix = ' s: '
+
+        debug1('latency control setting = %r' % latency_control)
+        if latency_buffer_size:
+            import sshuttle.ssnet as ssnet
+            ssnet.LATENCY_BUFFER_SIZE = latency_buffer_size
+
+        # synchronization header
+        sys.stdout.write('\0\0SSHUTTLE0001')
+        sys.stdout.flush()
+
+        handlers = []
+        mux = Mux(sys.stdin, sys.stdout)
+        handlers.append(mux)
+
+        debug1('auto-nets:' + str(auto_nets))
+        if auto_nets:
+            routes = list(list_routes())
+            debug1('available routes:')
+            for r in routes:
+                debug1('  %d/%s/%d' % r)
+        else:
+            routes = []
+
+        routepkt = ''
         for r in routes:
-            debug1('  %d/%s/%d\n' % r)
-    else:
-        routes = []
+            routepkt += '%d,%s,%d\n' % r
+        mux.send(0, ssnet.CMD_ROUTES, b(routepkt))
+
+        hw = Hostwatch()
+        hw.leftover = b('')
 
-    routepkt = ''
-    for r in routes:
-        routepkt += '%d,%s,%d\n' % r
-    mux.send(0, ssnet.CMD_ROUTES, b(routepkt))
-
-    hw = Hostwatch()
-    hw.leftover = b('')
-
-    def hostwatch_ready(sock):
-        assert(hw.pid)
-        content = hw.sock.recv(4096)
-        if content:
-            lines = (hw.leftover + content).split(b('\n'))
-            if lines[-1]:
-                # no terminating newline: entry isn't complete yet!
-                hw.leftover = lines.pop()
-                lines.append(b(''))
+        def hostwatch_ready(sock):
+            assert(hw.pid)
+            content = hw.sock.recv(4096)
+            if content:
+                lines = (hw.leftover + content).split(b('\n'))
+                if lines[-1]:
+                    # no terminating newline: entry isn't complete yet!
+                    hw.leftover = lines.pop()
+                    lines.append(b(''))
+                else:
+                    hw.leftover = b('')
+                mux.send(0, ssnet.CMD_HOST_LIST, b('\n').join(lines))
             else:
-                hw.leftover = b('')
-            mux.send(0, ssnet.CMD_HOST_LIST, b('\n').join(lines))
-        else:
-            raise Fatal(' s: hostwatch process died')
+                raise Fatal('hostwatch process died')
 
-    def got_host_req(data):
-        if not hw.pid:
-            (hw.pid, hw.sock) = start_hostwatch(
-                    data.decode("ASCII").strip().split(), auto_hosts)
-            handlers.append(Handler(socks=[hw.sock],
-                                    callback=hostwatch_ready))
-    mux.got_host_req = got_host_req
-
-    def new_channel(channel, data):
-        (family, dstip, dstport) = data.decode("ASCII").split(',', 2)
-        family = int(family)
-        # AF_INET is the same constant on Linux and BSD but AF_INET6
-        # is different. As the client and server can be running on
-        # different platforms we can not just set the socket family
-        # to what comes in the wire.
-        if family != socket.AF_INET:
-            family = socket.AF_INET6
-        dstport = int(dstport)
-        outwrap = ssnet.connect_dst(family, dstip, dstport)
-        handlers.append(Proxy(MuxWrapper(mux, channel), outwrap))
-    mux.new_channel = new_channel
-
-    dnshandlers = {}
-
-    def dns_req(channel, data):
-        debug2('Incoming DNS request channel=%d.\n' % channel)
-        h = DnsProxy(mux, channel, data, to_nameserver)
-        handlers.append(h)
-        dnshandlers[channel] = h
-    mux.got_dns_req = dns_req
-
-    udphandlers = {}
-
-    def udp_req(channel, cmd, data):
-        debug2('Incoming UDP request channel=%d, cmd=%d\n' % (channel, cmd))
-        if cmd == ssnet.CMD_UDP_DATA:
-            (dstip, dstport, data) = data.split(b(','), 2)
+        def got_host_req(data):
+            if not hw.pid:
+                (hw.pid, hw.sock) = start_hostwatch(
+                        data.decode("ASCII").strip().split(), auto_hosts)
+                handlers.append(Handler(socks=[hw.sock],
+                                        callback=hostwatch_ready))
+        mux.got_host_req = got_host_req
+
+        def new_channel(channel, data):
+            (family, dstip, dstport) = data.decode("ASCII").split(',', 2)
+            family = int(family)
+            # AF_INET is the same constant on Linux and BSD but AF_INET6
+            # is different. As the client and server can be running on
+            # different platforms we can not just set the socket family
+            # to what comes in the wire.
+            if family != socket.AF_INET:
+                family = socket.AF_INET6
             dstport = int(dstport)
-            debug2('is incoming UDP data. %r %d.\n' % (dstip, dstport))
-            h = udphandlers[channel]
-            h.send((dstip, dstport), data)
-        elif cmd == ssnet.CMD_UDP_CLOSE:
-            debug2('is incoming UDP close\n')
-            h = udphandlers[channel]
-            h.ok = False
-            del mux.channels[channel]
-
-    def udp_open(channel, data):
-        debug2('Incoming UDP open.\n')
-        family = int(data)
-        mux.channels[channel] = lambda cmd, data: udp_req(channel, cmd, data)
-        if channel in udphandlers:
-            raise Fatal(' s: UDP connection channel %d already open' % channel)
-        else:
-            h = UdpProxy(mux, channel, family)
+            outwrap = ssnet.connect_dst(family, dstip, dstport)
+            handlers.append(Proxy(MuxWrapper(mux, channel), outwrap))
+        mux.new_channel = new_channel
+
+        dnshandlers = {}
+
+        def dns_req(channel, data):
+            debug2('Incoming DNS request channel=%d.' % channel)
+            h = DnsProxy(mux, channel, data, to_nameserver)
             handlers.append(h)
-            udphandlers[channel] = h
-    mux.got_udp_open = udp_open
+            dnshandlers[channel] = h
+        mux.got_dns_req = dns_req
 
-    while mux.ok:
-        if hw.pid:
-            assert(hw.pid > 0)
-            (rpid, rv) = os.waitpid(hw.pid, os.WNOHANG)
-            if rpid:
-                raise Fatal(
-                    'hostwatch exited unexpectedly: code 0x%04x\n' % rv)
-
-        ssnet.runonce(handlers, mux)
-        if latency_control:
-            mux.check_fullness()
-
-        if dnshandlers:
-            now = time.time()
-            remove = []
-            for channel, h in dnshandlers.items():
-                if h.timeout < now or not h.ok:
-                    debug3('expiring dnsreqs channel=%d\n' % channel)
-                    remove.append(channel)
-                    h.ok = False
-            for channel in remove:
-                del dnshandlers[channel]
-        if udphandlers:
-            remove = []
-            for channel, h in udphandlers.items():
-                if not h.ok:
-                    debug3('expiring UDP channel=%d\n' % channel)
-                    remove.append(channel)
-                    h.ok = False
-            for channel in remove:
-                del udphandlers[channel]
+        udphandlers = {}
+
+        def udp_req(channel, cmd, data):
+            debug2('Incoming UDP request channel=%d, cmd=%d' %
+                   (channel, cmd))
+            if cmd == ssnet.CMD_UDP_DATA:
+                (dstip, dstport, data) = data.split(b(','), 2)
+                dstport = int(dstport)
+                debug2('is incoming UDP data. %r %d.' % (dstip, dstport))
+                h = udphandlers[channel]
+                h.send((dstip, dstport), data)
+            elif cmd == ssnet.CMD_UDP_CLOSE:
+                debug2('is incoming UDP close')
+                h = udphandlers[channel]
+                h.ok = False
+                del mux.channels[channel]
+
+        def udp_open(channel, data):
+            debug2('Incoming UDP open.')
+            family = int(data)
+            mux.channels[channel] = lambda cmd, data: udp_req(channel, cmd,
+                                                              data)
+            if channel in udphandlers:
+                raise Fatal('UDP connection channel %d already open' %
+                            channel)
+            else:
+                h = UdpProxy(mux, channel, family)
+                handlers.append(h)
+                udphandlers[channel] = h
+        mux.got_udp_open = udp_open
+
+        while mux.ok:
+            if hw.pid:
+                assert(hw.pid > 0)
+                (rpid, rv) = os.waitpid(hw.pid, os.WNOHANG)
+                if rpid:
+                    raise Fatal(
+                        'hostwatch exited unexpectedly: code 0x%04x' % rv)
+
+            ssnet.runonce(handlers, mux)
+            if latency_control:
+                mux.check_fullness()
+
+            if dnshandlers:
+                now = time.time()
+                remove = []
+                for channel, h in dnshandlers.items():
+                    if h.timeout < now or not h.ok:
+                        debug3('expiring dnsreqs channel=%d' % channel)
+                        remove.append(channel)
+                        h.ok = False
+                for channel in remove:
+                    del dnshandlers[channel]
+            if udphandlers:
+                remove = []
+                for channel, h in udphandlers.items():
+                    if not h.ok:
+                        debug3('expiring UDP channel=%d' % channel)
+                        remove.append(channel)
+                        h.ok = False
+                for channel in remove:
+                    del udphandlers[channel]
+
+    except Fatal as e:
+        log('fatal: %s' % e)
+        sys.exit(99)
diff -pruN 1.0.5-1/sshuttle/ssh.py 1.1.0-1/sshuttle/ssh.py
--- 1.0.5-1/sshuttle/ssh.py	2020-12-16 09:08:57.000000000 +0000
+++ 1.1.0-1/sshuttle/ssh.py	2022-01-27 22:07:24.000000000 +0000
@@ -61,11 +61,11 @@ def parse_hostport(rhostport):
     if ":" in host:
         # IPv6 address and/or got a port specified
 
-        # If it is an IPv6 adress with port specification,
+        # If it is an IPv6 address with port specification,
         # then it will look like: [::1]:22
 
         try:
-            # try to parse host as an IP adress,
+            # try to parse host as an IP address,
             # if that works it is an IPv6 address
             host = str(ipaddress.ip_address(host))
         except ValueError:
@@ -103,11 +103,21 @@ def connect(ssh_cmd, rhostport, python,
                 empackage(z, 'sshuttle.server') +
                 b"\n")
 
+    # If the exec() program calls sys.exit(), it should exit python
+    # and the sys.exit(98) call won't be reached (so we try to only
+    # exit that way in the server). However, if the code that we
+    # exec() simply returns from main, then we will return from
+    # exec(). If the server's python process dies, it should stop
+    # executing and also won't reach sys.exit(98).
+    #
+    # So, we shouldn't reach sys.exit(98) and we certainly shouldn't
+    # reach it immediately after trying to start the server.
     pyscript = r"""
                 import sys, os;
                 verbosity=%d;
                 sys.stdin = os.fdopen(0, "rb");
-                exec(compile(sys.stdin.read(%d), "assembler.py", "exec"))
+                exec(compile(sys.stdin.read(%d), "assembler.py", "exec"));
+                sys.exit(98);
                 """ % (helpers.verbose or 0, len(content))
     pyscript = re.sub(r'\s+', ' ', pyscript.strip())
 
@@ -127,8 +137,47 @@ def connect(ssh_cmd, rhostport, python,
         if python:
             pycmd = "'%s' -c '%s'" % (python, pyscript)
         else:
+            # By default, we run the following code in a shell.
+            # However, with restricted shells and other unusual
+            # situations, there can be trouble. See the RESTRICTED
+            # SHELL section in "man bash" for more information. The
+            # code makes many assumptions:
+            #
+            # (1) That /bin/sh exists and that we can call it.
+            # Restricted shells often do *not* allow you to run
+            # programs specified with an absolute path like /bin/sh.
+            # Either way, if there is trouble with this, it should
+            # return error code 127.
+            #
+            # (2) python3 or python exists in the PATH and is
+            # executable. If they aren't, then exec won't work (see (4)
+            # below).
+            #
+            # (3) In /bin/sh, that we can redirect stderr in order to
+            # hide the version that "python3 -V" might print (some
+            # restricted shells don't allow redirection, see
+            # RESTRICTED SHELL section in 'man bash'). However, if we
+            # are in a restricted shell, we'd likely have trouble with
+            # assumption (1) above.
+            #
+            # (4) The 'exec' command should work except if we failed
+            # to exec python because it doesn't exist or isn't
+            # executable OR if exec isn't allowed (some restricted
+            # shells don't allow exec). If the exec succeeded, it will
+            # not return and not get to the "exit 97" command. If exec
+            # does return, we exit with code 97.
+            #
+            # Specifying the exact python program to run with --python
+            # avoids many of the issues above. However, if
+            # you have a restricted shell on remote, you may only be
+            # able to run python if it is in your PATH (and you can't
+            # run programs specified with an absolute path). In that
+            # case, sshuttle might not work at all since it is not
+            # possible to run python on the remote machine---even if
+            # it is present.
             pycmd = ("P=python3; $P -V 2>%s || P=python; "
-                     "exec \"$P\" -c %s") % (os.devnull, quote(pyscript))
+                     "exec \"$P\" -c %s; exit 97") % \
+                     (os.devnull, quote(pyscript))
             pycmd = ("/bin/sh -c {}".format(quote(pycmd)))
 
         if password is not None:
@@ -160,7 +209,7 @@ def connect(ssh_cmd, rhostport, python,
     s1a, s1b = os.dup(s1.fileno()), os.dup(s1.fileno())
     s1.close()
 
-    debug2('executing: %r\n' % argv)
+    debug2('executing: %r' % argv)
     p = ssubprocess.Popen(argv, stdin=s1a, stdout=s1b, preexec_fn=setup,
                           close_fds=True, stderr=stderr)
     os.close(s1a)
diff -pruN 1.0.5-1/sshuttle/ssnet.py 1.1.0-1/sshuttle/ssnet.py
--- 1.0.5-1/sshuttle/ssnet.py	2020-12-16 09:08:51.000000000 +0000
+++ 1.1.0-1/sshuttle/ssnet.py	2022-01-27 22:07:24.000000000 +0000
@@ -83,7 +83,7 @@ def _nb_clean(func, *args):
         if e.errno not in (errno.EWOULDBLOCK, errno.EAGAIN):
             raise
         else:
-            debug3('%s: err was: %s\n' % (func.__name__, e))
+            debug3('%s: err was: %s' % (func.__name__, e))
             return None
 
 
@@ -111,7 +111,7 @@ class SockWrapper:
     def __init__(self, rsock, wsock, connect_to=None, peername=None):
         global _swcount
         _swcount += 1
-        debug3('creating new SockWrapper (%d now exist)\n' % _swcount)
+        debug3('creating new SockWrapper (%d now exist)' % _swcount)
         self.exc = None
         self.rsock = rsock
         self.wsock = wsock
@@ -124,9 +124,9 @@ class SockWrapper:
     def __del__(self):
         global _swcount
         _swcount -= 1
-        debug1('%r: deleting (%d remain)\n' % (self, _swcount))
+        debug1('%r: deleting (%d remain)' % (self, _swcount))
         if self.exc:
-            debug1('%r: error was: %s\n' % (self, self.exc))
+            debug1('%r: error was: %s' % (self, self.exc))
 
     def __repr__(self):
         if self.rsock == self.wsock:
@@ -148,14 +148,14 @@ class SockWrapper:
         if not self.connect_to:
             return  # already connected
         self.rsock.setblocking(False)
-        debug3('%r: trying connect to %r\n' % (self, self.connect_to))
+        debug3('%r: trying connect to %r' % (self, self.connect_to))
         try:
             self.rsock.connect(self.connect_to)
             # connected successfully (Linux)
             self.connect_to = None
         except socket.error:
             _, e = sys.exc_info()[:2]
-            debug3('%r: connect result: %s\n' % (self, e))
+            debug3('%r: connect result: %s' % (self, e))
             if e.args[0] == errno.EINVAL:
                 # this is what happens when you call connect() on a socket
                 # that is now connected but returned EINPROGRESS last time,
@@ -165,7 +165,7 @@ class SockWrapper:
                 realerr = self.rsock.getsockopt(socket.SOL_SOCKET,
                                                 socket.SO_ERROR)
                 e = socket.error(realerr, os.strerror(realerr))
-                debug3('%r: fixed connect result: %s\n' % (self, e))
+                debug3('%r: fixed connect result: %s' % (self, e))
             if e.args[0] in [errno.EINPROGRESS, errno.EALREADY]:
                 pass  # not connected yet
             elif e.args[0] == 0:
@@ -191,13 +191,13 @@ class SockWrapper:
 
     def noread(self):
         if not self.shut_read:
-            debug2('%r: done reading\n' % self)
+            debug2('%r: done reading' % self)
             self.shut_read = True
             # self.rsock.shutdown(SHUT_RD)  # doesn't do anything anyway
 
     def nowrite(self):
         if not self.shut_write:
-            debug2('%r: done writing\n' % self)
+            debug2('%r: done writing' % self)
             self.shut_write = True
             try:
                 self.wsock.shutdown(SHUT_WR)
@@ -218,7 +218,7 @@ class SockWrapper:
         except OSError:
             _, e = sys.exc_info()[:2]
             if e.errno == errno.EPIPE:
-                debug1('%r: uwrite: got EPIPE\n' % self)
+                debug1('%r: uwrite: got EPIPE' % self)
                 self.nowrite()
                 return 0
             else:
@@ -275,12 +275,12 @@ class Handler:
             _add(r, i)
 
     def callback(self, sock):
-        log('--no callback defined-- %r\n' % self)
+        log('--no callback defined-- %r' % self)
         (r, _, _) = select.select(self.socks, [], [], 0)
         for s in r:
             v = s.recv(4096)
             if not v:
-                log('--closed-- %r\n' % self)
+                log('--closed-- %r' % self)
                 self.socks = []
                 self.ok = False
 
@@ -377,7 +377,7 @@ class Mux(Handler):
         # for b in self.outbuf:
         #    (s1,s2,c) = struct.unpack('!ccH', b[:4])
         #    ob.append(c)
-        # log('outbuf: %d %r\n' % (self.amount_queued(), ob))
+        # log('outbuf: %d %r' % (self.amount_queued(), ob))
 
     def send(self, channel, cmd, data):
         assert isinstance(data, bytes)
@@ -385,18 +385,18 @@ class Mux(Handler):
         p = struct.pack('!ccHHH', b('S'), b('S'), channel, cmd, len(data)) \
             + data
         self.outbuf.append(p)
-        debug2(' > channel=%d cmd=%s len=%d (fullness=%d)\n'
+        debug2(' > channel=%d cmd=%s len=%d (fullness=%d)'
                % (channel, cmd_to_name.get(cmd, hex(cmd)),
                   len(data), self.fullness))
         self.fullness += len(data)
 
     def got_packet(self, channel, cmd, data):
-        debug2('<  channel=%d cmd=%s len=%d\n'
+        debug2('<  channel=%d cmd=%s len=%d'
                % (channel, cmd_to_name.get(cmd, hex(cmd)), len(data)))
         if cmd == CMD_PING:
             self.send(0, CMD_PONG, data)
         elif cmd == CMD_PONG:
-            debug2('received PING response\n')
+            debug2('received PING response')
             self.too_full = False
             self.fullness = 0
         elif cmd == CMD_EXIT:
@@ -431,7 +431,7 @@ class Mux(Handler):
         else:
             callback = self.channels.get(channel)
             if not callback:
-                log('warning: closed channel %d got cmd=%s len=%d\n'
+                log('warning: closed channel %d got cmd=%s len=%d'
                     % (channel, cmd_to_name.get(cmd, hex(cmd)), len(data)))
             else:
                 callback(cmd, data)
@@ -446,7 +446,7 @@ class Mux(Handler):
             flags = fcntl.fcntl(self.wfile.fileno(), fcntl.F_SETFL, flags)
         if self.outbuf and self.outbuf[0]:
             wrote = _nb_clean(os.write, self.wfile.fileno(), self.outbuf[0])
-            debug2('mux wrote: %r/%d\n' % (wrote, len(self.outbuf[0])))
+            debug2('mux wrote: %r/%d' % (wrote, len(self.outbuf[0])))
             if wrote:
                 self.outbuf[0] = self.outbuf[0][wrote:]
         while self.outbuf and not self.outbuf[0]:
@@ -461,11 +461,14 @@ class Mux(Handler):
             flags |= os.O_NONBLOCK
             flags = fcntl.fcntl(self.rfile.fileno(), fcntl.F_SETFL, flags)
         try:
-            read = _nb_clean(os.read, self.rfile.fileno(), LATENCY_BUFFER_SIZE)
+            # If LATENCY_BUFFER_SIZE is inappropriately large, we will
+            # get a MemoryError here. Read no more than 1MiB.
+            read = _nb_clean(os.read, self.rfile.fileno(),
+                             min(1048576, LATENCY_BUFFER_SIZE))
         except OSError:
             _, e = sys.exc_info()[:2]
             raise Fatal('other end: %r' % e)
-        # log('<<< %r\n' % b)
+        # log('<<< %r' % b)
         if read == b(''):  # EOF
             self.ok = False
         if read:
@@ -473,7 +476,7 @@ class Mux(Handler):
 
     def handle(self):
         self.fill()
-        # log('inbuf is: (%d,%d) %r\n'
+        # log('inbuf is: (%d,%d) %r'
         #     % (self.want, len(self.inbuf), self.inbuf))
         while 1:
             if len(self.inbuf) >= (self.want or HDR_LEN):
@@ -511,7 +514,7 @@ class MuxWrapper(SockWrapper):
         self.channel = channel
         self.mux.channels[channel] = self.got_packet
         self.socks = []
-        debug2('new channel: %d\n' % channel)
+        debug2('new channel: %d' % channel)
 
     def __del__(self):
         self.nowrite()
@@ -527,7 +530,7 @@ class MuxWrapper(SockWrapper):
 
     def setnoread(self):
         if not self.shut_read:
-            debug2('%r: done reading\n' % self)
+            debug2('%r: done reading' % self)
             self.shut_read = True
             self.maybe_close()
 
@@ -538,13 +541,13 @@ class MuxWrapper(SockWrapper):
 
     def setnowrite(self):
         if not self.shut_write:
-            debug2('%r: done writing\n' % self)
+            debug2('%r: done writing' % self)
             self.shut_write = True
             self.maybe_close()
 
     def maybe_close(self):
         if self.shut_read and self.shut_write:
-            debug2('%r: closing connection\n' % self)
+            debug2('%r: closing connection' % self)
             # remove the mux's reference to us.  The python garbage collector
             # will then be able to reap our object.
             self.mux.channels[self.channel] = None
@@ -581,9 +584,9 @@ class MuxWrapper(SockWrapper):
 
 
 def connect_dst(family, ip, port):
-    debug2('Connecting to %s:%d\n' % (ip, port))
+    debug2('Connecting to %s:%d' % (ip, port))
     outsock = socket.socket(family)
-    outsock.setsockopt(socket.SOL_IP, socket.IP_TTL, 63)
+
     return SockWrapper(outsock, outsock,
                        connect_to=(ip, port),
                        peername='%s:%d' % (ip, port))
@@ -599,11 +602,11 @@ def runonce(handlers, mux):
 
     for s in handlers:
         s.pre_select(r, w, x)
-    debug2('Waiting: %d r=%r w=%r x=%r (fullness=%d/%d)\n'
+    debug2('Waiting: %d r=%r w=%r x=%r (fullness=%d/%d)'
            % (len(handlers), _fds(r), _fds(w), _fds(x),
                mux.fullness, mux.too_full))
     (r, w, x) = select.select(r, w, x)
-    debug2('  Ready: %d r=%r w=%r x=%r\n'
+    debug2('  Ready: %d r=%r w=%r x=%r'
            % (len(handlers), _fds(r), _fds(w), _fds(x)))
     ready = r + w + x
     did = {}
diff -pruN 1.0.5-1/sshuttle/sudoers.py 1.1.0-1/sshuttle/sudoers.py
--- 1.0.5-1/sshuttle/sudoers.py	2020-12-16 09:08:51.000000000 +0000
+++ 1.1.0-1/sshuttle/sudoers.py	2022-01-27 22:07:24.000000000 +0000
@@ -19,9 +19,14 @@ Cmnd_Alias %(ca)s = /usr/bin/env PYTHONP
 %(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):
-    content = template % {
+    content = warning_msg
+    content += template % {
         'ca': command_alias,
         'dist_packages': path_to_dist_packages,
         'py': sys.executable,
@@ -42,14 +47,15 @@ def save_config(content, file_name):
     process.stdin.write(content.encode())
 
     streamdata = process.communicate()[0]
+    sys.stdout.write(streamdata.decode("ASCII"))
     returncode = process.returncode
 
     if returncode:
-        log('Failed updating sudoers file.\n')
+        log('Failed updating sudoers file.')
         debug1(streamdata)
         exit(returncode)
     else:
-        log('Success, sudoers file update.\n')
+        log('Success, sudoers file update.')
         exit(0)
 
 
@@ -61,4 +67,5 @@ def sudoers(user_name=None, no_modify=No
         sys.stdout.write(content)
         exit(0)
     else:
+        sys.stdout.write(warning_msg)
         save_config(content, file_name)
diff -pruN 1.0.5-1/sshuttle/version.py 1.1.0-1/sshuttle/version.py
--- 1.0.5-1/sshuttle/version.py	2020-12-28 23:39:03.000000000 +0000
+++ 1.1.0-1/sshuttle/version.py	2022-01-27 22:37:51.000000000 +0000
@@ -1,5 +1,5 @@
 # coding: utf-8
 # file generated by setuptools_scm
 # don't change, don't track in version control
-version = '1.0.5'
-version_tuple = (1, 0, 5)
+version = '1.1.0'
+version_tuple = (1, 1, 0)
diff -pruN 1.0.5-1/sshuttle.egg-info/PKG-INFO 1.1.0-1/sshuttle.egg-info/PKG-INFO
--- 1.0.5-1/sshuttle.egg-info/PKG-INFO	2020-12-28 23:39:03.000000000 +0000
+++ 1.1.0-1/sshuttle.egg-info/PKG-INFO	2022-01-27 22:37:51.000000000 +0000
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: sshuttle
-Version: 1.0.5
+Version: 1.1.0
 Summary: Full-featured" VPN over an SSH tunnel
 Home-page: https://github.com/sshuttle/sshuttle
 Author: Brian May
@@ -32,7 +32,7 @@ Description: sshuttle: where transparent
         - You can't use openssh's PermitTunnel feature because
           it's disabled by default on openssh servers; plus it does
           TCP-over-TCP, which has `terrible performance`_.
-          
+        
         .. _terrible performance: https://sshuttle.readthedocs.io/en/stable/how-it-works.html
         
         Obtaining sshuttle
@@ -45,7 +45,7 @@ Description: sshuttle: where transparent
         - Debian stretch or later::
         
               apt-get install sshuttle
-              
+        
         - Arch Linux::
         
               pacman -S sshuttle
@@ -54,6 +54,14 @@ Description: sshuttle: where transparent
         
               dnf install sshuttle
         
+        - openSUSE::
+        
+              zypper in sshuttle
+        
+        - Gentoo::
+        
+              emerge -av net-proxy/sshuttle
+        
         - NixOS::
         
               nix-env -iA nixos.sshuttle
@@ -75,6 +83,11 @@ Description: sshuttle: where transparent
               # pkg
               pkg install py36-sshuttle
         
+        - macOS, via MacPorts::
+        
+              sudo port selfupdate
+              sudo port install sshuttle
+        
         It is also possible to install into a virtualenv as a non-root user.
         
         - From PyPI::
@@ -111,7 +124,7 @@ Description: sshuttle: where transparent
         
         Running as a service
         --------------------
-        Sshuttle can also be run as a service and configured using a config management system: 
+        Sshuttle can also be run as a service and configured using a config management system:
         https://medium.com/@mike.reider/using-sshuttle-as-a-service-bec2684a65fe
         
 Keywords: ssh vpn
diff -pruN 1.0.5-1/sshuttle.egg-info/requires.txt 1.1.0-1/sshuttle.egg-info/requires.txt
--- 1.0.5-1/sshuttle.egg-info/requires.txt	2020-12-28 23:39:03.000000000 +0000
+++ 1.1.0-1/sshuttle.egg-info/requires.txt	1970-01-01 00:00:00.000000000 +0000
@@ -1 +0,0 @@
-psutil
diff -pruN 1.0.5-1/sshuttle.egg-info/SOURCES.txt 1.1.0-1/sshuttle.egg-info/SOURCES.txt
--- 1.0.5-1/sshuttle.egg-info/SOURCES.txt	2020-12-28 23:39:03.000000000 +0000
+++ 1.1.0-1/sshuttle.egg-info/SOURCES.txt	2022-01-27 22:37:51.000000000 +0000
@@ -1,5 +1,6 @@
 .gitignore
 .prospector.yml
+.readthedocs.yaml
 CHANGES.rst
 LICENSE
 MANIFEST.in
@@ -11,6 +12,7 @@ run
 setup.cfg
 setup.py
 tox.ini
+.github/dependabot.yml
 .github/workflows/pythonpackage.yml
 bin/sudoers-add
 docs/Makefile
@@ -53,7 +55,6 @@ sshuttle.egg-info/PKG-INFO
 sshuttle.egg-info/SOURCES.txt
 sshuttle.egg-info/dependency_links.txt
 sshuttle.egg-info/entry_points.txt
-sshuttle.egg-info/requires.txt
 sshuttle.egg-info/top_level.txt
 sshuttle/methods/__init__.py
 sshuttle/methods/ipfw.py
diff -pruN 1.0.5-1/tests/client/test_firewall.py 1.1.0-1/tests/client/test_firewall.py
--- 1.0.5-1/tests/client/test_firewall.py	2020-12-27 23:46:37.000000000 +0000
+++ 1.1.0-1/tests/client/test_firewall.py	2022-01-27 22:07:24.000000000 +0000
@@ -1,7 +1,7 @@
 import io
 from socket import AF_INET, AF_INET6
 
-from mock import Mock, patch, call
+from unittest.mock import Mock, patch, call
 import sshuttle.firewall
 
 
@@ -15,7 +15,7 @@ NSLIST
 {inet},1.2.3.33
 {inet6},2404:6800:4004:80c::33
 PORTS 1024,1025,1026,1027
-GO 1 -
+GO 1 - 0x01 12345
 HOST 1.2.3.3,existing
 """.format(inet=AF_INET, inet6=AF_INET6))
     stdout = Mock()
@@ -125,7 +125,8 @@ def test_main(mock_get_method, mock_setu
             [(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),
+            None,
+            '0x01'),
         call().setup_firewall(
             1025, 1027,
             [(AF_INET, u'1.2.3.33')],
@@ -133,7 +134,8 @@ def test_main(mock_get_method, mock_setu
             [(AF_INET, 24, False, u'1.2.3.0', 8000, 9000),
                 (AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
             True,
-            None),
+            None,
+            '0x01'),
         call().restore_firewall(1024, AF_INET6, True, None),
         call().restore_firewall(1025, AF_INET, True, None),
     ]
diff -pruN 1.0.5-1/tests/client/test_helpers.py 1.1.0-1/tests/client/test_helpers.py
--- 1.0.5-1/tests/client/test_helpers.py	2020-12-16 09:08:57.000000000 +0000
+++ 1.1.0-1/tests/client/test_helpers.py	2022-01-27 22:07:24.000000000 +0000
@@ -3,7 +3,7 @@ import socket
 from socket import AF_INET, AF_INET6
 import errno
 
-from mock import patch, call
+from unittest.mock import patch, call
 import sshuttle.helpers
 
 
@@ -24,19 +24,19 @@ def test_log(mock_stderr, mock_stdout):
         call.flush(),
     ]
     assert mock_stderr.mock_calls == [
-        call.write('prefix: message'),
+        call.write('prefix: message\r\n'),
         call.flush(),
-        call.write('prefix: abc'),
+        call.write('prefix: abc\r\n'),
         call.flush(),
-        call.write('prefix: message 1\n'),
+        call.write('prefix: message 1\r\n'),
         call.flush(),
-        call.write('prefix: message 2\n'),
-        call.write('---> line2\n'),
-        call.write('---> line3\n'),
+        call.write('prefix: message 2\r\n'),
+        call.write('    line2\r\n'),
+        call.write('    line3\r\n'),
         call.flush(),
-        call.write('prefix: message 3\n'),
-        call.write('---> line2\n'),
-        call.write('---> line3\n'),
+        call.write('prefix: message 3\r\n'),
+        call.write('    line2\r\n'),
+        call.write('    line3\r\n'),
         call.flush(),
     ]
 
@@ -51,7 +51,7 @@ def test_debug1(mock_stderr, mock_stdout
         call.flush(),
     ]
     assert mock_stderr.mock_calls == [
-        call.write('prefix: message'),
+        call.write('prefix: message\r\n'),
         call.flush(),
     ]
 
@@ -76,7 +76,7 @@ def test_debug2(mock_stderr, mock_stdout
         call.flush(),
     ]
     assert mock_stderr.mock_calls == [
-        call.write('prefix: message'),
+        call.write('prefix: message\r\n'),
         call.flush(),
     ]
 
@@ -101,7 +101,7 @@ def test_debug3(mock_stderr, mock_stdout
         call.flush(),
     ]
     assert mock_stderr.mock_calls == [
-        call.write('prefix: message'),
+        call.write('prefix: message\r\n'),
         call.flush(),
     ]
 
diff -pruN 1.0.5-1/tests/client/test_methods_nat.py 1.1.0-1/tests/client/test_methods_nat.py
--- 1.0.5-1/tests/client/test_methods_nat.py	2020-12-16 09:08:57.000000000 +0000
+++ 1.1.0-1/tests/client/test_methods_nat.py	2022-01-27 22:07:24.000000000 +0000
@@ -3,7 +3,7 @@ from socket import AF_INET, AF_INET6
 import struct
 
 import pytest
-from mock import Mock, patch, call
+from unittest.mock import Mock, patch, call
 from sshuttle.helpers import Fatal
 from sshuttle.methods import get_method
 
@@ -11,7 +11,7 @@ from sshuttle.methods import get_method
 def test_get_supported_features():
     method = get_method('nat')
     features = method.get_supported_features()
-    assert not features.ipv6
+    assert features.ipv6
     assert not features.udp
     assert features.dns
 
@@ -85,26 +85,52 @@ def test_firewall_command():
 
 
 @patch('sshuttle.methods.nat.ipt')
-@patch('sshuttle.methods.nat.ipt_ttl')
 @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
     method = get_method('nat')
     assert method.name == 'nat'
 
-    with pytest.raises(Exception) as excinfo:
-        method.setup_firewall(
-            1024, 1026,
-            [(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 == []
-    assert mock_ipt_ttl.mock_calls == []
+    assert mock_ipt.mock_calls == []
+    method.setup_firewall(
+        1024, 1026,
+        [(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)],
+        False,
+        None,
+        '0x01')
+
+    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.mock_calls == []
 
     with pytest.raises(Exception) as excinfo:
@@ -115,10 +141,10 @@ def test_setup_firewall(mock_ipt_chain_e
             [(AF_INET, 24, False, u'1.2.3.0', 8000, 9000),
                 (AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
             True,
-            None)
+            None,
+            '0x01')
     assert str(excinfo.value) == 'UDP not supported by nat method_name'
     assert mock_ipt_chain_exists.mock_calls == []
-    assert mock_ipt_ttl.mock_calls == []
     assert mock_ipt.mock_calls == []
 
     method.setup_firewall(
@@ -128,14 +154,11 @@ def test_setup_firewall(mock_ipt_chain_e
         [(AF_INET, 24, False, u'1.2.3.0', 8000, 9000),
             (AF_INET, 32, True, u'1.2.3.66', 8080, 8080)],
         False,
-        None)
+        None,
+        '0x01')
     assert mock_ipt_chain_exists.mock_calls == [
         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 == [
         call(AF_INET, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
         call(AF_INET, 'nat', '-D', 'PREROUTING', '-j', 'sshuttle-1025'),
@@ -146,7 +169,7 @@ def test_setup_firewall(mock_ipt_chain_e
         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', 'REDIRECT',
-             '--dest', u'1.2.3.33/32', '-p', 'udp',
+             '--dest', u'1.2.3.33', '-p', 'udp',
              '--dport', '53', '--to-ports', '1027'),
         call(AF_INET, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN',
              '-m', 'addrtype', '--dst-type', 'LOCAL'),
@@ -157,20 +180,33 @@ def test_setup_firewall(mock_ipt_chain_e
              '--to-ports', '1025')
     ]
     mock_ipt_chain_exists.reset_mock()
-    mock_ipt_ttl.reset_mock()
     mock_ipt.reset_mock()
 
     method.restore_firewall(1025, AF_INET, False, None)
     assert mock_ipt_chain_exists.mock_calls == [
         call(AF_INET, 'nat', 'sshuttle-1025')
     ]
-    assert mock_ipt_ttl.mock_calls == []
     assert mock_ipt.mock_calls == [
-        call(AF_INET, 'nat', '-D', 'OUTPUT', '-j', 'sshuttle-1025'),
-        call(AF_INET, 'nat', '-D', 'PREROUTING', '-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', '-F', 'sshuttle-1025'),
         call(AF_INET, 'nat', '-X', 'sshuttle-1025')
     ]
     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()
diff -pruN 1.0.5-1/tests/client/test_methods_pf.py 1.1.0-1/tests/client/test_methods_pf.py
--- 1.0.5-1/tests/client/test_methods_pf.py	2020-12-16 09:08:57.000000000 +0000
+++ 1.1.0-1/tests/client/test_methods_pf.py	2022-01-27 22:07:24.000000000 +0000
@@ -2,7 +2,7 @@ import socket
 from socket import AF_INET, AF_INET6
 
 import pytest
-from mock import Mock, patch, call, ANY
+from unittest.mock import Mock, patch, call, ANY
 from sshuttle.methods import get_method
 from sshuttle.helpers import Fatal, get_env
 from sshuttle.methods.pf import FreeBsd, Darwin, OpenBsd
@@ -186,7 +186,8 @@ def test_setup_firewall_darwin(mock_pf_g
         [(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)
+        None,
+        '0x01')
     assert mock_ioctl.mock_calls == [
         call(mock_pf_get_dev(), 0xC4704433, ANY),
         call(mock_pf_get_dev(), 0xCC20441A, ANY),
@@ -225,7 +226,8 @@ def test_setup_firewall_darwin(mock_pf_g
             [(AF_INET, 24, False, u'1.2.3.0', 0, 0),
                 (AF_INET, 32, True, u'1.2.3.66', 80, 80)],
             True,
-            None)
+            None,
+            '0x01')
     assert str(excinfo.value) == 'UDP not supported by pf method_name'
     assert mock_pf_get_dev.mock_calls == []
     assert mock_ioctl.mock_calls == []
@@ -238,7 +240,8 @@ def test_setup_firewall_darwin(mock_pf_g
         [(AF_INET, 24, False, u'1.2.3.0', 0, 0),
             (AF_INET, 32, True, u'1.2.3.66', 80, 80)],
         False,
-        None)
+        None,
+        '0x01')
     assert mock_ioctl.mock_calls == [
         call(mock_pf_get_dev(), 0xC4704433, ANY),
         call(mock_pf_get_dev(), 0xCC20441A, ANY),
@@ -298,7 +301,8 @@ def test_setup_firewall_freebsd(mock_pf_
         [(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)
+        None,
+        '0x01')
 
     assert mock_pfctl.mock_calls == [
         call('-s all'),
@@ -330,7 +334,8 @@ def test_setup_firewall_freebsd(mock_pf_
             [(AF_INET, 24, False, u'1.2.3.0', 0, 0),
                 (AF_INET, 32, True, u'1.2.3.66', 80, 80)],
             True,
-            None)
+            None,
+            '0x01')
     assert str(excinfo.value) == 'UDP not supported by pf method_name'
     assert mock_pf_get_dev.mock_calls == []
     assert mock_ioctl.mock_calls == []
@@ -343,7 +348,8 @@ def test_setup_firewall_freebsd(mock_pf_
         [(AF_INET, 24, False, u'1.2.3.0', 0, 0),
             (AF_INET, 32, True, u'1.2.3.66', 80, 80)],
         False,
-        None)
+        None,
+        '0x01')
     assert mock_ioctl.mock_calls == [
         call(mock_pf_get_dev(), 0xC4704433, ANY),
         call(mock_pf_get_dev(), 0xCBE0441A, ANY),
@@ -401,7 +407,8 @@ def test_setup_firewall_openbsd(mock_pf_
         [(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)
+        None,
+        '0x01')
 
     assert mock_ioctl.mock_calls == [
         call(mock_pf_get_dev(), 0xcd60441a, ANY),
@@ -437,7 +444,8 @@ def test_setup_firewall_openbsd(mock_pf_
             [(AF_INET, 24, False, u'1.2.3.0', 0, 0),
                 (AF_INET, 32, True, u'1.2.3.66', 80, 80)],
             True,
-            None)
+            None,
+            '0x01')
     assert str(excinfo.value) == 'UDP not supported by pf method_name'
     assert mock_pf_get_dev.mock_calls == []
     assert mock_ioctl.mock_calls == []
@@ -450,7 +458,8 @@ def test_setup_firewall_openbsd(mock_pf_
         [(AF_INET, 24, False, u'1.2.3.0', 0, 0),
             (AF_INET, 32, True, u'1.2.3.66', 80, 80)],
         False,
-        None)
+        None,
+        '0x01')
     assert mock_ioctl.mock_calls == [
         call(mock_pf_get_dev(), 0xcd60441a, ANY),
         call(mock_pf_get_dev(), 0xcd60441a, ANY),
diff -pruN 1.0.5-1/tests/client/test_methods_tproxy.py 1.1.0-1/tests/client/test_methods_tproxy.py
--- 1.0.5-1/tests/client/test_methods_tproxy.py	2019-12-12 21:16:39.000000000 +0000
+++ 1.1.0-1/tests/client/test_methods_tproxy.py	2022-01-27 22:07:24.000000000 +0000
@@ -1,13 +1,12 @@
 import socket
 from socket import AF_INET, AF_INET6
 
-from mock import Mock, patch, call
+from unittest.mock import Mock, patch, call
 
 from sshuttle.methods import get_method
 
 
-@patch("sshuttle.methods.tproxy.recvmsg")
-def test_get_supported_features_recvmsg(mock_recvmsg):
+def test_get_supported_features():
     method = get_method('tproxy')
     features = method.get_supported_features()
     assert features.ipv6
@@ -15,15 +14,6 @@ def test_get_supported_features_recvmsg(
     assert features.dns
 
 
-@patch("sshuttle.methods.tproxy.recvmsg", None)
-def test_get_supported_features_norecvmsg():
-    method = get_method('tproxy')
-    features = method.get_supported_features()
-    assert features.ipv6
-    assert not features.udp
-    assert not features.dns
-
-
 def test_get_tcp_dstip():
     sock = Mock()
     sock.getsockname.return_value = ('127.0.0.1', 1024)
@@ -92,9 +82,8 @@ def test_firewall_command():
 
 
 @patch('sshuttle.methods.tproxy.ipt')
-@patch('sshuttle.methods.tproxy.ipt_ttl')
 @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
     method = get_method('tproxy')
     assert method.name == 'tproxy'
@@ -108,13 +97,13 @@ def test_setup_firewall(mock_ipt_chain_e
         [(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)
+        None,
+        '0x01')
     assert mock_ipt_chain_exists.mock_calls == [
         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(AF_INET6, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1024'),
         call(AF_INET6, 'mangle', '-F', 'sshuttle-m-1024'),
@@ -133,18 +122,22 @@ def test_setup_firewall(mock_ipt_chain_e
         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-t-1024', '-j', 'RETURN',
+             '-m', 'addrtype', '--dst-type', 'LOCAL'),
+        call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
+             '-m', 'addrtype', '--dst-type', 'LOCAL'),
         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-t-1024', '-m', 'socket',
              '-j', 'sshuttle-d-1024', '-m', 'tcp', '-p', 'tcp'),
         call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-m', 'socket',
              '-j', 'sshuttle-d-1024', '-m', 'udp', '-p', 'udp'),
         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'),
         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',
              '-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1026'),
         call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
@@ -160,22 +153,23 @@ def test_setup_firewall(mock_ipt_chain_e
              '--dest', u'2404:6800:4004:80c::101f/128',
              '-m', 'udp', '-p', 'udp', '--dport', '8080:8080'),
         call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
-             '--set-mark', '1', '--dest', u'2404:6800:4004:80c::/64',
+             '--set-mark', '0x01', '--dest', u'2404:6800:4004:80c::/64',
              '-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000'),
         call(AF_INET6, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
-             '--tproxy-mark', '0x1/0x1', '--dest', u'2404:6800:4004:80c::/64',
+             '--tproxy-mark', '0x01', '--dest',
+             u'2404:6800:4004:80c::/64',
              '-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000',
              '--on-port', '1024'),
         call(AF_INET6, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
-             '--set-mark', '1', '--dest', u'2404:6800:4004:80c::/64',
+             '--set-mark', '0x01', '--dest', u'2404:6800:4004:80c::/64',
              '-m', 'udp', '-p', 'udp', '--dport', '8000:9000'),
         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',
              '--on-port', '1024')
     ]
     mock_ipt_chain_exists.reset_mock()
-    mock_ipt_ttl.reset_mock()
     mock_ipt.reset_mock()
 
     method.restore_firewall(1025, AF_INET6, True, None)
@@ -184,7 +178,6 @@ def test_setup_firewall(mock_ipt_chain_e
         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(AF_INET6, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
         call(AF_INET6, 'mangle', '-F', 'sshuttle-m-1025'),
@@ -196,7 +189,6 @@ def test_setup_firewall(mock_ipt_chain_e
         call(AF_INET6, 'mangle', '-X', 'sshuttle-d-1025')
     ]
     mock_ipt_chain_exists.reset_mock()
-    mock_ipt_ttl.reset_mock()
     mock_ipt.reset_mock()
 
     # IPV4
@@ -208,13 +200,13 @@ def test_setup_firewall(mock_ipt_chain_e
         [(AF_INET, 24, False, u'1.2.3.0', 0, 0),
             (AF_INET, 32, True, u'1.2.3.66', 80, 80)],
         True,
-        None)
+        None,
+        '0x01')
     assert mock_ipt_chain_exists.mock_calls == [
         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(AF_INET, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
         call(AF_INET, 'mangle', '-F', 'sshuttle-m-1025'),
@@ -233,18 +225,22 @@ def test_setup_firewall(mock_ipt_chain_e
         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-t-1025', '-j', 'RETURN',
+             '-m', 'addrtype', '--dst-type', 'LOCAL'),
+        call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
+             '-m', 'addrtype', '--dst-type', 'LOCAL'),
         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-t-1025', '-m', 'socket',
              '-j', 'sshuttle-d-1025', '-m', 'tcp', '-p', 'tcp'),
         call(AF_INET, 'mangle', '-A', 'sshuttle-t-1025', '-m', 'socket',
              '-j', 'sshuttle-d-1025', '-m', 'udp', '-p', 'udp'),
         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'),
         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'),
         call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
              '--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp',
@@ -259,20 +255,19 @@ def test_setup_firewall(mock_ipt_chain_e
              '--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp',
              '--dport', '80:80'),
         call(AF_INET, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
-             '--set-mark', '1', '--dest', u'1.2.3.0/24',
+             '--set-mark', '0x01', '--dest', u'1.2.3.0/24',
              '-m', 'tcp', '-p', 'tcp'),
         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'),
         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'),
         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')
     ]
     mock_ipt_chain_exists.reset_mock()
-    mock_ipt_ttl.reset_mock()
     mock_ipt.reset_mock()
 
     method.restore_firewall(1025, AF_INET, True, None)
@@ -281,7 +276,6 @@ def test_setup_firewall(mock_ipt_chain_e
         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(AF_INET, 'mangle', '-D', 'OUTPUT', '-j', 'sshuttle-m-1025'),
         call(AF_INET, 'mangle', '-F', 'sshuttle-m-1025'),
@@ -293,5 +287,4 @@ def test_setup_firewall(mock_ipt_chain_e
         call(AF_INET, 'mangle', '-X', 'sshuttle-d-1025')
     ]
     mock_ipt_chain_exists.reset_mock()
-    mock_ipt_ttl.reset_mock()
     mock_ipt.reset_mock()
diff -pruN 1.0.5-1/tests/client/test_options.py 1.1.0-1/tests/client/test_options.py
--- 1.0.5-1/tests/client/test_options.py	2020-12-16 09:08:57.000000000 +0000
+++ 1.1.0-1/tests/client/test_options.py	2022-01-27 22:07:24.000000000 +0000
@@ -100,3 +100,8 @@ def test_parse_subnetport_ip6_with_mask_
             == [(socket.AF_INET6, ip, 128, 80, 80)]
         assert sshuttle.options.parse_subnetport('[' + ip_repr + '/16]:80-90')\
             == [(socket.AF_INET6, ip, 16, 80, 90)]
+
+
+def test_convert_arg_line_to_args_skips_comments():
+    parser = sshuttle.options.MyArgumentParser()
+    assert parser.convert_arg_line_to_args("# whatever something") == []
diff -pruN 1.0.5-1/tests/client/test_sdnotify.py 1.1.0-1/tests/client/test_sdnotify.py
--- 1.0.5-1/tests/client/test_sdnotify.py	2019-12-12 21:16:39.000000000 +0000
+++ 1.1.0-1/tests/client/test_sdnotify.py	2022-01-27 22:07:24.000000000 +0000
@@ -1,6 +1,6 @@
 import socket
 
-from mock import Mock, patch, call
+from unittest.mock import Mock, patch, call
 
 import sshuttle.sdnotify
 
diff -pruN 1.0.5-1/tests/server/test_server.py 1.1.0-1/tests/server/test_server.py
--- 1.0.5-1/tests/server/test_server.py	2019-12-12 21:16:39.000000000 +0000
+++ 1.1.0-1/tests/server/test_server.py	2022-01-27 22:07:24.000000000 +0000
@@ -1,7 +1,7 @@
 import io
 import socket
 
-from mock import patch, Mock
+from unittest.mock import patch, Mock
 
 import sshuttle.server
 
diff -pruN 1.0.5-1/tox.ini 1.1.0-1/tox.ini
--- 1.0.5-1/tox.ini	2020-12-28 00:00:43.000000000 +0000
+++ 1.1.0-1/tox.ini	2022-01-27 22:07:24.000000000 +0000
@@ -1,7 +1,6 @@
 [tox]
 downloadcache = {toxworkdir}/cache/
 envlist =
-    py35,
     py36,
     py37,
     py38,
