mirror of
https://github.com/sshuttle/sshuttle.git
synced 2024-11-21 23:43:18 +01:00
Adds support for tunneling specific port ranges (#144)
* Adds support for tunneling specific port ranges This set of changes implements the ability of specifying a port or port range for an IP or subnet to only tunnel those ports for that subnet. Also supports excluding a port or port range for a given IP or subnet. When, for a given subnet, there are intercepting ranges being added and excluded, the most specific, i.e., smaller range, takes precedence. In case of a tie the exclusion wins. For different subnets, the most specific, i.e., largest swidth, takes precedence independent of any eventual port ranges. Examples: Tunnels all traffic to the 188.0.0.0/8 subnet except those to port 443. ``` sshuttle -r <server> 188.0.0.0/8 -x 188.0.0.0/8:443 ``` Only tunnels traffic to port 80 of the 188.0.0.0/8 subnet. ``` sshuttle -r <server> 188.0.0.0/8:80 ``` Tunnels traffic to the 188.0.0.0/8 subnet and the port range that goes from 80 to 89. ``` sshuttle -r <server> 188.0.0.0/8:80-89 -x 188.0.0.0/8:80-90 ``` * Allow subnets to be specified with domain names Simplifies the implementation of address parsing by using socket.getaddrinfo(), which can handle domain resolution, IPv4 and IPv6 addresses. This was proposed and mostly implemented by @DavidBuchanan314 in #146. Signed-off-by: David Buchanan <DavidBuchanan314@users.noreply.github.com> Signed-off-by: João Vieira <vieira@yubo.be> * Also use getaddrinfo for parsing listen addr:port * Fixes tests for tunneling a port range * Updates documentation to include port/port range Adds some examples with subnet:port and subnet:port-port. Also clarifies the versions of Python supported on the server while maintaining the recommendation for Python 2.7, 3.5 or later. Mentions support for pfSense. * In Py2 only named arguments may follow *expression Fixes issue in Python 2.7 where *expression may only be followed by named arguments. * Use right regex to extract ip4/6, mask and ports * Tests for parse_subnetport
This commit is contained in:
parent
ef83a5c573
commit
c4a41ada09
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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 = []
|
||||
|
@ -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',
|
||||
|
@ -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"",
|
||||
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))
|
||||
swidth,
|
||||
b" port %d:%d" % (fport, lport) if fport else b"")))
|
||||
|
||||
anchor = pf_get_anchor(family, port)
|
||||
pf.add_anchors(anchor)
|
||||
|
@ -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]:
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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),
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
[(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),
|
||||
@ -343,14 +342,13 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
assert mock_pfctl.mock_calls == [
|
||||
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'),
|
||||
@ -385,8 +383,8 @@ def test_setup_firewall_openbsd(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 == [
|
||||
@ -398,16 +396,15 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
call('-f /dev/stdin', b'match 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'pass in on lo0 inet6 proto tcp to '
|
||||
b'<forward_subnets> divert-to ::1 port 1024\n'
|
||||
b'pass in on lo0 inet6 proto tcp to 2404:6800:4004:80c::/64 '
|
||||
b'port 8000:9000 divert-to ::1 port 1024\n'
|
||||
b'pass in on lo0 inet6 proto udp '
|
||||
b'to <dns_servers> port 53 rdr-to ::1 port 1026\n'
|
||||
b'pass out inet6 proto tcp to '
|
||||
b'<forward_subnets> route-to lo0 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 inet6 proto tcp to 2404:6800:4004:80c::/64 '
|
||||
b'port 8000:9000 route-to lo0 keep state\n'
|
||||
b'pass out inet6 proto udp to '
|
||||
b'<dns_servers> port 53 route-to lo0 keep state\n'),
|
||||
call('-e'),
|
||||
@ -421,7 +418,8 @@ def test_setup_firewall_openbsd(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 == []
|
||||
@ -432,7 +430,8 @@ def test_setup_firewall_openbsd(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(), 0xcd48441a, ANY),
|
||||
@ -443,13 +442,13 @@ def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
call('-f /dev/stdin', b'match 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'pass in on lo0 inet proto tcp to <forward_subnets> divert-to 127.0.0.1 port 1025\n'
|
||||
b'pass in on lo0 inet proto tcp to 1.2.3.0/24 divert-to '
|
||||
b'127.0.0.1 port 1025\n'
|
||||
b'pass in on lo0 inet proto udp to '
|
||||
b'<dns_servers> port 53 rdr-to 127.0.0.1 port 1027\n'
|
||||
b'pass out inet proto tcp to '
|
||||
b'<forward_subnets> route-to lo0 keep state\n'
|
||||
b'pass out quick inet proto tcp to 1.2.3.66/32 port 80:80\n'
|
||||
b'pass out inet proto tcp to 1.2.3.0/24 route-to lo0 keep state\n'
|
||||
b'pass out inet proto udp to '
|
||||
b'<dns_servers> port 53 route-to lo0 keep state\n'),
|
||||
call('-e'),
|
||||
|
@ -102,8 +102,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::', 8000, 9000),
|
||||
(10, 128, True, u'2404:6800:4004:80c::101f', 8080, 8080)],
|
||||
True)
|
||||
assert mock_ipt_chain_exists.mock_calls == [
|
||||
call(10, 'mangle', 'sshuttle-m-1024'),
|
||||
@ -144,28 +144,30 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
||||
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1026'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
|
||||
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||
'-m', 'tcp', '-p', 'tcp'),
|
||||
'-m', 'tcp', '-p', 'tcp', '--dport', '8080:8080'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'RETURN',
|
||||
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||
'-m', 'tcp', '-p', 'tcp'),
|
||||
'-m', 'tcp', '-p', 'tcp', '--dport', '8080:8080'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'RETURN',
|
||||
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||
'-m', 'udp', '-p', 'udp'),
|
||||
'-m', 'udp', '-p', 'udp', '--dport', '8080:8080'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'RETURN',
|
||||
'--dest', u'2404:6800:4004:80c::101f/128',
|
||||
'-m', 'udp', '-p', 'udp'),
|
||||
'-m', 'udp', '-p', 'udp', '--dport', '8080:8080'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
|
||||
'--set-mark', '1', '--dest', u'2404:6800:4004:80c::/64',
|
||||
'-m', 'tcp', '-p', 'tcp'),
|
||||
'-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
|
||||
'--tproxy-mark', '0x1/0x1', '--dest', u'2404:6800:4004:80c::/64',
|
||||
'-m', 'tcp', '-p', 'tcp', '--on-port', '1024'),
|
||||
'-m', 'tcp', '-p', 'tcp', '--dport', '8000:9000',
|
||||
'--on-port', '1024'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-m-1024', '-j', 'MARK',
|
||||
'--set-mark', '1', '--dest', u'2404:6800:4004:80c::/64',
|
||||
'-m', 'udp', '-p', 'udp'),
|
||||
call(10, 'mangle', '-A', 'sshuttle-t-1024', '-j', 'TPROXY',
|
||||
'--tproxy-mark', '0x1/0x1', '--dest', u'2404:6800:4004:80c::/64',
|
||||
'-m', 'udp', '-p', 'udp', '--on-port', '1024')
|
||||
'-m', 'udp', '-p', 'udp', '--dport', '8000:9000',
|
||||
'--on-port', '1024')
|
||||
]
|
||||
mock_ipt_chain_exists.reset_mock()
|
||||
mock_ipt_ttl.reset_mock()
|
||||
@ -198,7 +200,7 @@ 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', 0, 0), (2, 32, True, u'1.2.3.66', 80, 80)],
|
||||
True)
|
||||
assert mock_ipt_chain_exists.mock_calls == [
|
||||
call(2, 'mangle', 'sshuttle-m-1025'),
|
||||
@ -237,13 +239,17 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
||||
'--tproxy-mark', '0x1/0x1', '--dest', u'1.2.3.33/32',
|
||||
'-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', '1027'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
||||
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp'),
|
||||
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp',
|
||||
'--dport', '80:80'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'RETURN',
|
||||
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp'),
|
||||
'--dest', u'1.2.3.66/32', '-m', 'tcp', '-p', 'tcp',
|
||||
'--dport', '80:80'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'RETURN',
|
||||
'--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp'),
|
||||
'--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp',
|
||||
'--dport', '80:80'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-t-1025', '-j', 'RETURN',
|
||||
'--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp'),
|
||||
'--dest', u'1.2.3.66/32', '-m', 'udp', '-p', 'udp',
|
||||
'--dport', '80:80'),
|
||||
call(2, 'mangle', '-A', 'sshuttle-m-1025', '-j', 'MARK',
|
||||
'--set-mark', '1', '--dest', u'1.2.3.0/24',
|
||||
'-m', 'tcp', '-p', 'tcp'),
|
||||
|
101
sshuttle/tests/client/test_options.py
Normal file
101
sshuttle/tests/client/test_options.py
Normal file
@ -0,0 +1,101 @@
|
||||
import socket
|
||||
import pytest
|
||||
import sshuttle.options
|
||||
from argparse import ArgumentTypeError as Fatal
|
||||
|
||||
_ip4_reprs = {
|
||||
'0.0.0.0': '0.0.0.0',
|
||||
'255.255.255.255': '255.255.255.255',
|
||||
'10.0': '10.0.0.0',
|
||||
'184.172.10.74': '184.172.10.74',
|
||||
'3098282570': '184.172.10.74',
|
||||
'0xb8.0xac.0x0a.0x4a': '184.172.10.74',
|
||||
'0270.0254.0012.0112': '184.172.10.74',
|
||||
'localhost': '127.0.0.1'
|
||||
}
|
||||
|
||||
_ip4_swidths = (1, 8, 22, 27, 32)
|
||||
|
||||
_ip6_reprs = {
|
||||
'::': '::',
|
||||
'::1': '::1',
|
||||
'fc00::': 'fc00::',
|
||||
'2a01:7e00:e000:188::1': '2a01:7e00:e000:188::1'
|
||||
}
|
||||
|
||||
_ip6_swidths = (48, 64, 96, 115, 128)
|
||||
|
||||
def test_parse_subnetport_ip4():
|
||||
for ip_repr, ip in _ip4_reprs.items():
|
||||
assert sshuttle.options.parse_subnetport(ip_repr) \
|
||||
== (socket.AF_INET, ip, 32, 0, 0)
|
||||
with pytest.raises(Fatal) as excinfo:
|
||||
sshuttle.options.parse_subnetport('10.256.0.0')
|
||||
assert str(excinfo.value) == 'Unable to resolve address: 10.256.0.0'
|
||||
|
||||
|
||||
def test_parse_subnetport_ip4_with_mask():
|
||||
for ip_repr, ip in _ip4_reprs.items():
|
||||
for swidth in _ip4_swidths:
|
||||
assert sshuttle.options.parse_subnetport(
|
||||
'/'.join((ip_repr, str(swidth)))
|
||||
) == (socket.AF_INET, ip, swidth, 0, 0)
|
||||
assert sshuttle.options.parse_subnetport('0/0') \
|
||||
== (socket.AF_INET, '0.0.0.0', 0, 0, 0)
|
||||
with pytest.raises(Fatal) as excinfo:
|
||||
sshuttle.options.parse_subnetport('10.0.0.0/33')
|
||||
assert str(excinfo.value) == 'width 33 is not between 0 and 32'
|
||||
|
||||
|
||||
def test_parse_subnetport_ip4_with_port():
|
||||
for ip_repr, ip in _ip4_reprs.items():
|
||||
assert sshuttle.options.parse_subnetport(':'.join((ip_repr, '80'))) \
|
||||
== (socket.AF_INET, ip, 32, 80, 80)
|
||||
assert sshuttle.options.parse_subnetport(':'.join((ip_repr, '80-90'))) \
|
||||
== (socket.AF_INET, ip, 32, 80, 90)
|
||||
|
||||
|
||||
def test_parse_subnetport_ip4_with_mask_and_port():
|
||||
for ip_repr, ip in _ip4_reprs.items():
|
||||
assert sshuttle.options.parse_subnetport(ip_repr + '/32:80') \
|
||||
== (socket.AF_INET, ip, 32, 80, 80)
|
||||
assert sshuttle.options.parse_subnetport(ip_repr + '/16:80-90') \
|
||||
== (socket.AF_INET, ip, 16, 80, 90)
|
||||
|
||||
|
||||
def test_parse_subnetport_ip6():
|
||||
for ip_repr, ip in _ip6_reprs.items():
|
||||
assert sshuttle.options.parse_subnetport(ip_repr) \
|
||||
== (socket.AF_INET6, ip, 128, 0, 0)
|
||||
with pytest.raises(Fatal) as excinfo:
|
||||
sshuttle.options.parse_subnetport('2001::1::3f')
|
||||
assert str(excinfo.value) == 'Unable to resolve address: 2001::1::3f'
|
||||
|
||||
|
||||
def test_parse_subnetport_ip6_with_mask():
|
||||
for ip_repr, ip in _ip6_reprs.items():
|
||||
for swidth in _ip4_swidths + _ip6_swidths:
|
||||
assert sshuttle.options.parse_subnetport(
|
||||
'/'.join((ip_repr, str(swidth)))
|
||||
) == (socket.AF_INET6, ip, swidth, 0, 0)
|
||||
assert sshuttle.options.parse_subnetport('::/0') \
|
||||
== (socket.AF_INET6, '::', 0, 0, 0)
|
||||
with pytest.raises(Fatal) as excinfo:
|
||||
sshuttle.options.parse_subnetport('fc00::/129')
|
||||
assert str(excinfo.value) == 'width 129 is not between 0 and 128'
|
||||
|
||||
|
||||
def test_parse_subnetport_ip6_with_port():
|
||||
for ip_repr, ip in _ip6_reprs.items():
|
||||
assert sshuttle.options.parse_subnetport('[' + ip_repr + ']:80') \
|
||||
== (socket.AF_INET6, ip, 128, 80, 80)
|
||||
assert sshuttle.options.parse_subnetport('[' + ip_repr + ']:80-90') \
|
||||
== (socket.AF_INET6, ip, 128, 80, 90)
|
||||
|
||||
|
||||
def test_parse_subnetport_ip6_with_mask_and_port():
|
||||
for ip_repr, ip in _ip6_reprs.items():
|
||||
assert sshuttle.options.parse_subnetport('[' + ip_repr + '/128]:80') \
|
||||
== (socket.AF_INET6, ip, 128, 80, 80)
|
||||
assert sshuttle.options.parse_subnetport('[' + ip_repr + '/16]:80-90') \
|
||||
== (socket.AF_INET6, ip, 16, 80, 90)
|
Loading…
Reference in New Issue
Block a user