diff --git a/bin/sudoers-add b/bin/sudoers-add index 5bec3d1..e359d46 100755 --- a/bin/sudoers-add +++ b/bin/sudoers-add @@ -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 --git a/docs/manpage.rst b/docs/manpage.rst index 33e3373..ead9a16 100644 --- a/docs/manpage.rst +++ b/docs/manpage.rst @@ -10,8 +10,8 @@ Synopsis 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:: 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 @@ -141,7 +142,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 , --exclude= @@ -274,9 +278,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 , --tmark= - 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,11 +310,8 @@ Arguments read from a file must be one per line, as shown below:: --option2 value2 -Comments in config file -....................... - -It's possible to add comments in the configuration file. This allows annotating the -various subnets with human-readable descriptions, like:: +The configuration file supports comments for human-readable +annotations. For example:: # company-internal API 8.8.8.8/32 @@ -319,51 +321,96 @@ various subnets with human-readable descriptions, like:: 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). - [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... + $ sshuttle -r example.com -x example.com:22 0/0 + + 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 : (, '0.0.0.0', 0, 0, 0) + c : Subnets to exclude from forwarding: + c : (, '...', 32, 22, 22) + c : (, '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 + +Connect to a remote server, with automatic hostname and subnet guessing:: - $ sshuttle -vNHr example.org - - 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 : (, '...', 32, 22, 22) + c : (, '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:: diff --git a/docs/tproxy.rst b/docs/tproxy.rst index 6a9dbc8..a805120 100644 --- a/docs/tproxy.rst +++ b/docs/tproxy.rst @@ -12,7 +12,8 @@ There are some things you need to consider for TPROXY to work: ip -6 route add local default dev lo table 100 ip -6 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. diff --git a/docs/usage.rst b/docs/usage.rst index 646c968..3fd63a1 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -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. diff --git a/sshuttle/client.py b/sshuttle/client.py index 647fb27..349ad86 100644 --- a/sshuttle/client.py +++ b/sshuttle/client.py @@ -201,12 +201,11 @@ class FirewallClient: def __init__(self, method_name, sudo_pythonpath, ttl): 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] + - ['--firewall'] + - ['--ttl', str(ttl)]) + ['--firewall']) if ssyslog._p: argvbase += ['--syslog'] @@ -224,7 +223,8 @@ class FirewallClient: if sudo_pythonpath: elev_prefix += ['/usr/bin/env', - 'PYTHONPATH=%s' % python_path] + 'PYTHONPATH=%s' % + os.path.dirname(os.path.dirname(__file__))] argv_tries = [elev_prefix + argvbase, argvbase] # we can't use stdin/stdout=subprocess.PIPE here, as we normally would, @@ -261,7 +261,7 @@ class FirewallClient: def setup(self, subnets_include, subnets_exclude, nslist, redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp, - user, tmark, ttl): + user, ttl, tmark): self.subnets_include = subnets_include self.subnets_exclude = subnets_exclude self.nslist = nslist @@ -311,7 +311,9 @@ 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 %d %s\n' % + (udp, user, self.ttl, + bytes(self.tmark, 'ascii'))) self.pfile.flush() line = self.pfile.readline() @@ -1003,7 +1005,7 @@ def main(listenip_v6, listenip_v4, # start the firewall fw.setup(subnets_include, subnets_exclude, nslist, redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, - required.udp, user, tmark, ttl) + required.udp, user, ttl, tmark) # start the client process try: diff --git a/sshuttle/cmdline.py b/sshuttle/cmdline.py index d935d9e..e792e22 100644 --- a/sshuttle/cmdline.py +++ b/sshuttle/cmdline.py @@ -20,7 +20,7 @@ def main(): return 1 if not opt.sudoers_filename: - log('--sudoers-file must be set or omited.') + log('--sudoers-file must be set or omitted.') return 1 sudoers( @@ -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() diff --git a/sshuttle/firewall.py b/sshuttle/firewall.py index 021ca96..031454c 100644 --- a/sshuttle/firewall.py +++ b/sshuttle/firewall.py @@ -223,11 +223,13 @@ def main(method_name, syslog, ttl): raise Fatal('expected GO but got %r' % line) _, _, args = line.partition(" ") - udp, user = args.strip().split(" ", 1) + udp, user, ttl, tmark = args.strip().split(" ", 3) udp = bool(int(udp)) if user == '-': user = None - debug2('Got udp: %r, user: %r' % (udp, user)) + ttl = int(ttl) + debug2('Got udp: %r, user: %r, ttl: %s, tmark: %s' % + (udp, user, ttl, tmark)) 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] @@ -242,14 +244,14 @@ def main(method_name, syslog, ttl): method.setup_firewall( port_v6, dnsport_v6, nslist_v6, socket.AF_INET6, subnets_v6, udp, - user, ttl) + user, ttl, tmark) if subnets_v4 or nslist_v4: debug2('setting up IPv4.') method.setup_firewall( port_v4, dnsport_v4, nslist_v4, socket.AF_INET, subnets_v4, udp, - user, ttl) + user, ttl, tmark) flush_systemd_dns_cache() stdout.write('STARTED\n') diff --git a/sshuttle/hostwatch.py b/sshuttle/hostwatch.py index 683b6a7..7e4d3c5 100644 --- a/sshuttle/hostwatch.py +++ b/sshuttle/hostwatch.py @@ -16,8 +16,6 @@ NETSTAT_POLL_TIME = 30 CACHEFILE = os.path.expanduser('~/.sshuttle.hosts') -_nmb_ok = True -_smb_ok = True hostnames = {} queue = {} try: @@ -141,110 +139,11 @@ def _check_netstat(): check_host(ip) -def _check_smb(hostname): - return - global _smb_ok - if not _smb_ok: - return - debug2(' > smb: %s' % hostname) - argv = ['smbclient', '-U', '%', '-L', hostname] - try: - p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null, - env=get_env()) - lines = p.stdout.readlines() - p.wait() - except OSError: - _, e = sys.exc_info()[:2] - log('%r failed: %r' % (argv, e)) - _smb_ok = False - return - - lines.reverse() - - # junk at top - while lines: - line = lines.pop().strip() - if re.match(r'Server\s+', line): - break - - # server list section: - # Server Comment - # ------ ------- - while lines: - line = lines.pop().strip() - if not line or re.match(r'-+\s+-+', line): - continue - if re.match(r'Workgroup\s+Master', line): - break - words = line.split() - hostname = words[0].lower() - debug3('< %s' % hostname) - check_host(hostname) - - # workgroup list section: - # Workgroup Master - # --------- ------ - while lines: - line = lines.pop().strip() - if re.match(r'-+\s+', line): - continue - if not line: - break - words = line.split() - (workgroup, hostname) = (words[0].lower(), words[1].lower()) - debug3('< group(%s) -> %s' % (workgroup, hostname)) - check_host(hostname) - check_workgroup(workgroup) - - if lines: - assert(0) - - -def _check_nmb(hostname, is_workgroup, is_master): - return - global _nmb_ok - if not _nmb_ok: - return - debug2(' > n%d%d: %s' % (is_workgroup, is_master, hostname)) - argv = ['nmblookup'] + ['-M'] * is_master + ['--', hostname] - try: - p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null, - env=get_env) - lines = p.stdout.readlines() - rv = p.wait() - except OSError: - _, e = sys.exc_info()[:2] - log('%r failed: %r' % (argv, e)) - _nmb_ok = False - return - if rv: - log('%r returned %d' % (argv, rv)) - return - for line in lines: - m = re.match(r'(\d+\.\d+\.\d+\.\d+) (\w+)<\w\w>\n', line) - if m: - g = m.groups() - (ip, name) = (g[0], g[1].lower()) - debug3('< %s -> %s' % (name, ip)) - if is_workgroup: - _enqueue(_check_smb, ip) - else: - found_host(name, ip) - check_host(name) - - def check_host(hostname): if _is_ip(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): @@ -277,8 +176,6 @@ 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() diff --git a/sshuttle/methods/__init__.py b/sshuttle/methods/__init__.py index 1882c3a..0e4c49d 100644 --- a/sshuttle/methods/__init__.py +++ b/sshuttle/methods/__init__.py @@ -91,7 +91,7 @@ class BaseMethod(object): (key, self.name)) def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, - user): + user, ttl, tmark): raise NotImplementedError() def restore_firewall(self, port, family, udp, user): diff --git a/sshuttle/methods/ipfw.py b/sshuttle/methods/ipfw.py index f93bdf4..bda8968 100644 --- a/sshuttle/methods/ipfw.py +++ b/sshuttle/methods/ipfw.py @@ -189,7 +189,7 @@ class Method(BaseMethod): # udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVDSTADDR, 1) def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, - user, ttl): + user, ttl, tmark): # IPv6 not supported if family not in [socket.AF_INET]: raise Exception( diff --git a/sshuttle/methods/nat.py b/sshuttle/methods/nat.py index baa9998..a7a661c 100644 --- a/sshuttle/methods/nat.py +++ b/sshuttle/methods/nat.py @@ -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, ttl): + user, ttl, tmark): if family != socket.AF_INET and family != socket.AF_INET6: raise Exception( 'Address family "%s" unsupported by nat method_name' diff --git a/sshuttle/methods/nft.py b/sshuttle/methods/nft.py index 775fa51..8f54c86 100644 --- a/sshuttle/methods/nft.py +++ b/sshuttle/methods/nft.py @@ -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, ttl): + user, ttl, tmark): if udp: raise Exception("UDP not supported by nft") diff --git a/sshuttle/methods/pf.py b/sshuttle/methods/pf.py index 1bc67e7..be46be7 100644 --- a/sshuttle/methods/pf.py +++ b/sshuttle/methods/pf.py @@ -444,7 +444,7 @@ class Method(BaseMethod): return sock.getsockname() def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, - user, ttl): + user, ttl, tmark): if family not in [socket.AF_INET, socket.AF_INET6]: raise Exception( 'Address family "%s" unsupported by pf method_name' diff --git a/sshuttle/methods/tproxy.py b/sshuttle/methods/tproxy.py index c1cccd5..eb337fe 100644 --- a/sshuttle/methods/tproxy.py +++ b/sshuttle/methods/tproxy.py @@ -151,17 +151,7 @@ class Method(BaseMethod): udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVORIGDSTADDR, 1) def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, - user, ttl): - 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, ttl, tmark): if family not in [socket.AF_INET, socket.AF_INET6]: raise Exception( 'Address family "%s" unsupported by tproxy method' @@ -192,8 +182,8 @@ 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. @@ -224,7 +214,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)) @@ -249,7 +239,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)))) @@ -273,7 +263,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)))) diff --git a/sshuttle/options.py b/sshuttle/options.py index 290e176..0a311aa 100644 --- a/sshuttle/options.py +++ b/sshuttle/options.py @@ -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 [] @@ -220,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( @@ -280,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( @@ -445,8 +447,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 --git a/tests/client/test_firewall.py b/tests/client/test_firewall.py index 4a1d744..31618d9 100644 --- a/tests/client/test_firewall.py +++ b/tests/client/test_firewall.py @@ -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 - 63 0x01 HOST 1.2.3.3,existing """.format(inet=AF_INET, inet6=AF_INET6)) stdout = Mock() @@ -126,7 +126,7 @@ def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts): (AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)], True, None, - 63), + 63, '0x01'), call().setup_firewall( 1025, 1027, [(AF_INET, u'1.2.3.33')], @@ -135,7 +135,7 @@ def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts): (AF_INET, 32, True, u'1.2.3.66', 8080, 8080)], True, None, - 63), + 63, '0x01'), call().restore_firewall(1024, AF_INET6, True, None), call().restore_firewall(1025, AF_INET, True, None), ] diff --git a/tests/client/test_methods_nat.py b/tests/client/test_methods_nat.py index 10b3ddb..0bd0ec2 100644 --- a/tests/client/test_methods_nat.py +++ b/tests/client/test_methods_nat.py @@ -103,7 +103,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt): (AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 80, 80)], False, None, - 63) + 63, '0x01') assert mock_ipt_chain_exists.mock_calls == [ call(AF_INET6, 'nat', 'sshuttle-1024') @@ -150,7 +150,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt): (AF_INET, 32, True, u'1.2.3.66', 8080, 8080)], True, None, - 63) + 63, '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 == [] @@ -164,7 +164,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt): (AF_INET, 32, True, u'1.2.3.66', 8080, 8080)], False, None, - 63) + 63, '0x01') assert mock_ipt_chain_exists.mock_calls == [ call(AF_INET, 'nat', 'sshuttle-1025') ] diff --git a/tests/client/test_methods_pf.py b/tests/client/test_methods_pf.py index d807b11..83f7537 100644 --- a/tests/client/test_methods_pf.py +++ b/tests/client/test_methods_pf.py @@ -187,7 +187,7 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl): (AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)], False, None, - 63) + 63, '0x01') assert mock_ioctl.mock_calls == [ call(mock_pf_get_dev(), 0xC4704433, ANY), call(mock_pf_get_dev(), 0xCC20441A, ANY), @@ -227,7 +227,7 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl): (AF_INET, 32, True, u'1.2.3.66', 80, 80)], True, None, - 63) + 63, '0x01') assert str(excinfo.value) == 'UDP not supported by pf method_name' assert mock_pf_get_dev.mock_calls == [] assert mock_ioctl.mock_calls == [] @@ -241,7 +241,7 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl): (AF_INET, 32, True, u'1.2.3.66', 80, 80)], False, None, - 63) + 63, '0x01') assert mock_ioctl.mock_calls == [ call(mock_pf_get_dev(), 0xC4704433, ANY), call(mock_pf_get_dev(), 0xCC20441A, ANY), @@ -302,7 +302,7 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl, (AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)], False, None, - 63) + 63, '0x01') assert mock_pfctl.mock_calls == [ call('-s all'), @@ -335,7 +335,7 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl, (AF_INET, 32, True, u'1.2.3.66', 80, 80)], True, None, - 63) + 63, '0x01') assert str(excinfo.value) == 'UDP not supported by pf method_name' assert mock_pf_get_dev.mock_calls == [] assert mock_ioctl.mock_calls == [] @@ -349,7 +349,7 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl, (AF_INET, 32, True, u'1.2.3.66', 80, 80)], False, None, - 63) + 63, '0x01') assert mock_ioctl.mock_calls == [ call(mock_pf_get_dev(), 0xC4704433, ANY), call(mock_pf_get_dev(), 0xCBE0441A, ANY), @@ -408,7 +408,7 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl): (AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)], False, None, - 63) + 63, '0x01') assert mock_ioctl.mock_calls == [ call(mock_pf_get_dev(), 0xcd60441a, ANY), @@ -445,7 +445,7 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl): (AF_INET, 32, True, u'1.2.3.66', 80, 80)], True, None, - 63) + 63, '0x01') assert str(excinfo.value) == 'UDP not supported by pf method_name' assert mock_pf_get_dev.mock_calls == [] assert mock_ioctl.mock_calls == [] @@ -459,7 +459,7 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl): (AF_INET, 32, True, u'1.2.3.66', 80, 80)], False, None, - 63) + 63, '0x01') assert mock_ioctl.mock_calls == [ call(mock_pf_get_dev(), 0xcd60441a, ANY), call(mock_pf_get_dev(), 0xcd60441a, ANY), diff --git a/tests/client/test_methods_tproxy.py b/tests/client/test_methods_tproxy.py index 50c77e6..d3db207 100644 --- a/tests/client/test_methods_tproxy.py +++ b/tests/client/test_methods_tproxy.py @@ -109,7 +109,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt): (AF_INET6, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)], True, None, - 63) + 63, '0x01') assert mock_ipt_chain_exists.mock_calls == [ call(AF_INET6, 'mangle', 'sshuttle-m-1024'), call(AF_INET6, 'mangle', 'sshuttle-t-1024'), @@ -139,17 +139,17 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt): 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', @@ -165,17 +165,19 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt): '--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') ] @@ -214,7 +216,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt): (AF_INET, 32, True, u'1.2.3.66', 80, 80)], True, None, - 63) + 63, '0x01') assert mock_ipt_chain_exists.mock_calls == [ call(AF_INET, 'mangle', 'sshuttle-m-1025'), call(AF_INET, 'mangle', 'sshuttle-t-1025'), @@ -244,17 +246,17 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt): 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', @@ -269,16 +271,16 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt): '--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()