diff options
-rw-r--r-- | docs/manpage.rst | 19 | ||||
-rw-r--r-- | docs/overview.rst | 2 | ||||
-rw-r--r-- | docs/requirements.rst | 7 | ||||
-rw-r--r-- | sshuttle/client.py | 19 | ||||
-rw-r--r-- | sshuttle/cmdline.py | 10 | ||||
-rw-r--r-- | sshuttle/firewall.py | 20 | ||||
-rw-r--r-- | sshuttle/methods/nat.py | 20 | ||||
-rw-r--r-- | sshuttle/methods/pf.py | 43 | ||||
-rw-r--r-- | sshuttle/methods/tproxy.py | 39 | ||||
-rw-r--r-- | sshuttle/options.py | 122 | ||||
-rw-r--r-- | sshuttle/tests/client/test_firewall.py | 15 | ||||
-rw-r--r-- | sshuttle/tests/client/test_methods_nat.py | 15 | ||||
-rw-r--r-- | sshuttle/tests/client/test_methods_pf.py | 93 | ||||
-rw-r--r-- | sshuttle/tests/client/test_methods_tproxy.py | 34 | ||||
-rw-r--r-- | sshuttle/tests/client/test_options.py | 101 |
15 files changed, 353 insertions, 206 deletions
diff --git a/docs/manpage.rst b/docs/manpage.rst index fe6633a..44a178e 100644 --- a/docs/manpage.rst +++ b/docs/manpage.rst @@ -31,11 +31,18 @@ Options .. option:: subnets A list of subnets to route over the VPN, in the form - ``a.b.c.d[/width]``. Valid examples are 1.2.3.4 (a + ``a.b.c.d[/width][port[-port]]``. Valid examples are 1.2.3.4 (a single IP address), 1.2.3.4/32 (equivalent to 1.2.3.4), 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'). + 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. + It is also possible to use a name in which case the first IP it resolves + to during startup will be routed over the VPN. Valid examples are + example.com, example.com:8000 and example.com:8000-9000. .. option:: --method [auto|nat|tproxy|pf] @@ -54,9 +61,11 @@ Options connections from other machines on your network (ie. to run :program:`sshuttle` on a router) try enabling IP Forwarding in your kernel, then using ``--listen 0.0.0.0:0``. + You can use any name resolving to an IP address of the machine running + :program:`sshuttle`, e.g. ``--listen localhost``. - For the tproxy method this can be an IPv6 address. Use this option twice if - required, to provide both IPv4 and IPv6 addresses. + For the tproxy and pf methods this can be an IPv6 address. Use this option + twice if required, to provide both IPv4 and IPv6 addresses. .. option:: -H, --auto-hosts @@ -176,7 +185,7 @@ Options .. option:: --disable-ipv6 - If using the tproxy method, this will disable IPv6 support. + If using tproxy or pf methods, this will disable IPv6 support. .. option:: --firewall diff --git a/docs/overview.rst b/docs/overview.rst index dc32a80..a5f02c0 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -4,7 +4,7 @@ Overview As far as I know, sshuttle is the only program that solves the following common case: -- Your client machine (or router) is Linux, FreeBSD, or MacOS. +- Your client machine (or router) is Linux, MacOS, FreeBSD, OpenBSD or pfSense. - You have access to a remote network via ssh. diff --git a/docs/requirements.rst b/docs/requirements.rst index d32b348..9e9b54f 100644 --- a/docs/requirements.rst +++ b/docs/requirements.rst @@ -41,7 +41,7 @@ order to get the ``recvmsg()`` function. See :doc:`tproxy` for more information. -MacOS / FreeBSD / OpenBSD +MacOS / FreeBSD / OpenBSD / pfSense ~~~~~~~~~~~~~~~~~~~~~~~~~ Method: pf @@ -65,8 +65,9 @@ cmd.exe with Administrator access. See :doc:`windows` for more information. Server side Requirements ------------------------ -Server requirements are more relaxed, however it is recommended that you use -Python 2.7 or Python 3.5. +The server can run in any version of Python between 2.4 and 3.6. +However it is recommended that you use Python 2.7, Python 3.5 or later whenever +possible as support for older versions might be dropped in the future. Additional Suggested Software diff --git a/sshuttle/client.py b/sshuttle/client.py index e4d2470..d366db8 100644 --- a/sshuttle/client.py +++ b/sshuttle/client.py @@ -255,12 +255,13 @@ class FirewallClient: def start(self): self.pfile.write(b'ROUTES\n') - for (family, ip, width) in self.subnets_include + self.auto_nets: - self.pfile.write(b'%d,%d,0,%s\n' - % (family, width, ip.encode("ASCII"))) - for (family, ip, width) in self.subnets_exclude: - self.pfile.write(b'%d,%d,1,%s\n' - % (family, width, ip.encode("ASCII"))) + for (family, ip, width, fport, lport) \ + in self.subnets_include + self.auto_nets: + self.pfile.write(b'%d,%d,0,%s,%d,%d\n' + % (family, width, ip.encode("ASCII"), fport, lport)) + for (family, ip, width, fport, lport) in self.subnets_exclude: + self.pfile.write(b'%d,%d,1,%s,%d,%d\n' + % (family, width, ip.encode("ASCII"), fport, lport)) self.pfile.write(b'NSLIST\n') for (family, ip) in self.nslist: @@ -484,7 +485,7 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename, debug2("Ignored auto net %d/%s/%d\n" % (family, ip, width)) else: debug2("Adding auto net %d/%s/%d\n" % (family, ip, width)) - fw.auto_nets.append((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 # up intercepting the ssh connection! @@ -591,11 +592,11 @@ def main(listenip_v6, listenip_v4, if required.ipv4 and \ not any(listenip_v4[0] == sex[1] for sex in subnets_v4): - subnets_exclude.append((socket.AF_INET, listenip_v4[0], 32)) + subnets_exclude.append((socket.AF_INET, listenip_v4[0], 32, 0, 0)) if required.ipv6 and \ not any(listenip_v6[0] == sex[1] for sex in subnets_v6): - subnets_exclude.append((socket.AF_INET6, listenip_v6[0], 128)) + subnets_exclude.append((socket.AF_INET6, listenip_v6[0], 128, 0, 0)) 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 diff --git a/sshuttle/cmdline.py b/sshuttle/cmdline.py index 4c25e5b..7b34267 100644 --- a/sshuttle/cmdline.py +++ b/sshuttle/cmdline.py @@ -1,10 +1,11 @@ import re +import socket import sshuttle.helpers as helpers import sshuttle.client as client import sshuttle.firewall as firewall import sshuttle.hostwatch as hostwatch import sshuttle.ssyslog as ssyslog -from sshuttle.options import parser, parse_ipport6, parse_ipport4 +from sshuttle.options import parser, parse_ipport from sshuttle.helpers import family_ip_tuple, log, Fatal @@ -46,10 +47,11 @@ def main(): ipport_v4 = None list = opt.listen.split(",") for ip in list: - if '[' in ip and ']' in ip: - ipport_v6 = parse_ipport6(ip) + family, ip, port = parse_ipport(ip) + if family == socket.AF_INET6: + ipport_v6 = (ip, port) else: - ipport_v4 = parse_ipport4(ip) + ipport_v4 = (ip, port) else: # parse_ipport4('127.0.0.1:0') ipport_v4 = "auto" diff --git a/sshuttle/firewall.py b/sshuttle/firewall.py index 0e30e9c..4f37735 100644 --- a/sshuttle/firewall.py +++ b/sshuttle/firewall.py @@ -74,6 +74,15 @@ def setup_daemon(): return sys.stdin, sys.stdout +# Note that we're sorting in a very particular order: +# we need to go from most-specific (largest swidth) to least-specific, +# and at any given level of specificity, smaller port ranges come +# before larger port ranges. On ties excludes come first. +# s:(inet, subnet width, exclude flag, subnet, first port, last port) +def subnet_weight(s): + return (s[1], s[-2] or -65535 - s[-1], s[2]) + + # 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. @@ -119,10 +128,17 @@ def main(method_name, syslog): elif line.startswith("NSLIST\n"): break try: - (family, width, exclude, ip) = line.strip().split(',', 3) + (family, width, exclude, ip, fport, lport) = \ + line.strip().split(',', 5) except: raise Fatal('firewall: expected route or NSLIST but got %r' % line) - subnets.append((int(family), int(width), bool(int(exclude)), ip)) + subnets.append(( + int(family), + int(width), + bool(int(exclude)), + ip, + int(fport), + int(lport))) debug2('firewall manager: Got subnets: %r\n' % subnets) nslist = [] diff --git a/sshuttle/methods/nat.py b/sshuttle/methods/nat.py index c5afc03..a5e7b96 100644 --- a/sshuttle/methods/nat.py +++ b/sshuttle/methods/nat.py @@ -1,4 +1,5 @@ import socket +from sshuttle.firewall import subnet_weight from sshuttle.helpers import family_to_string from sshuttle.linux import ipt, ipt_ttl, ipt_chain_exists, nonfatal from sshuttle.methods import BaseMethod @@ -38,22 +39,21 @@ class Method(BaseMethod): _ipt('-I', 'OUTPUT', '1', '-j', chain) _ipt('-I', 'PREROUTING', '1', '-j', chain) - # create new subnet entries. Note that we're sorting in a very - # particular order: we need to go from most-specific (largest - # swidth) to least-specific, and at any given level of specificity, - # we want excludes to come first. That's why the columns are in - # such a non- intuitive order. - for f, swidth, sexclude, snet \ - in sorted(subnets, key=lambda s: s[1], reverse=True): + # create new subnet entries. + for f, swidth, sexclude, snet, fport, lport \ + in sorted(subnets, key=subnet_weight, reverse=True): + tcp_ports = ('-p', 'tcp') + if fport: + tcp_ports = tcp_ports + ('--dport', '%d:%d' % (fport, lport)) + if sexclude: _ipt('-A', chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), - '-p', 'tcp') + *tcp_ports) else: _ipt_ttl('-A', chain, '-j', 'REDIRECT', '--dest', '%s/%s' % (snet, swidth), - '-p', 'tcp', - '--to-ports', str(port)) + *(tcp_ports + ('--to-ports', str(port)))) for f, ip in [i for i in nslist if i[0] == family]: _ipt_ttl('-A', chain, '-j', 'REDIRECT', diff --git a/sshuttle/methods/pf.py b/sshuttle/methods/pf.py index 5888b47..28d7ddd 100644 --- a/sshuttle/methods/pf.py +++ b/sshuttle/methods/pf.py @@ -9,6 +9,7 @@ import shlex 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 from sshuttle.methods import BaseMethod @@ -186,16 +187,18 @@ class FreeBsd(Generic): inet_version = self._inet_version(family) lo_addr = self._lo_addr(family) - tables = [ - b'table <forward_subnets> {%s}' % b','.join(includes) - ] + tables = [] translating_rules = [ - b'rdr pass on lo0 %s proto tcp to <forward_subnets> ' - b'-> %s port %r' % (inet_version, lo_addr, port) + b'rdr pass on lo0 %s proto tcp to %s ' + b'-> %s port %r' % (inet_version, subnet, lo_addr, port) + for exclude, subnet in includes if not exclude ] filtering_rules = [ b'pass out route-to lo0 %s proto tcp ' - b'to <forward_subnets> keep state' % inet_version + b'to %s keep state' % (inet_version, subnet) + if not exclude else + b'pass out quick %s proto tcp to %s' % (inet_version, subnet) + for exclude, subnet in includes ] if len(nslist) > 0: @@ -254,16 +257,18 @@ class OpenBsd(Generic): inet_version = self._inet_version(family) lo_addr = self._lo_addr(family) - tables = [ - b'table <forward_subnets> {%s}' % b','.join(includes) - ] + tables = [] translating_rules = [ - b'pass in on lo0 %s proto tcp to <forward_subnets> ' - b'divert-to %s port %r' % (inet_version, lo_addr, port) + b'pass in on lo0 %s proto tcp to %s ' + b'divert-to %s port %r' % (inet_version, subnet, lo_addr, port) + for exclude, subnet in includes if not exclude ] filtering_rules = [ - b'pass out %s proto tcp to <forward_subnets> ' - b'route-to lo0 keep state' % inet_version + b'pass out %s proto tcp to %s ' + b'route-to lo0 keep state' % (inet_version, subnet) + if not exclude else + b'pass out quick %s proto tcp to %s' % (inet_version, subnet) + for exclude, subnet in includes ] if len(nslist) > 0: @@ -429,12 +434,12 @@ class Method(BaseMethod): # If a given subnet is both included and excluded, list the # exclusion first; the table will ignore the second, opposite # definition - for f, swidth, sexclude, snet in sorted( - subnets, key=lambda s: (s[1], s[2]), reverse=True): - includes.append(b"%s%s/%d" % - (b"!" if sexclude else b"", - snet.encode("ASCII"), - swidth)) + for f, swidth, sexclude, snet, fport, lport \ + in sorted(subnets, key=subnet_weight, reverse=True): + includes.append((sexclude, b"%s/%d%s" % ( + snet.encode("ASCII"), + swidth, + b" port %d:%d" % (fport, lport) if fport else b""))) anchor = pf_get_anchor(family, port) pf.add_anchors(anchor) diff --git a/sshuttle/methods/tproxy.py b/sshuttle/methods/tproxy.py index 5094962..44b8fd7 100644 --- a/sshuttle/methods/tproxy.py +++ b/sshuttle/methods/tproxy.py @@ -1,4 +1,5 @@ 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.methods import BaseMethod @@ -163,6 +164,11 @@ class Method(BaseMethod): 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 + + mark_chain = 'sshuttle-m-%s' % port tproxy_chain = 'sshuttle-t-%s' % port divert_chain = 'sshuttle-d-%s' % port @@ -197,33 +203,44 @@ class Method(BaseMethod): '-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', str(dnsport)) - for f, swidth, sexclude, snet \ - in sorted(subnets, key=lambda s: s[1], reverse=True): + for f, swidth, sexclude, snet, fport, lport \ + in sorted(subnets, key=subnet_weight, reverse=True): + tcp_ports = ('-p', 'tcp') + tcp_ports = _ipt_proto_ports(tcp_ports, fport, lport) + if sexclude: _ipt('-A', mark_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), - '-m', 'tcp', '-p', 'tcp') + '-m', 'tcp', + *tcp_ports) _ipt('-A', tproxy_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), - '-m', 'tcp', '-p', 'tcp') + '-m', 'tcp', + *tcp_ports) else: _ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1', '--dest', '%s/%s' % (snet, swidth), - '-m', 'tcp', '-p', 'tcp') + '-m', 'tcp', + *tcp_ports) _ipt('-A', tproxy_chain, '-j', 'TPROXY', '--tproxy-mark', '0x1/0x1', '--dest', '%s/%s' % (snet, swidth), - '-m', 'tcp', '-p', 'tcp', - '--on-port', str(port)) + '-m', 'tcp', + *(tcp_ports + ('--on-port', str(port)))) if udp: + udp_ports = ('-p', 'udp') + udp_ports = _ipt_proto_ports(udp_ports, fport, lport) + if sexclude: _ipt('-A', mark_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), - '-m', 'udp', '-p', 'udp') + '-m', 'udp', + *udp_ports) _ipt('-A', tproxy_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), - '-m', 'udp', '-p', 'udp') + '-m', 'udp', + *udp_ports) else: _ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1', '--dest', '%s/%s' % (snet, swidth), @@ -231,8 +248,8 @@ class Method(BaseMethod): _ipt('-A', tproxy_chain, '-j', 'TPROXY', '--tproxy-mark', '0x1/0x1', '--dest', '%s/%s' % (snet, swidth), - '-m', 'udp', '-p', 'udp', - '--on-port', str(port)) + '-m', 'udp', + *(udp_ports + ('--on-port', str(port)))) def restore_firewall(self, port, family, udp): if family not in [socket.AF_INET, socket.AF_INET6]: diff --git a/sshuttle/options.py b/sshuttle/options.py index d97d7ae..659c014 100644 --- a/sshuttle/options.py +++ b/sshuttle/options.py @@ -4,41 +4,8 @@ from argparse import ArgumentParser, Action, ArgumentTypeError as Fatal from sshuttle import __version__ -# 1.2.3.4/5 or just 1.2.3.4 -def parse_subnet4(s): - m = re.match(r'(\d+)(?:\.(\d+)\.(\d+)\.(\d+))?(?:/(\d+))?$', s) - if not m: - raise Fatal('%r is not a valid IP subnet format' % s) - (a, b, c, d, width) = m.groups() - (a, b, c, d) = (int(a or 0), int(b or 0), int(c or 0), int(d or 0)) - if width is None: - width = 32 - else: - width = int(width) - if a > 255 or b > 255 or c > 255 or d > 255: - raise Fatal('%d.%d.%d.%d has numbers > 255' % (a, b, c, d)) - if width > 32: - raise Fatal('*/%d is greater than the maximum of 32' % width) - return(socket.AF_INET, '%d.%d.%d.%d' % (a, b, c, d), width) - - -# 1:2::3/64 or just 1:2::3 -def parse_subnet6(s): - m = re.match(r'(?:([a-fA-F\d:]+))?(?:/(\d+))?$', s) - if not m: - raise Fatal('%r is not a valid IP subnet format' % s) - (net, width) = m.groups() - if width is None: - width = 128 - else: - width = int(width) - if width > 128: - raise Fatal('*/%d is greater than the maximum of 128' % width) - return(socket.AF_INET6, net, width) - - # Subnet file, supporting empty lines and hash-started comment lines -def parse_subnet_file(s): +def parse_subnetport_file(s): try: handle = open(s, 'r') except OSError: @@ -52,47 +19,66 @@ def parse_subnet_file(s): continue if line[0] == '#': continue - subnets.append(parse_subnet(line)) + subnets.append(parse_subnetport(line)) return subnets -# 1.2.3.4/5 or just 1.2.3.4 -# 1:2::3/64 or just 1:2::3 -def parse_subnet(subnet_str): - if ':' in subnet_str: - return parse_subnet6(subnet_str) +# 1.2.3.4/5:678, 1.2.3.4:567, 1.2.3.4/16 or just 1.2.3.4 +# [1:2::3/64]:456, [1:2::3]:456, 1:2::3/64 or just 1:2::3 +# example.com:123 or just example.com +def parse_subnetport(s): + if s.count(':') > 1: + rx = r'(?:\[?([\w\:]+)(?:/(\d+))?]?)(?::(\d+)(?:-(\d+))?)?$' else: - return parse_subnet4(subnet_str) + rx = r'([\w\.]+)(?:/(\d+))?(?::(\d+)(?:-(\d+))?)?$' + + m = re.match(rx, s) + if not m: + raise Fatal('%r is not a valid address/mask:port format' % s) + + addr, width, fport, lport = m.groups() + try: + addrinfo = socket.getaddrinfo(addr, 0, 0, socket.SOCK_STREAM) + except socket.gaierror: + raise Fatal('Unable to resolve address: %s' % addr) + + family, _, _, _, addr = min(addrinfo) + max_width = 32 if family == socket.AF_INET else 128 + width = int(width or max_width) + if not 0 <= width <= max_width: + raise Fatal('width %d is not between 0 and %d' % (width, max_width)) + + return (family, addr[0], width, int(fport or 0), int(lport or fport or 0)) # 1.2.3.4:567 or just 1.2.3.4 or just 567 -def parse_ipport4(s): +# [1:2::3]:456 or [1:2::3] or just [::]:567 +# example.com:123 or just example.com +def parse_ipport(s): s = str(s) - m = re.match(r'(?:(\d+)\.(\d+)\.(\d+)\.(\d+))?(?::)?(?:(\d+))?$', s) + if s.isdigit(): + rx = r'()(\d+)$' + elif ']' in s: + rx = r'(?:\[([^]]+)])(?::(\d+))?$' + else: + rx = r'([\w\.]+)(?::(\d+))?$' + + m = re.match(rx, s) if not m: raise Fatal('%r is not a valid IP:port format' % s) - (a, b, c, d, port) = m.groups() - (a, b, c, d, port) = (int(a or 0), int(b or 0), int(c or 0), int(d or 0), - int(port or 0)) - if a > 255 or b > 255 or c > 255 or d > 255: - raise Fatal('%d.%d.%d.%d has numbers > 255' % (a, b, c, d)) - if port > 65535: - raise Fatal('*:%d is greater than the maximum of 65535' % port) - if a is None: - a = b = c = d = 0 - return ('%d.%d.%d.%d' % (a, b, c, d), port) + ip, port = m.groups() + ip = ip or '0.0.0.0' + port = int(port or 0) -# [1:2::3]:456 or [1:2::3] or 456 -def parse_ipport6(s): - s = str(s) - m = re.match(r'(?:\[([^]]*)])?(?::)?(?:(\d+))?$', s) - if not m: - raise Fatal('%s is not a valid IP:port format' % s) - (ip, port) = m.groups() - (ip, port) = (ip or '::', int(port or 0)) - return (ip, port) + try: + addrinfo = socket.getaddrinfo(ip, port, 0, socket.SOCK_STREAM) + except socket.gaierror: + raise Fatal('%r is not a valid IP:port format' % s) + + family, _, _, _, addr = min(addrinfo) + return (family,) + addr[:2] def parse_list(list): @@ -116,9 +102,9 @@ parser = ArgumentParser( ) parser.add_argument( "subnets", - metavar="IP/MASK [IP/MASK...]", + metavar="IP/MASK[:PORT[-PORT]]...", nargs="*", - type=parse_subnet, + type=parse_subnetport, help=""" capture and forward traffic to these subnets (whitespace separated) """ @@ -185,10 +171,10 @@ parser.add_argument( ) parser.add_argument( "-x", "--exclude", - metavar="IP/MASK", + metavar="IP/MASK[:PORT[-PORT]]", action="append", default=[], - type=parse_subnet, + type=parse_subnetport, help=""" exclude this subnet (can be used more than once) """ @@ -198,7 +184,7 @@ parser.add_argument( metavar="PATH", action=Concat, dest="exclude", - type=parse_subnet_file, + type=parse_subnetport_file, help=""" exclude the subnets in a file (whitespace separated) """ @@ -271,7 +257,7 @@ parser.add_argument( action=Concat, dest="subnets_file", default=[], - type=parse_subnet_file, + type=parse_subnetport_file, help=""" file where the subnets are stored, instead of on the command line """ diff --git a/sshuttle/tests/client/test_firewall.py b/sshuttle/tests/client/test_firewall.py index fb4ba40..b61a23e 100644 --- a/sshuttle/tests/client/test_firewall.py +++ b/sshuttle/tests/client/test_firewall.py @@ -6,10 +6,10 @@ import sshuttle.firewall def setup_daemon(): stdin = io.StringIO(u"""ROUTES -2,24,0,1.2.3.0 -2,32,1,1.2.3.66 -10,64,0,2404:6800:4004:80c:: -10,128,1,2404:6800:4004:80c::101f +2,24,0,1.2.3.0,8000,9000 +2,32,1,1.2.3.66,8080,8080 +10,64,0,2404:6800:4004:80c::,0,0 +10,128,1,2404:6800:4004:80c::101f,80,80 NSLIST 2,1.2.3.33 10,2404:6800:4004:80c::33 @@ -88,14 +88,15 @@ def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts): 1024, 1026, [(10, u'2404:6800:4004:80c::33')], 10, - [(10, 64, False, u'2404:6800:4004:80c::'), - (10, 128, True, u'2404:6800:4004:80c::101f')], + [(10, 64, False, u'2404:6800:4004:80c::', 0, 0), + (10, 128, True, u'2404:6800:4004:80c::101f', 80, 80)], True), call().setup_firewall( 1025, 1027, [(2, u'1.2.3.33')], 2, - [(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')], + [(2, 24, False, u'1.2.3.0', 8000, 9000), + (2, 32, True, u'1.2.3.66', 8080, 8080)], True), call().restore_firewall(1024, 10, True), call().restore_firewall(1025, 2, True), diff --git a/sshuttle/tests/client/test_methods_nat.py b/sshuttle/tests/client/test_methods_nat.py index 2144e25..4ae571b 100644 --- a/sshuttle/tests/client/test_methods_nat.py +++ b/sshuttle/tests/client/test_methods_nat.py @@ -86,8 +86,8 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt): 1024, 1026, [(10, u'2404:6800:4004:80c::33')], 10, - [(10, 64, False, u'2404:6800:4004:80c::'), - (10, 128, True, u'2404:6800:4004:80c::101f')], + [(10, 64, False, u'2404:6800:4004:80c::', 0, 0), + (10, 128, True, u'2404:6800:4004:80c::101f', 80, 80)], True) assert str(excinfo.value) \ == 'Address family "AF_INET6" unsupported by nat method_name' @@ -100,7 +100,8 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt): 1025, 1027, [(2, u'1.2.3.33')], 2, - [(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')], + [(2, 24, False, u'1.2.3.0', 8000, 9000), + (2, 32, True, u'1.2.3.66', 8080, 8080)], True) assert str(excinfo.value) == 'UDP not supported by nat method_name' assert mock_ipt_chain_exists.mock_calls == [] @@ -111,14 +112,16 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt): 1025, 1027, [(2, u'1.2.3.33')], 2, - [(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')], + [(2, 24, False, u'1.2.3.0', 8000, 9000), + (2, 32, True, u'1.2.3.66', 8080, 8080)], False) assert mock_ipt_chain_exists.mock_calls == [ call(2, 'nat', 'sshuttle-1025') ] assert mock_ipt_ttl.mock_calls == [ call(2, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT', - '--dest', u'1.2.3.0/24', '-p', 'tcp', '--to-ports', '1025'), + '--dest', u'1.2.3.0/24', '-p', 'tcp', '--dport', '8000:9000', + '--to-ports', '1025'), call(2, 'nat', '-A', 'sshuttle-1025', '-j', 'REDIRECT', '--dest', u'1.2.3.33/32', '-p', 'udp', '--dport', '53', '--to-ports', '1027') @@ -133,7 +136,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt): call(2, 'nat', '-I', 'OUTPUT', '1', '-j', 'sshuttle-1025'), call(2, 'nat', '-I', 'PREROUTING', '1', '-j', 'sshuttle-1025'), call(2, 'nat', '-A', 'sshuttle-1025', '-j', 'RETURN', - '--dest', u'1.2.3.66/32', '-p', 'tcp') + '--dest', u'1.2.3.66/32', '-p', 'tcp', '--dport', '8080:8080') ] mock_ipt_chain_exists.reset_mock() mock_ipt_ttl.reset_mock() diff --git a/sshuttle/tests/client/test_methods_pf.py b/sshuttle/tests/client/test_methods_pf.py index 4ec6fc5..5df57af 100644 --- a/sshuttle/tests/client/test_methods_pf.py +++ b/sshuttle/tests/client/test_methods_pf.py @@ -182,8 +182,8 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl): 1024, 1026, [(10, u'2404:6800:4004:80c::33')], 10, - [(10, 64, False, u'2404:6800:4004:80c::'), - (10, 128, True, u'2404:6800:4004:80c::101f')], + [(10, 64, False, u'2404:6800:4004:80c::', 8000, 9000), + (10, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)], False) assert mock_ioctl.mock_calls == [ call(mock_pf_get_dev(), 0xC4704433, ANY), @@ -198,16 +198,15 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl): call('-f /dev/stdin', b'pass on lo\n'), call('-s all'), call('-a sshuttle6-1024 -f /dev/stdin', - b'table <forward_subnets> {' - b'!2404:6800:4004:80c::101f/128,2404:6800:4004:80c::/64' - b'}\n' b'table <dns_servers> {2404:6800:4004:80c::33}\n' - b'rdr pass on lo0 inet6 proto tcp ' - b'to <forward_subnets> -> ::1 port 1024\n' + b'rdr pass on lo0 inet6 proto tcp to ' + b'2404:6800:4004:80c::/64 port 8000:9000 -> ::1 port 1024\n' b'rdr pass on lo0 inet6 proto udp ' b'to <dns_servers> port 53 -> ::1 port 1026\n' - b'pass out route-to lo0 inet6 proto tcp ' - b'to <forward_subnets> keep state\n' + b'pass out quick inet6 proto tcp to ' + b'2404:6800:4004:80c::101f/128 port 8080:8080\n' + b'pass out route-to lo0 inet6 proto tcp to ' + b'2404:6800:4004:80c::/64 port 8000:9000 keep state\n' b'pass out route-to lo0 inet6 proto udp ' b'to <dns_servers> port 53 keep state\n'), call('-E'), @@ -221,7 +220,8 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl): 1025, 1027, [(2, u'1.2.3.33')], 2, - [(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')], + [(2, 24, False, u'1.2.3.0', 0, 0), + (2, 32, True, u'1.2.3.66', 80, 80)], True) assert str(excinfo.value) == 'UDP not supported by pf method_name' assert mock_pf_get_dev.mock_calls == [] @@ -232,7 +232,7 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl): 1025, 1027, [(2, u'1.2.3.33')], 2, - [(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')], + [(2, 24, False, u'1.2.3.0', 0, 0), (2, 32, True, u'1.2.3.66', 80, 80)], False) assert mock_ioctl.mock_calls == [ call(mock_pf_get_dev(), 0xC4704433, ANY), @@ -247,14 +247,13 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl): call('-f /dev/stdin', b'pass on lo\n'), call('-s all'), call('-a sshuttle-1025 -f /dev/stdin', - b'table <forward_subnets> {!1.2.3.66/32,1.2.3.0/24}\n' b'table <dns_servers> {1.2.3.33}\n' - b'rdr pass on lo0 inet proto tcp ' - b'to <forward_subnets> -> 127.0.0.1 port 1025\n' + b'rdr pass on lo0 inet proto tcp to 1.2.3.0/24 ' + b'-> 127.0.0.1 port 1025\n' b'rdr pass on lo0 inet proto udp ' b'to <dns_servers> port 53 -> 127.0.0.1 port 1027\n' - b'pass out route-to lo0 inet proto tcp ' - b'to <forward_subnets> keep state\n' + b'pass out quick inet proto tcp to 1.2.3.66/32 port 80:80\n' + b'pass out route-to lo0 inet proto tcp to 1.2.3.0/24 keep state\n' b'pass out route-to lo0 inet proto udp ' b'to <dns_servers> port 53 keep state\n'), call('-E'), @@ -289,23 +288,22 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl): 1024, 1026, [(10, u'2404:6800:4004:80c::33')], 10, - [(10, 64, False, u'2404:6800:4004:80c::'), - (10, 128, True, u'2404:6800:4004:80c::101f')], + [(10, 64, False, u'2404:6800:4004:80c::', 8000, 9000), + (10, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)], False) assert mock_pfctl.mock_calls == [ call('-s all'), call('-a sshuttle6-1024 -f /dev/stdin', - b'table <forward_subnets> {' - b'!2404:6800:4004:80c::101f/128,2404:6800:4004:80c::/64' - b'}\n' b'table <dns_servers> {2404:6800:4004:80c::33}\n' - b'rdr pass on lo0 inet6 proto tcp ' - b'to <forward_subnets> -> ::1 port 1024\n' + b'rdr pass on lo0 inet6 proto tcp to 2404:6800:4004:80c::/64 ' + b'port 8000:9000 -> ::1 port 1024\n' b'rdr pass on lo0 inet6 proto udp ' b'to <dns_servers> port 53 -> ::1 port 1026\n' - b'pass out route-to lo0 inet6 proto tcp ' - b'to <forward_subnets> keep state\n' + b'pass out quick inet6 proto tcp to ' + b'2404:6800:4004:80c::101f/128 port 8080:8080\n' + b'pass out route-to lo0 inet6 proto tcp to ' + b'2404:6800:4004:80c::/64 port 8000:9000 keep state\n' b'pass out route-to lo0 inet6 proto udp ' b'to <dns_servers> port 53 keep state\n'), call('-e'), @@ -319,7 +317,8 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl): 1025, 1027, [(2, u'1.2.3.33')], 2, - [(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')], + [(2, 24, False, u'1.2.3.0', 0, 0), + (2, 32, True, u'1.2.3.66', 80, 80)], True) assert str(excinfo.value) == 'UDP not supported by pf method_name' assert mock_pf_get_dev.mock_calls == [] @@ -330,7 +329,7 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl): 1025, 1027, [(2, u'1.2.3.33')], 2, - |