mirror of
https://github.com/sshuttle/sshuttle.git
synced 2024-11-22 07:53:43 +01:00
oldpy back to sync with master and hostwatch fixes
Ported fixes from #55 so they are compatible with older python versions.
This commit is contained in:
commit
1bab128cc5
53
README.rst
53
README.rst
@ -43,17 +43,9 @@ Client side Requirements
|
||||
| | | * IPv6 TCP + |
|
||||
| | | * IPv6 UDP + |
|
||||
+-------+--------+------------+-----------------------------------------------+
|
||||
| BSD | IPFW | * IPv4 TCP | Your kernel needs to be compiled with |
|
||||
| | | | `IPFIREWALL_FORWARD` and you need to have ipfw|
|
||||
| | | | available. |
|
||||
+-------+--------+------------+-----------------------------------------------+
|
||||
| MacOS | PF | * IPv4 TCP + You need to have the pfctl command. |
|
||||
+-------+--------+------------+-----------------------------------------------+
|
||||
|
||||
The IPFW method is depreciated. It was originally required for MacOS support,
|
||||
however is no longer maintained. It is likely to get removed from future
|
||||
versions of sshuttle.
|
||||
|
||||
|
||||
Server side Requirements
|
||||
------------------------
|
||||
@ -80,25 +72,42 @@ later.
|
||||
|
||||
There are some things you need to consider for TPROXY to work:
|
||||
|
||||
1. The following commands need to be run first as root. This only needs to be
|
||||
done once after booting up::
|
||||
- The following commands need to be run first as root. This only needs to be
|
||||
done once after booting up::
|
||||
|
||||
ip route add local default dev lo table 100
|
||||
ip rule add fwmark 1 lookup 100
|
||||
ip -6 route add local default dev lo table 100
|
||||
ip -6 rule add fwmark 1 lookup 100
|
||||
ip route add local default dev lo table 100
|
||||
ip rule add fwmark 1 lookup 100
|
||||
ip -6 route add local default dev lo table 100
|
||||
ip -6 rule add fwmark 1 lookup 100
|
||||
|
||||
2. The client needs to be run as root. e.g.::
|
||||
- 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.
|
||||
|
||||
sudo SSH_AUTH_SOCK="$SSH_AUTH_SOCK" $HOME/tree/sshuttle.tproxy/sshuttle --method=tproxy ...
|
||||
- The client needs to be run as root. e.g.::
|
||||
|
||||
3. You do need the `--method=tproxy` parameter, as above.
|
||||
sudo SSH_AUTH_SOCK="$SSH_AUTH_SOCK" $HOME/tree/sshuttle.tproxy/sshuttle --method=tproxy ...
|
||||
|
||||
4. The routes for the outgoing packets must already exist. For example, if your
|
||||
connection does not have IPv6 support, no IPv6 routes will exist, IPv6
|
||||
packets will not be generated and sshuttle cannot intercept them. Add some
|
||||
dummy routes to external interfaces. Make sure they get removed however
|
||||
after sshuttle exits.
|
||||
- You may need to exclude the IP address of the server you are connecting to.
|
||||
Otherwise sshuttle may attempt to intercept the ssh packets, which will not
|
||||
work. Use the `--exclude` parameter for this.
|
||||
|
||||
- Similarly, UDP return packets (including DNS) could get intercepted and
|
||||
bounced back. This is the case if you have a broad subnet such as
|
||||
``0.0.0.0/0`` or ``::/0`` that includes the IP address of the client. Use the
|
||||
`--exclude` parameter for this.
|
||||
|
||||
- You do need the `--method=tproxy` parameter, as above.
|
||||
|
||||
- The routes for the outgoing packets must already exist. For example, if your
|
||||
connection does not have IPv6 support, no IPv6 routes will exist, IPv6
|
||||
packets will not be generated and sshuttle cannot intercept them::
|
||||
|
||||
telnet -6 www.google.com 80
|
||||
Trying 2404:6800:4001:805::1010...
|
||||
telnet: Unable to connect to remote host: Network is unreachable
|
||||
|
||||
Add some dummy routes to external interfaces. Make sure they get removed
|
||||
however after sshuttle exits.
|
||||
|
||||
|
||||
Obtaining sshuttle
|
||||
|
2
run
2
run
@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
if python3 -V 2>/dev/null; then
|
||||
if python3.5 -V 2>/dev/null; then
|
||||
exec python3 -m "sshuttle" "$@"
|
||||
else
|
||||
exec python -m "sshuttle" "$@"
|
||||
|
@ -6,6 +6,7 @@ import sshuttle.options as options
|
||||
import sshuttle.client as client
|
||||
import sshuttle.firewall as firewall
|
||||
import sshuttle.hostwatch as hostwatch
|
||||
import sshuttle.ssyslog as ssyslog
|
||||
from sshuttle.helpers import family_ip_tuple, log, Fatal
|
||||
|
||||
|
||||
@ -119,7 +120,7 @@ H,auto-hosts scan for remote hostnames and update local /etc/hosts
|
||||
N,auto-nets automatically determine subnets to route
|
||||
dns capture local DNS requests and forward to the remote DNS server
|
||||
ns-hosts= capture and forward remote DNS requests to the following servers
|
||||
method= auto, nat, tproxy, pf or ipfw
|
||||
method= auto, nat, tproxy or pf
|
||||
python= path to python interpreter on the remote server
|
||||
r,remote= ssh hostname (and optional username) of remote sshuttle server
|
||||
x,exclude= exclude this subnet (can be used more than once)
|
||||
@ -181,7 +182,7 @@ try:
|
||||
includes = parse_subnet_file(opt.subnets)
|
||||
if not opt.method:
|
||||
method_name = "auto"
|
||||
elif opt.method in ["auto", "nat", "tproxy", "ipfw", "pf"]:
|
||||
elif opt.method in ["auto", "nat", "tproxy", "pf"]:
|
||||
method_name = opt.method
|
||||
else:
|
||||
o.fatal("method_name %s not supported" % opt.method)
|
||||
@ -197,6 +198,9 @@ try:
|
||||
ipport_v6 = parse_ipport6(ip)
|
||||
else:
|
||||
ipport_v4 = parse_ipport4(ip)
|
||||
if opt.syslog:
|
||||
ssyslog.start_syslog()
|
||||
ssyslog.stderr_to_syslog()
|
||||
return_code = client.main(ipport_v6, ipport_v4,
|
||||
opt.ssh_cmd,
|
||||
remotename,
|
||||
@ -209,7 +213,7 @@ try:
|
||||
opt.auto_nets,
|
||||
parse_subnets(includes),
|
||||
parse_subnets(excludes),
|
||||
opt.syslog, opt.daemon, opt.pidfile)
|
||||
opt.daemon, opt.pidfile)
|
||||
|
||||
if return_code == 0:
|
||||
log('Normal exit code, exiting...')
|
||||
|
@ -4,13 +4,15 @@ import imp
|
||||
|
||||
z = zlib.decompressobj()
|
||||
while 1:
|
||||
name = sys.stdin.readline().strip().decode("ASCII")
|
||||
name = stdin.readline().strip()
|
||||
if name:
|
||||
nbytes = int(sys.stdin.readline())
|
||||
name = name.decode("ASCII")
|
||||
|
||||
nbytes = int(stdin.readline())
|
||||
if verbosity >= 2:
|
||||
sys.stderr.write('server: assembling %r (%d bytes)\n'
|
||||
% (name, nbytes))
|
||||
content = z.decompress(sys.stdin.read(nbytes))
|
||||
content = z.decompress(stdin.read(nbytes))
|
||||
|
||||
module = imp.new_module(name)
|
||||
parents = name.rsplit(".", 1)
|
||||
|
@ -14,7 +14,7 @@ import platform
|
||||
from sshuttle.ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
|
||||
from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, islocal, \
|
||||
resolvconf_nameservers
|
||||
from sshuttle.methods import get_method
|
||||
from sshuttle.methods import get_method, Features
|
||||
|
||||
_extra_fd = os.open('/dev/null', os.O_RDONLY)
|
||||
|
||||
@ -67,7 +67,7 @@ def daemonize():
|
||||
|
||||
outfd = os.open(_pidname, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o666)
|
||||
try:
|
||||
os.write(outfd, '%d\n' % os.getpid())
|
||||
os.write(outfd, b'%d\n' % os.getpid())
|
||||
finally:
|
||||
os.close(outfd)
|
||||
os.chdir("/")
|
||||
@ -81,8 +81,6 @@ def daemonize():
|
||||
os.dup2(si.fileno(), 1)
|
||||
si.close()
|
||||
|
||||
ssyslog.stderr_to_syslog()
|
||||
|
||||
|
||||
def daemon_cleanup():
|
||||
try:
|
||||
@ -277,18 +275,25 @@ udp_by_src = {}
|
||||
|
||||
|
||||
def expire_connections(now, mux):
|
||||
remove = []
|
||||
for chan, timeout in dnsreqs.items():
|
||||
if timeout < now:
|
||||
debug3('expiring dnsreqs channel=%d\n' % chan)
|
||||
remove.append(chan)
|
||||
del mux.channels[chan]
|
||||
del dnsreqs[chan]
|
||||
for chan in remove:
|
||||
del dnsreqs[chan]
|
||||
debug3('Remaining DNS requests: %d\n' % len(dnsreqs))
|
||||
|
||||
remove = []
|
||||
for peer, (chan, timeout) in udp_by_src.items():
|
||||
if timeout < now:
|
||||
debug3('expiring UDP channel channel=%d peer=%r\n' % (chan, peer))
|
||||
mux.send(chan, ssnet.CMD_UDP_CLOSE, '')
|
||||
mux.send(chan, ssnet.CMD_UDP_CLOSE, b'')
|
||||
remove.append(peer)
|
||||
del mux.channels[chan]
|
||||
del udp_by_src[peer]
|
||||
for peer in remove:
|
||||
del udp_by_src[peer]
|
||||
debug3('Remaining UDP channels: %d\n' % len(udp_by_src))
|
||||
|
||||
|
||||
@ -330,7 +335,7 @@ def onaccept_tcp(listener, method, mux, handlers):
|
||||
|
||||
|
||||
def udp_done(chan, data, method, sock, dstip):
|
||||
(src, srcport, data) = data.split(",", 2)
|
||||
(src, srcport, data) = data.split(b",", 2)
|
||||
srcip = (src, int(srcport))
|
||||
debug3('doing send from %r to %r\n' % (srcip, dstip,))
|
||||
method.send_udp(sock, srcip, dstip, data)
|
||||
@ -349,10 +354,10 @@ def onaccept_udp(listener, method, mux, handlers):
|
||||
chan = mux.next_channel()
|
||||
mux.channels[chan] = lambda cmd, data: udp_done(
|
||||
chan, data, method, listener, dstip=srcip)
|
||||
mux.send(chan, ssnet.CMD_UDP_OPEN, listener.family)
|
||||
mux.send(chan, ssnet.CMD_UDP_OPEN, b"%d" % listener.family)
|
||||
udp_by_src[srcip] = chan, now + 30
|
||||
|
||||
hdr = "%s,%r," % (dstip[0], dstip[1])
|
||||
hdr = b"%s,%d," % (dstip[0].encode("ASCII"), dstip[1])
|
||||
mux.send(chan, ssnet.CMD_UDP_DATA, hdr + data)
|
||||
|
||||
expire_connections(now, mux)
|
||||
@ -382,8 +387,7 @@ def ondns(listener, method, mux, handlers):
|
||||
|
||||
def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
||||
python, latency_control,
|
||||
dns_listener, seed_hosts, auto_nets,
|
||||
syslog, daemon):
|
||||
dns_listener, seed_hosts, auto_nets, daemon):
|
||||
|
||||
debug1('Starting client with Python version %s\n'
|
||||
% platform.python_version())
|
||||
@ -433,21 +437,26 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
||||
if initstring != expected:
|
||||
raise Fatal('expected server init string %r; got %r'
|
||||
% (expected, initstring))
|
||||
debug1('connected.\n')
|
||||
print('Connected.')
|
||||
log('Connected.\n')
|
||||
sys.stdout.flush()
|
||||
if daemon:
|
||||
daemonize()
|
||||
log('daemonizing (%s).\n' % _pidname)
|
||||
elif syslog:
|
||||
debug1('switching to syslog.\n')
|
||||
ssyslog.stderr_to_syslog()
|
||||
|
||||
def onroutes(routestr):
|
||||
if auto_nets:
|
||||
for line in routestr.strip().split('\n'):
|
||||
(family, ip, width) = line.split(',', 2)
|
||||
fw.auto_nets.append((int(family), ip, int(width)))
|
||||
for line in routestr.strip().split(b'\n'):
|
||||
(family, ip, width) = line.split(b',', 2)
|
||||
family = int(family)
|
||||
width = int(width)
|
||||
ip = ip.decode("ASCII")
|
||||
if family == socket.AF_INET6 and tcp_listener.v6 is None:
|
||||
debug2("Ignored auto net %d/%s/%d\n" % (family, ip, width))
|
||||
if family == socket.AF_INET and tcp_listener.v4 is None:
|
||||
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))
|
||||
|
||||
# we definitely want to do this *after* starting ssh, or we might end
|
||||
# up intercepting the ssh connection!
|
||||
@ -493,10 +502,8 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
||||
def main(listenip_v6, listenip_v4,
|
||||
ssh_cmd, remotename, python, latency_control, dns, nslist,
|
||||
method_name, seed_hosts, auto_nets,
|
||||
subnets_include, subnets_exclude, syslog, daemon, pidfile):
|
||||
subnets_include, subnets_exclude, daemon, pidfile):
|
||||
|
||||
if syslog:
|
||||
ssyslog.start_syslog()
|
||||
if daemon:
|
||||
try:
|
||||
check_daemon(pidfile)
|
||||
@ -507,19 +514,45 @@ def main(listenip_v6, listenip_v4,
|
||||
|
||||
fw = FirewallClient(method_name)
|
||||
|
||||
features = fw.method.get_supported_features()
|
||||
# Get family specific subnet lists
|
||||
if dns:
|
||||
nslist += resolvconf_nameservers()
|
||||
|
||||
subnets = subnets_include + subnets_exclude # we don't care here
|
||||
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]
|
||||
subnets_v4 = [i for i in subnets if i[0] == socket.AF_INET]
|
||||
nslist_v4 = [i for i in nslist if i[0] == socket.AF_INET]
|
||||
|
||||
# Check features available
|
||||
avail = fw.method.get_supported_features()
|
||||
required = Features()
|
||||
|
||||
if listenip_v6 == "auto":
|
||||
if features.ipv6:
|
||||
if avail.ipv6:
|
||||
listenip_v6 = ('::1', 0)
|
||||
else:
|
||||
listenip_v6 = None
|
||||
|
||||
required.ipv6 = len(subnets_v6) > 0 or len(nslist_v6) > 0 \
|
||||
or listenip_v6 is not None
|
||||
required.udp = avail.udp
|
||||
required.dns = len(nslist) > 0
|
||||
|
||||
fw.method.assert_features(required)
|
||||
|
||||
if required.ipv6 and listenip_v6 is None:
|
||||
raise Fatal("IPv6 required but not listening.")
|
||||
|
||||
# display features enabled
|
||||
debug1("IPv6 enabled: %r\n" % required.ipv6)
|
||||
debug1("UDP enabled: %r\n" % required.udp)
|
||||
debug1("DNS enabled: %r\n" % required.dns)
|
||||
|
||||
# bind to required ports
|
||||
if listenip_v4 == "auto":
|
||||
listenip_v4 = ('127.0.0.1', 0)
|
||||
|
||||
udp = features.udp
|
||||
debug1("UDP enabled: %r\n" % udp)
|
||||
|
||||
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
|
||||
ports = [0, ]
|
||||
@ -538,7 +571,7 @@ def main(listenip_v6, listenip_v4,
|
||||
tcp_listener = MultiListener()
|
||||
tcp_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
|
||||
if udp:
|
||||
if required.udp:
|
||||
udp_listener = MultiListener(socket.SOCK_DGRAM)
|
||||
udp_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
else:
|
||||
@ -586,10 +619,7 @@ def main(listenip_v6, listenip_v4,
|
||||
udp_listener.print_listening("UDP redirector")
|
||||
|
||||
bound = False
|
||||
if dns or nslist:
|
||||
if dns:
|
||||
nslist += resolvconf_nameservers()
|
||||
dns = True
|
||||
if required.dns:
|
||||
# search for spare port for DNS
|
||||
debug2('Binding DNS:')
|
||||
ports = range(12300, 9000, -1)
|
||||
@ -630,22 +660,45 @@ def main(listenip_v6, listenip_v4,
|
||||
dnsport_v4 = 0
|
||||
dns_listener = None
|
||||
|
||||
fw.method.check_settings(udp, dns)
|
||||
# Last minute sanity checks.
|
||||
# These should never fail.
|
||||
# If these do fail, something is broken above.
|
||||
if len(subnets_v6) > 0:
|
||||
assert required.ipv6
|
||||
if redirectport_v6 == 0:
|
||||
raise Fatal("IPv6 subnets defined but not listening")
|
||||
|
||||
if len(nslist_v6) > 0:
|
||||
assert required.dns
|
||||
assert required.ipv6
|
||||
if dnsport_v6 == 0:
|
||||
raise Fatal("IPv6 ns servers defined but not listening")
|
||||
|
||||
if len(subnets_v4) > 0:
|
||||
if redirectport_v4 == 0:
|
||||
raise Fatal("IPv4 subnets defined but not listening")
|
||||
|
||||
if len(nslist_v4) > 0:
|
||||
if dnsport_v4 == 0:
|
||||
raise Fatal("IPv4 ns servers defined but not listening")
|
||||
|
||||
# setup method specific stuff on listeners
|
||||
fw.method.setup_tcp_listener(tcp_listener)
|
||||
if udp_listener:
|
||||
fw.method.setup_udp_listener(udp_listener)
|
||||
if dns_listener:
|
||||
fw.method.setup_udp_listener(dns_listener)
|
||||
|
||||
# start the firewall
|
||||
fw.setup(subnets_include, subnets_exclude, nslist,
|
||||
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4,
|
||||
udp)
|
||||
required.udp)
|
||||
|
||||
# start the client process
|
||||
try:
|
||||
return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
||||
python, latency_control, dns_listener,
|
||||
seed_hosts, auto_nets, syslog,
|
||||
daemon)
|
||||
seed_hosts, auto_nets, daemon)
|
||||
finally:
|
||||
try:
|
||||
if daemon:
|
||||
|
@ -5,14 +5,14 @@ import sshuttle.ssyslog as ssyslog
|
||||
import sys
|
||||
import os
|
||||
import platform
|
||||
import traceback
|
||||
from sshuttle.helpers import debug1, debug2, Fatal
|
||||
from sshuttle.methods import get_auto_method, get_method
|
||||
|
||||
hostmap = {}
|
||||
HOSTSFILE = '/etc/hosts'
|
||||
|
||||
|
||||
def rewrite_etc_hosts(port):
|
||||
def rewrite_etc_hosts(hostmap, port):
|
||||
BAKFILE = '%s.sbak' % HOSTSFILE
|
||||
APPEND = '# sshuttle-firewall-%d AUTOCREATED' % port
|
||||
old_content = ''
|
||||
@ -37,7 +37,7 @@ def rewrite_etc_hosts(port):
|
||||
f.write('%-30s %s\n' % ('%s %s' % (ip, name), APPEND))
|
||||
f.close()
|
||||
|
||||
if st:
|
||||
if st is not None:
|
||||
os.chown(tmpname, st.st_uid, st.st_gid)
|
||||
os.chmod(tmpname, st.st_mode)
|
||||
else:
|
||||
@ -47,9 +47,7 @@ def rewrite_etc_hosts(port):
|
||||
|
||||
|
||||
def restore_etc_hosts(port):
|
||||
global hostmap
|
||||
hostmap = {}
|
||||
rewrite_etc_hosts(port)
|
||||
rewrite_etc_hosts({}, port)
|
||||
|
||||
|
||||
# Isolate function that needs to be replaced for tests
|
||||
@ -86,8 +84,9 @@ def setup_daemon():
|
||||
# are hopefully harmless.
|
||||
def main(method_name, syslog):
|
||||
stdin, stdout = setup_daemon()
|
||||
hostmap = {}
|
||||
|
||||
debug1('Starting firewall with Python version %s\n'
|
||||
debug1('firewall manager: Starting firewall with Python version %s\n'
|
||||
% platform.python_version())
|
||||
|
||||
if method_name == "auto":
|
||||
@ -99,7 +98,7 @@ def main(method_name, syslog):
|
||||
ssyslog.start_syslog()
|
||||
ssyslog.stderr_to_syslog()
|
||||
|
||||
debug1('firewall manager ready method name %s.\n' % method.name)
|
||||
debug1('firewall manager: ready method name %s.\n' % method.name)
|
||||
stdout.write('READY %s\n' % method.name)
|
||||
stdout.flush()
|
||||
|
||||
@ -124,7 +123,7 @@ def main(method_name, syslog):
|
||||
except:
|
||||
raise Fatal('firewall: expected route or NSLIST but got %r' % line)
|
||||
subnets.append((int(family), int(width), bool(int(exclude)), ip))
|
||||
debug2('Got subnets: %r\n' % subnets)
|
||||
debug2('firewall manager: Got subnets: %r\n' % subnets)
|
||||
|
||||
nslist = []
|
||||
if line != 'NSLIST\n':
|
||||
@ -140,8 +139,8 @@ def main(method_name, syslog):
|
||||
except:
|
||||
raise Fatal('firewall: expected nslist or PORTS but got %r' % line)
|
||||
nslist.append((int(family), ip))
|
||||
debug2('Got partial nslist: %r\n' % nslist)
|
||||
debug2('Got nslist: %r\n' % nslist)
|
||||
debug2('firewall manager: Got partial nslist: %r\n' % nslist)
|
||||
debug2('firewall manager: Got nslist: %r\n' % nslist)
|
||||
|
||||
if not line.startswith('PORTS '):
|
||||
raise Fatal('firewall: expected PORTS but got %r' % line)
|
||||
@ -163,7 +162,7 @@ def main(method_name, syslog):
|
||||
assert(dnsport_v4 >= 0)
|
||||
assert(dnsport_v4 <= 65535)
|
||||
|
||||
debug2('Got ports: %d,%d,%d,%d\n'
|
||||
debug2('firewall manager: Got ports: %d,%d,%d,%d\n'
|
||||
% (port_v6, port_v4, dnsport_v6, dnsport_v4))
|
||||
|
||||
line = stdin.readline(128)
|
||||
@ -174,29 +173,27 @@ def main(method_name, syslog):
|
||||
|
||||
_, _, udp = line.partition(" ")
|
||||
udp = bool(int(udp))
|
||||
debug2('Got udp: %r\n' % udp)
|
||||
debug2('firewall manager: Got udp: %r\n' % udp)
|
||||
|
||||
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]
|
||||
subnets_v4 = [i for i in subnets if i[0] == socket.AF_INET]
|
||||
nslist_v4 = [i for i in nslist if i[0] == socket.AF_INET]
|
||||
|
||||
try:
|
||||
do_wait = None
|
||||
debug1('firewall manager: starting transproxy.\n')
|
||||
debug1('firewall manager: setting up.\n')
|
||||
|
||||
nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6]
|
||||
subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6]
|
||||
if port_v6 > 0:
|
||||
do_wait = method.setup_firewall(
|
||||
if len(subnets_v6) > 0 or len(nslist_v6) > 0:
|
||||
debug2('firewall manager: setting up IPv6.\n')
|
||||
method.setup_firewall(
|
||||
port_v6, dnsport_v6, nslist_v6,
|
||||
socket.AF_INET6, subnets_v6, udp)
|
||||
elif len(subnets_v6) > 0:
|
||||
debug1("IPv6 subnets defined but IPv6 disabled\n")
|
||||
|
||||
nslist_v4 = [i for i in nslist if i[0] == socket.AF_INET]
|
||||
subnets_v4 = [i for i in subnets if i[0] == socket.AF_INET]
|
||||
if port_v4 > 0:
|
||||
do_wait = method.setup_firewall(
|
||||
if len(subnets_v4) > 0 or len(nslist_v4) > 0:
|
||||
debug2('firewall manager: setting up IPv4.\n')
|
||||
method.setup_firewall(
|
||||
port_v4, dnsport_v4, nslist_v4,
|
||||
socket.AF_INET, subnets_v4, udp)
|
||||
elif len(subnets_v4) > 0:
|
||||
debug1('IPv4 subnets defined but IPv4 disabled\n')
|
||||
|
||||
stdout.write('STARTED\n')
|
||||
|
||||
@ -211,16 +208,15 @@ def main(method_name, syslog):
|
||||
# to stay running so that we don't need a *second* password
|
||||
# authentication at shutdown time - that cleanup is important!
|
||||
while 1:
|
||||
if do_wait is not None:
|
||||
do_wait()
|
||||
line = stdin.readline(128)
|
||||
if line.startswith('HOST '):
|
||||
(name, ip) = line[5:].strip().split(',', 1)
|
||||
hostmap[name] = ip
|
||||
rewrite_etc_hosts(port_v6 or port_v4)
|
||||
debug2('firewall manager: setting up /etc/hosts.\n')
|
||||
rewrite_etc_hosts(hostmap, port_v6 or port_v4)
|
||||
elif line:
|
||||
if not method.firewall_command(line):
|
||||
raise Fatal('expected EOF, got %r' % line)
|
||||
raise Fatal('firewall: expected command, got %r' % line)
|
||||
else:
|
||||
break
|
||||
finally:
|
||||
@ -228,8 +224,41 @@ def main(method_name, syslog):
|
||||
debug1('firewall manager: undoing changes.\n')
|
||||
except:
|
||||
pass
|
||||
if port_v6:
|
||||
method.setup_firewall(port_v6, 0, [], socket.AF_INET6, [], udp)
|
||||
if port_v4:
|
||||
method.setup_firewall(port_v4, 0, [], socket.AF_INET, [], udp)
|
||||
restore_etc_hosts(port_v6 or port_v4)
|
||||
|
||||
try:
|
||||
if len(subnets_v6) > 0 or len(nslist_v6) > 0:
|
||||
debug2('firewall manager: undoing IPv6 changes.\n')
|
||||
method.restore_firewall(port_v6, socket.AF_INET6, udp)
|
||||
except:
|
||||
try:
|
||||
debug1("firewall manager: "
|
||||
"Error trying to undo IPv6 firewall.\n")
|
||||
for line in traceback.format_exc().splitlines():
|
||||
debug1("---> %s\n" % line)
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
if len(subnets_v4) > 0 or len(nslist_v4) > 0:
|
||||
debug2('firewall manager: undoing IPv4 changes.\n')
|
||||
method.restore_firewall(port_v4, socket.AF_INET, udp)
|
||||
except:
|
||||
try:
|
||||
debug1("firewall manager: "
|
||||
"Error trying to undo IPv4 firewall.\n")
|
||||
for line in traceback.format_exc().splitlines():
|
||||
debug1("firewall manager: ---> %s\n" % line)
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
debug2('firewall manager: undoing /etc/hosts changes.\n')
|
||||
restore_etc_hosts(port_v6 or port_v4)
|
||||
except:
|
||||
try:
|
||||
debug1("firewall manager: "
|
||||
"Error trying to undo /etc/hosts changes.\n")
|
||||
for line in traceback.format_exc().splitlines():
|
||||
debug1("firewall manager: ---> %s\n" % line)
|
||||
except:
|
||||
pass
|
||||
|
@ -17,9 +17,17 @@ else:
|
||||
return s
|
||||
|
||||
def log(s):
|
||||
global logprefix
|
||||
try:
|
||||
sys.stdout.flush()
|
||||
sys.stderr.write(logprefix + s)
|
||||
if s.find("\n") != -1:
|
||||
prefix = logprefix
|
||||
s = s.rstrip("\n")
|
||||
for line in s.split("\n"):
|
||||
sys.stderr.write(prefix + line + "\n")
|
||||
prefix = "---> "
|
||||
else:
|
||||
sys.stderr.write(logprefix + s)
|
||||
sys.stderr.flush()
|
||||
except IOError:
|
||||
# this could happen if stderr gets forcibly disconnected, eg. because
|
||||
|
@ -9,7 +9,7 @@ import platform
|
||||
|
||||
import subprocess as ssubprocess
|
||||
import sshuttle.helpers as helpers
|
||||
from sshuttle.helpers import log, debug1, debug2, debug3
|
||||
from sshuttle.helpers import b, log, debug1, debug2, debug3
|
||||
|
||||
POLL_TIME = 60 * 15
|
||||
NETSTAT_POLL_TIME = 30
|
||||
@ -37,7 +37,7 @@ def write_host_cache():
|
||||
try:
|
||||
f = open(tmpname, 'wb')
|
||||
for name, ip in sorted(hostnames.items()):
|
||||
f.write('%s,%s\n' % (name, ip))
|
||||
f.write(b('%s,%s\n' % (name, ip)))
|
||||
f.close()
|
||||
os.chmod(tmpname, 384) # 600 in octal, 'rw-------'
|
||||
os.rename(tmpname, CACHEFILE)
|
||||
@ -124,7 +124,7 @@ def _check_netstat():
|
||||
argv = ['netstat', '-n']
|
||||
try:
|
||||
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
|
||||
content = p.stdout.read()
|
||||
content = p.stdout.read().decode("ASCII")
|
||||
p.wait()
|
||||
except OSError:
|
||||
_, e = sys.exc_info()[:2]
|
||||
|
@ -39,6 +39,7 @@ class BaseMethod(object):
|
||||
result = Features()
|
||||
result.ipv6 = False
|
||||
result.udp = False
|
||||
result.dns = True
|
||||
return result
|
||||
|
||||
def get_tcp_dstip(self, sock):
|
||||
@ -61,13 +62,20 @@ class BaseMethod(object):
|
||||
def setup_udp_listener(self, udp_listener):
|
||||
pass
|
||||
|
||||
def check_settings(self, udp, dns):
|
||||
if udp:
|
||||
Fatal("UDP support not supported with method %s.\n" % self.name)
|
||||
def assert_features(self, features):
|
||||
avail = self.get_supported_features()
|
||||
for key in ["udp", "dns", "ipv6"]:
|
||||
if getattr(features, key) and not getattr(avail, key):
|
||||
raise Fatal(
|
||||
"Feature %s not supported with method %s.\n" %
|
||||
(key, self.name))
|
||||
|
||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
|
||||
raise NotImplementedError()
|
||||
|
||||
def restore_firewall(self, port, family, udp):
|
||||
raise NotImplementedError()
|
||||
|
||||
def firewall_command(self, line):
|
||||
return False
|
||||
|
||||
@ -86,14 +94,12 @@ def get_method(method_name):
|
||||
|
||||
|
||||
def get_auto_method():
|
||||
if _program_exists('ipfw'):
|
||||
method_name = "ipfw"
|
||||
elif _program_exists('iptables'):
|
||||
if _program_exists('iptables'):
|
||||
method_name = "nat"
|
||||
elif _program_exists('pfctl'):
|
||||
method_name = "pf"
|
||||
else:
|
||||
raise Fatal(
|
||||
"can't find either ipfw, iptables or pfctl; check your PATH")
|
||||
"can't find either iptables or pfctl; check your PATH")
|
||||
|
||||
return get_method(method_name)
|
||||
|
@ -1,237 +0,0 @@
|
||||
import sys
|
||||
import select
|
||||
import socket
|
||||
import struct
|
||||
import subprocess as ssubprocess
|
||||
from sshuttle.helpers import log, debug1, debug3, islocal, \
|
||||
Fatal, family_to_string
|
||||
from sshuttle.methods import BaseMethod
|
||||
|
||||
|
||||
# python doesn't have a definition for this
|
||||
IPPROTO_DIVERT = 254
|
||||
|
||||
|
||||
def ipfw_rule_exists(n):
|
||||
argv = ['ipfw', 'list']
|
||||
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE)
|
||||
found = False
|
||||
for line in p.stdout:
|
||||
if line.startswith('%05d ' % n):
|
||||
if not ('ipttl 42' in line
|
||||
or ('skipto %d' % (n + 1)) in line
|
||||
or 'check-state' in line):
|
||||
log('non-sshuttle ipfw rule: %r\n' % line.strip())
|
||||
raise Fatal('non-sshuttle ipfw rule #%d already exists!' % n)
|
||||
found = True
|
||||
rv = p.wait()
|
||||
if rv:
|
||||
raise Fatal('%r returned %d' % (argv, rv))
|
||||
return found
|
||||
|
||||
|
||||
_oldctls = {}
|
||||
|
||||
|
||||
def _fill_oldctls(prefix):
|
||||
argv = ['sysctl', prefix]
|
||||
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE)
|
||||
for line in p.stdout:
|
||||
assert(line[-1] == '\n')
|
||||
(k, v) = line[:-1].split(': ', 1)
|
||||
_oldctls[k] = v
|
||||
rv = p.wait()
|
||||
if rv:
|
||||
raise Fatal('%r returned %d' % (argv, rv))
|
||||
if not line:
|
||||
raise Fatal('%r returned no data' % (argv,))
|
||||
|
||||
|
||||
def _sysctl_set(name, val):
|
||||
argv = ['sysctl', '-w', '%s=%s' % (name, val)]
|
||||
debug1('>> %s\n' % ' '.join(argv))
|
||||
return ssubprocess.call(argv, stdout=open('/dev/null', 'w'))
|
||||
|
||||
|
||||
_changedctls = []
|
||||
|
||||
|
||||
def sysctl_set(name, val, permanent=False):
|
||||
PREFIX = 'net.inet.ip'
|
||||
assert(name.startswith(PREFIX + '.'))
|
||||
val = str(val)
|
||||
if not _oldctls:
|
||||
_fill_oldctls(PREFIX)
|
||||
if not (name in _oldctls):
|
||||
debug1('>> No such sysctl: %r\n' % name)
|
||||
return False
|
||||
oldval = _oldctls[name]
|
||||
if val != oldval:
|
||||
rv = _sysctl_set(name, val)
|
||||
if rv == 0 and permanent:
|
||||
debug1('>> ...saving permanently in /etc/sysctl.conf\n')
|
||||
f = open('/etc/sysctl.conf', 'a')
|
||||
f.write('\n'
|
||||
'# Added by sshuttle\n'
|
||||
'%s=%s\n' % (name, val))
|
||||
f.close()
|
||||
else:
|
||||
_changedctls.append(name)
|
||||
return True
|
||||
|
||||
|
||||
def _udp_unpack(p):
|
||||
src = (socket.inet_ntoa(p[12:16]), struct.unpack('!H', p[20:22])[0])
|
||||
dst = (socket.inet_ntoa(p[16:20]), struct.unpack('!H', p[22:24])[0])
|
||||
return src, dst
|
||||
|
||||
|
||||
def _udp_repack(p, src, dst):
|
||||
addrs = socket.inet_aton(src[0]) + socket.inet_aton(dst[0])
|
||||
ports = struct.pack('!HH', src[1], dst[1])
|
||||
return p[:12] + addrs + ports + p[24:]
|
||||
|
||||
|
||||
_real_dns_server = [None]
|
||||
|
||||
|
||||
def _handle_diversion(divertsock, dnsport):
|
||||
p, tag = divertsock.recvfrom(4096)
|
||||
src, dst = _udp_unpack(p)
|
||||
debug3('got diverted packet from %r to %r\n' % (src, dst))
|
||||
if dst[1] == 53:
|
||||
# outgoing DNS
|
||||
debug3('...packet is a DNS request.\n')
|
||||
_real_dns_server[0] = dst
|
||||
dst = ('127.0.0.1', dnsport)
|
||||
elif src[1] == dnsport:
|
||||
if islocal(src[0], divertsock.family):
|
||||
debug3('...packet is a DNS response.\n')
|
||||
src = _real_dns_server[0]
|
||||
else:
|
||||
log('weird?! unexpected divert from %r to %r\n' % (src, dst))
|
||||
assert(0)
|
||||
newp = _udp_repack(p, src, dst)
|
||||
divertsock.sendto(newp, tag)
|
||||
|
||||
|
||||
def ipfw(*args):
|
||||
argv = ['ipfw', '-q'] + list(args)
|
||||
debug1('>> %s\n' % ' '.join(argv))
|
||||
rv = ssubprocess.call(argv)
|
||||
if rv:
|
||||
raise Fatal('%r returned %d' % (argv, rv))
|
||||
|
||||
|
||||
class Method(BaseMethod):
|
||||
|
||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
|
||||
# IPv6 not supported
|
||||
if family not in [socket.AF_INET, ]:
|
||||
raise Exception(
|
||||
'Address family "%s" unsupported by ipfw method_name'
|
||||
% family_to_string(family))
|
||||
if udp:
|
||||
raise Exception("UDP not supported by ipfw method_name")
|
||||
|
||||
sport = str(port)
|
||||
xsport = str(port + 1)
|
||||
|
||||
# cleanup any existing rules
|
||||
if ipfw_rule_exists(port):
|
||||
ipfw('delete', sport)
|
||||
|
||||
while _changedctls:
|
||||
name = _changedctls.pop()
|
||||
oldval = _oldctls[name]
|
||||
_sysctl_set(name, oldval)
|
||||
|
||||
if subnets or dnsport:
|
||||
sysctl_set('net.inet.ip.fw.enable', 1)
|
||||
changed = sysctl_set('net.inet.ip.scopedroute', 0, permanent=True)
|
||||
if changed:
|
||||
log("\n"
|
||||
" WARNING: ONE-TIME NETWORK DISRUPTION:\n"
|
||||
" =====================================\n"
|
||||
"sshuttle has changed a MacOS kernel setting to work around\n"
|
||||
"a bug in MacOS 10.6. This will cause your network to drop\n"
|
||||
"within 5-10 minutes unless you restart your network\n"
|
||||
"interface (change wireless networks or unplug/plug the\n"
|
||||
"ethernet port) NOW, then restart sshuttle. The fix is\n"
|
||||
"permanent; you only have to do this once.\n\n")
|
||||
sys.exit(1)
|
||||
|
||||
ipfw('add', sport, 'check-state', 'ip',
|
||||
'from', 'any', 'to', 'any')
|
||||
|
||||
if subnets:
|
||||
# create new subnet entries
|
||||
for f, swidth, sexclude, snet \
|
||||
in sorted(subnets, key=lambda s: s[1], reverse=True):
|
||||
if sexclude:
|
||||
ipfw('add', sport, 'skipto', xsport,
|
||||
'tcp',
|
||||
'from', 'any', 'to', '%s/%s' % (snet, swidth))
|
||||
else:
|
||||
ipfw('add', sport, 'fwd', '127.0.0.1,%d' % port,
|
||||
'tcp',
|
||||
'from', 'any', 'to', '%s/%s' % (snet, swidth),
|
||||
'not', 'ipttl', '42', 'keep-state', 'setup')
|
||||
|
||||
# This part is much crazier than it is on Linux, because MacOS (at
|
||||
# least 10.6, and probably other versions, and maybe FreeBSD too)
|
||||
# doesn't correctly fixup the dstip/dstport for UDP packets when it
|
||||
# puts them through a 'fwd' rule. It also doesn't fixup the
|
||||
# srcip/srcport in the response packet. In Linux iptables, all that
|
||||
# happens magically for us, so we just redirect the packets and relax.
|
||||
#
|
||||
# On MacOS, we have to fix the ports ourselves. For that, we use a
|
||||
# 'divert' socket, which receives raw packets and lets us mangle them.
|
||||
#
|
||||
# Here's how it works. Let's say the local DNS server is 1.1.1.1:53,
|
||||
# and the remote DNS server is 2.2.2.2:53, and the local transproxy
|
||||
# port is 10.0.0.1:12300, and a client machine is making a request from
|
||||
# 10.0.0.5:9999. We see a packet like this:
|
||||
# 10.0.0.5:9999 -> 1.1.1.1:53
|
||||
# Since the destip:port matches one of our local nameservers, it will
|
||||
# match a 'fwd' rule, thus grabbing it on the local machine. However,
|
||||
# the local kernel will then see a packet addressed to *:53 and not
|
||||
# know what to do with it; there's nobody listening on port 53. Thus,
|
||||
# we divert it, rewriting it into this:
|
||||
# 10.0.0.5:9999 -> 10.0.0.1:12300
|
||||
# This gets proxied out to the server, which sends it to 2.2.2.2:53,
|
||||
# and the answer comes back, and the proxy sends it back out like this:
|
||||
# 10.0.0.1:12300 -> 10.0.0.5:9999
|
||||
# But that's wrong! The original machine expected an answer from
|
||||
# 1.1.1.1:53, so we have to divert the *answer* and rewrite it:
|
||||
# 1.1.1.1:53 -> 10.0.0.5:9999
|
||||
#
|
||||
# See? Easy stuff.
|
||||
if dnsport:
|
||||
divertsock = socket.socket(socket.AF_INET, socket.SOCK_RAW,
|
||||
IPPROTO_DIVERT)
|
||||
divertsock.bind(('0.0.0.0', port)) # IP field is ignored
|
||||
|
||||
for f, ip in [i for i in nslist if i[0] == family]:
|
||||
# relabel and then catch outgoing DNS requests
|
||||
ipfw('add', sport, 'divert', sport,
|
||||
'udp',
|
||||
'from', 'any', 'to', '%s/32' % ip, '53',
|
||||
'not', 'ipttl', '42')
|
||||
# relabel DNS responses
|
||||
ipfw('add', sport, 'divert', sport,
|
||||
'udp',
|
||||
'from', 'any', str(dnsport), 'to', 'any',
|
||||
'not', 'ipttl', '42')
|
||||
|
||||
def do_wait():
|
||||
while 1:
|
||||
r, w, x = select.select([sys.stdin, divertsock], [], [])
|
||||
if divertsock in r:
|
||||
_handle_diversion(divertsock, dnsport)
|
||||
if sys.stdin in r:
|
||||
return
|
||||
else:
|
||||
do_wait = None
|
||||
|
||||
return do_wait
|
@ -30,41 +30,60 @@ class Method(BaseMethod):
|
||||
|
||||
chain = 'sshuttle-%s' % port
|
||||
|
||||
# basic cleanup/setup of chains
|
||||
self.restore_firewall(port, family, udp)
|
||||
|
||||
_ipt('-N', chain)
|
||||
_ipt('-F', chain)
|
||||
_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):
|
||||
if sexclude:
|
||||
_ipt('-A', chain, '-j', 'RETURN',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-p', 'tcp')
|
||||
else:
|
||||
_ipt_ttl('-A', chain, '-j', 'REDIRECT',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-p', 'tcp',
|
||||
'--to-ports', str(port))
|
||||
|
||||
for f, ip in [i for i in nslist if i[0] == family]:
|
||||
_ipt_ttl('-A', chain, '-j', 'REDIRECT',
|
||||
'--dest', '%s/32' % ip,
|
||||
'-p', 'udp',
|
||||
'--dport', '53',
|
||||
'--to-ports', str(dnsport))
|
||||
|
||||
def restore_firewall(self, port, family, udp):
|
||||
# only ipv4 supported with NAT
|
||||
if family != socket.AF_INET:
|
||||
raise Exception(
|
||||
'Address family "%s" unsupported by nat method_name'
|
||||
% family_to_string(family))
|
||||
if udp:
|
||||
raise Exception("UDP not supported by nat method_name")
|
||||
|
||||
table = "nat"
|
||||
|
||||
def _ipt(*args):
|
||||
return ipt(family, table, *args)
|
||||
|
||||
def _ipt_ttl(*args):
|
||||
return ipt_ttl(family, table, *args)
|
||||
|
||||
chain = 'sshuttle-%s' % port
|
||||
|
||||
# basic cleanup/setup of chains
|
||||
if ipt_chain_exists(family, table, chain):
|
||||
nonfatal(_ipt, '-D', 'OUTPUT', '-j', chain)
|
||||
nonfatal(_ipt, '-D', 'PREROUTING', '-j', chain)
|
||||
nonfatal(_ipt, '-F', chain)
|
||||
_ipt('-X', chain)
|
||||
|
||||
if subnets or dnsport:
|
||||
_ipt('-N', chain)
|
||||
_ipt('-F', chain)
|
||||
_ipt('-I', 'OUTPUT', '1', '-j', chain)
|
||||
_ipt('-I', 'PREROUTING', '1', '-j', chain)
|
||||
|
||||
if subnets:
|
||||
# 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):
|
||||
if sexclude:
|
||||
_ipt('-A', chain, '-j', 'RETURN',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-p', 'tcp')
|
||||
else:
|
||||
_ipt_ttl('-A', chain, '-j', 'REDIRECT',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-p', 'tcp',
|
||||
'--to-ports', str(port))
|
||||
|
||||
if dnsport:
|
||||
for f, ip in [i for i in nslist if i[0] == family]:
|
||||
_ipt_ttl('-A', chain, '-j', 'REDIRECT',
|
||||
'--dest', '%s/32' % ip,
|
||||
'-p', 'udp',
|
||||
'--dport', '53',
|
||||
'--to-ports', str(dnsport))
|
||||
|
@ -7,7 +7,7 @@ import subprocess as ssubprocess
|
||||
from fcntl import ioctl
|
||||
from ctypes import c_char, c_uint8, c_uint16, c_uint32, Union, Structure, \
|
||||
sizeof, addressof, memmove
|
||||
from sshuttle.helpers import debug1, debug2, Fatal, family_to_string
|
||||
from sshuttle.helpers import debug1, debug2, debug3, Fatal, family_to_string
|
||||
from sshuttle.methods import BaseMethod
|
||||
|
||||
|
||||
@ -24,67 +24,88 @@ def pfctl(args, stdin=None):
|
||||
|
||||
return o
|
||||
|
||||
_pf_context = {'started_by_sshuttle': False, 'Xtoken': ''}
|
||||
|
||||
|
||||
# This are some classes and functions used to support pf in yosemite.
|
||||
class pf_state_xport(Union):
|
||||
_fields_ = [("port", c_uint16),
|
||||
("call_id", c_uint16),
|
||||
("spi", c_uint32)]
|
||||
|
||||
|
||||
class pf_addr(Structure):
|
||||
|
||||
class _pfa(Union):
|
||||
_fields_ = [("v4", c_uint32), # struct in_addr
|
||||
("v6", c_uint32 * 4), # struct in6_addr
|
||||
("addr8", c_uint8 * 16),
|
||||
("addr16", c_uint16 * 8),
|
||||
("addr32", c_uint32 * 4)]
|
||||
|
||||
_fields_ = [("pfa", _pfa)]
|
||||
_anonymous_ = ("pfa",)
|
||||
|
||||
|
||||
class pfioc_natlook(Structure):
|
||||
_fields_ = [("saddr", pf_addr),
|
||||
("daddr", pf_addr),
|
||||
("rsaddr", pf_addr),
|
||||
("rdaddr", pf_addr),
|
||||
("sxport", pf_state_xport),
|
||||
("dxport", pf_state_xport),
|
||||
("rsxport", pf_state_xport),
|
||||
("rdxport", pf_state_xport),
|
||||
("af", c_uint8), # sa_family_t
|
||||
("proto", c_uint8),
|
||||
("proto_variant", c_uint8),
|
||||
("direction", c_uint8)]
|
||||
|
||||
pfioc_rule = c_char * 3104 # sizeof(struct pfioc_rule)
|
||||
|
||||
pfioc_pooladdr = c_char * 1136 # sizeof(struct pfioc_pooladdr)
|
||||
|
||||
MAXPATHLEN = 1024
|
||||
|
||||
DIOCNATLOOK = ((0x40000000 | 0x80000000) | (
|
||||
(sizeof(pfioc_natlook) & 0x1fff) << 16) | ((ord('D')) << 8) | (23))
|
||||
DIOCCHANGERULE = ((0x40000000 | 0x80000000) | (
|
||||
(sizeof(pfioc_rule) & 0x1fff) << 16) | ((ord('D')) << 8) | (26))
|
||||
DIOCBEGINADDRS = ((0x40000000 | 0x80000000) | (
|
||||
(sizeof(pfioc_pooladdr) & 0x1fff) << 16) | ((ord('D')) << 8) | (51))
|
||||
|
||||
PF_CHANGE_ADD_TAIL = 2
|
||||
PF_CHANGE_GET_TICKET = 6
|
||||
|
||||
PF_PASS = 0
|
||||
PF_RDR = 8
|
||||
|
||||
PF_OUT = 2
|
||||
|
||||
_pf_context = {'started_by_sshuttle': False, 'Xtoken': None}
|
||||
_pf_fd = None
|
||||
|
||||
|
||||
class OsDefs(object):
|
||||
|
||||
def __init__(self, platform=None):
|
||||
if platform is None:
|
||||
platform = sys.platform
|
||||
self.platform = platform
|
||||
|
||||
# This are some classes and functions used to support pf in yosemite.
|
||||
if platform == 'darwin':
|
||||
class pf_state_xport(Union):
|
||||
_fields_ = [("port", c_uint16),
|
||||
("call_id", c_uint16),
|
||||
("spi", c_uint32)]
|
||||
else:
|
||||
class pf_state_xport(Union):
|
||||
_fields_ = [("port", c_uint16),
|
||||
("call_id", c_uint16)]
|
||||
|
||||
class pf_addr(Structure):
|
||||
|
||||
class _pfa(Union):
|
||||
_fields_ = [("v4", c_uint32), # struct in_addr
|
||||
("v6", c_uint32 * 4), # struct in6_addr
|
||||
("addr8", c_uint8 * 16),
|
||||
("addr16", c_uint16 * 8),
|
||||
("addr32", c_uint32 * 4)]
|
||||
|
||||
_fields_ = [("pfa", _pfa)]
|
||||
_anonymous_ = ("pfa",)
|
||||
|
||||
class pfioc_natlook(Structure):
|
||||
_fields_ = [("saddr", pf_addr),
|
||||
("daddr", pf_addr),
|
||||
("rsaddr", pf_addr),
|
||||
("rdaddr", pf_addr),
|
||||
("sxport", pf_state_xport),
|
||||
("dxport", pf_state_xport),
|
||||
("rsxport", pf_state_xport),
|
||||
("rdxport", pf_state_xport),
|
||||
("af", c_uint8), # sa_family_t
|
||||
("proto", c_uint8),
|
||||
("proto_variant", c_uint8),
|
||||
("direction", c_uint8)]
|
||||
self.pfioc_natlook = pfioc_natlook
|
||||
|
||||
# sizeof(struct pfioc_rule)
|
||||
self.pfioc_rule = c_char * \
|
||||
(3104 if platform == 'darwin' else 3040)
|
||||
|
||||
# sizeof(struct pfioc_pooladdr)
|
||||
self.pfioc_pooladdr = c_char * 1136
|
||||
|
||||
self.MAXPATHLEN = 1024
|
||||
|
||||
self.DIOCNATLOOK = (
|
||||
(0x40000000 | 0x80000000) |
|
||||
((sizeof(pfioc_natlook) & 0x1fff) << 16) |
|
||||
((ord('D')) << 8) | (23))
|
||||
self.DIOCCHANGERULE = (
|
||||
(0x40000000 | 0x80000000) |
|
||||
((sizeof(self.pfioc_rule) & 0x1fff) << 16) |
|
||||
((ord('D')) << 8) | (26))
|
||||
self.DIOCBEGINADDRS = (
|
||||
(0x40000000 | 0x80000000) |
|
||||
((sizeof(self.pfioc_pooladdr) & 0x1fff) << 16) |
|
||||
((ord('D')) << 8) | (51))
|
||||
|
||||
self.PF_CHANGE_ADD_TAIL = 2
|
||||
self.PF_CHANGE_GET_TICKET = 6
|
||||
|
||||
self.PF_PASS = 0
|
||||
self.PF_RDR = 8
|
||||
|
||||
self.PF_OUT = 2
|
||||
|
||||
osdefs = OsDefs()
|
||||
|
||||
|
||||
def pf_get_dev():
|
||||
global _pf_fd
|
||||
if _pf_fd is None:
|
||||
@ -103,16 +124,16 @@ def pf_query_nat(family, proto, src_ip, src_port, dst_ip, dst_port):
|
||||
assert len(packed_src_ip) == len(packed_dst_ip)
|
||||
length = len(packed_src_ip)
|
||||
|
||||
pnl = pfioc_natlook()
|
||||
pnl = osdefs.pfioc_natlook()
|
||||
pnl.proto = proto
|
||||
pnl.direction = PF_OUT
|
||||
pnl.direction = osdefs.PF_OUT
|
||||
pnl.af = family
|
||||
memmove(addressof(pnl.saddr), packed_src_ip, length)
|
||||
pnl.sxport.port = socket.htons(src_port)
|
||||
memmove(addressof(pnl.daddr), packed_dst_ip, length)
|
||||
pnl.sxport.port = socket.htons(src_port)
|
||||
pnl.dxport.port = socket.htons(dst_port)
|
||||
|
||||
ioctl(pf_get_dev(), DIOCNATLOOK,
|
||||
ioctl(pf_get_dev(), osdefs.DIOCNATLOOK,
|
||||
(c_char * sizeof(pnl)).from_address(addressof(pnl)))
|
||||
|
||||
ip = socket.inet_ntop(
|
||||
@ -125,26 +146,26 @@ def pf_add_anchor_rule(type, name):
|
||||
ACTION_OFFSET = 0
|
||||
POOL_TICKET_OFFSET = 8
|
||||
ANCHOR_CALL_OFFSET = 1040
|
||||
RULE_ACTION_OFFSET = 3068
|
||||
RULE_ACTION_OFFSET = 3068 if osdefs.platform == 'darwin' else 2968
|
||||
|
||||
pr = pfioc_rule()
|
||||
ppa = pfioc_pooladdr()
|
||||
pr = osdefs.pfioc_rule()
|
||||
ppa = osdefs.pfioc_pooladdr()
|
||||
|
||||
ioctl(pf_get_dev(), DIOCBEGINADDRS, ppa)
|
||||
ioctl(pf_get_dev(), osdefs.DIOCBEGINADDRS, ppa)
|
||||
|
||||
memmove(addressof(pr) + POOL_TICKET_OFFSET, ppa[4:8], 4) # pool_ticket
|
||||
memmove(addressof(pr) + ANCHOR_CALL_OFFSET, name,
|
||||
min(MAXPATHLEN, len(name))) # anchor_call = name
|
||||
min(osdefs.MAXPATHLEN, len(name))) # anchor_call = name
|
||||
memmove(addressof(pr) + RULE_ACTION_OFFSET,
|
||||
struct.pack('I', type), 4) # rule.action = type
|
||||
|
||||
memmove(addressof(pr) + ACTION_OFFSET, struct.pack(
|
||||
'I', PF_CHANGE_GET_TICKET), 4) # action = PF_CHANGE_GET_TICKET
|
||||
ioctl(pf_get_dev(), DIOCCHANGERULE, pr)
|
||||
'I', osdefs.PF_CHANGE_GET_TICKET), 4) # action = PF_CHANGE_GET_TICKET
|
||||
ioctl(pf_get_dev(), osdefs.DIOCCHANGERULE, pr)
|
||||
|
||||
memmove(addressof(pr) + ACTION_OFFSET, struct.pack(
|
||||
'I', PF_CHANGE_ADD_TAIL), 4) # action = PF_CHANGE_ADD_TAIL
|
||||
ioctl(pf_get_dev(), DIOCCHANGERULE, pr)
|
||||
'I', osdefs.PF_CHANGE_ADD_TAIL), 4) # action = PF_CHANGE_ADD_TAIL
|
||||
ioctl(pf_get_dev(), osdefs.DIOCCHANGERULE, pr)
|
||||
|
||||
|
||||
class Method(BaseMethod):
|
||||
@ -156,19 +177,20 @@ class Method(BaseMethod):
|
||||
proxy = sock.getsockname()
|
||||
|
||||
argv = (sock.family, socket.IPPROTO_TCP,
|
||||
peer[0], peer[1], proxy[0], proxy[1])
|
||||
pfile.write("QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % argv)
|
||||
peer[0].encode("ASCII"), peer[1],
|
||||
proxy[0].encode("ASCII"), proxy[1])
|
||||
out_line = b"QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % argv
|
||||
pfile.write(out_line)
|
||||
pfile.flush()
|
||||
line = pfile.readline()
|
||||
debug2("QUERY_PF_NAT %d,%d,%s,%d,%s,%d" % argv + ' > ' + line)
|
||||
if line.startswith('QUERY_PF_NAT_SUCCESS '):
|
||||
(ip, port) = line[21:].split(',')
|
||||
return (ip, int(port))
|
||||
in_line = pfile.readline()
|
||||
debug2(out_line.decode("ASCII") + ' > ' + in_line.decode("ASCII"))
|
||||
if in_line.startswith(b'QUERY_PF_NAT_SUCCESS '):
|
||||
(ip, port) = in_line[21:].split(b',')
|
||||
return (ip.decode("ASCII"), int(port))
|
||||
|
||||
return sock.getsockname()
|
||||
|
||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
|
||||
global _pf_started_by_sshuttle
|
||||
tables = []
|
||||
translating_rules = []
|
||||
filtering_rules = []
|
||||
@ -180,57 +202,72 @@ class Method(BaseMethod):
|
||||
if udp:
|
||||
raise Exception("UDP not supported by pf method_name")
|
||||
|
||||
if subnets:
|
||||
if len(subnets) > 0:
|
||||
includes = []
|
||||
# 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("%s%s/%s" %
|
||||
("!" if sexclude else "", snet, swidth))
|
||||
includes.append(b"%s%s/%d" %
|
||||
(b"!" if sexclude else b"",
|
||||
snet.encode("ASCII"),
|
||||
swidth))
|
||||
|
||||
tables.append('table <forward_subnets> {%s}' % ','.join(includes))
|
||||
tables.append(
|
||||
b'table <forward_subnets> {%s}' % b','.join(includes))
|
||||
translating_rules.append(
|
||||
'rdr pass on lo0 proto tcp '
|
||||
'to <forward_subnets> -> 127.0.0.1 port %r' % port)
|
||||
b'rdr pass on lo0 proto tcp '
|
||||
b'to <forward_subnets> -> 127.0.0.1 port %r' % port)
|
||||
filtering_rules.append(
|
||||
'pass out route-to lo0 inet proto tcp '
|
||||
'to <forward_subnets> keep state')
|
||||
b'pass out route-to lo0 inet proto tcp '
|
||||
b'to <forward_subnets> keep state')
|
||||
|
||||
if dnsport:
|
||||
tables.append('table <dns_servers> {%s}' % ','.join(
|
||||
[ns[1] for ns in nslist]))
|
||||
translating_rules.append(
|
||||
'rdr pass on lo0 proto udp to '
|
||||
'<dns_servers> port 53 -> 127.0.0.1 port %r' % dnsport)
|
||||
filtering_rules.append(
|
||||
'pass out route-to lo0 inet proto udp to '
|
||||
'<dns_servers> port 53 keep state')
|
||||
if len(nslist) > 0:
|
||||
tables.append(
|
||||
b'table <dns_servers> {%s}' %
|
||||
b','.join([ns[1].encode("ASCII") for ns in nslist]))
|
||||
translating_rules.append(
|
||||
b'rdr pass on lo0 proto udp to '
|
||||
b'<dns_servers> port 53 -> 127.0.0.1 port %r' % dnsport)
|
||||
filtering_rules.append(
|
||||
b'pass out route-to lo0 inet proto udp to '
|
||||
b'<dns_servers> port 53 keep state')
|
||||
|
||||
rules = '\n'.join(tables + translating_rules + filtering_rules) \
|
||||
+ '\n'
|
||||
rules = b'\n'.join(tables + translating_rules + filtering_rules) \
|
||||
+ b'\n'
|
||||
assert isinstance(rules, bytes)
|
||||
debug3("rules:\n" + rules.decode("ASCII"))
|
||||
|
||||
pf_status = pfctl('-s all')[0]
|
||||
if '\nrdr-anchor "sshuttle" all\n' not in pf_status:
|
||||
pf_add_anchor_rule(PF_RDR, "sshuttle")
|
||||
if '\nanchor "sshuttle" all\n' not in pf_status:
|
||||
pf_add_anchor_rule(PF_PASS, "sshuttle")
|
||||
pf_status = pfctl('-s all')[0]
|
||||
if b'\nrdr-anchor "sshuttle" all\n' not in pf_status:
|
||||
pf_add_anchor_rule(osdefs.PF_RDR, b"sshuttle")
|
||||
if b'\nanchor "sshuttle" all\n' not in pf_status:
|
||||
pf_add_anchor_rule(osdefs.PF_PASS, b"sshuttle")
|
||||
|
||||
pfctl('-a sshuttle -f /dev/stdin', rules)
|
||||
if sys.platform == "darwin":
|
||||
o = pfctl('-E')
|
||||
_pf_context['Xtoken'] = \
|
||||
re.search(r'Token : (.+)', o[1]).group(1)
|
||||
elif 'INFO:\nStatus: Disabled' in pf_status:
|
||||
pfctl('-e')
|
||||
_pf_context['started_by_sshuttle'] = True
|
||||
else:
|
||||
pfctl('-a sshuttle -F all')
|
||||
if sys.platform == "darwin":
|
||||
pfctl('-X %s' % _pf_context['Xtoken'])
|
||||
elif _pf_context['started_by_sshuttle']:
|
||||
pfctl('-d')
|
||||
pfctl('-a sshuttle -f /dev/stdin', rules)
|
||||
if osdefs.platform == "darwin":
|
||||
o = pfctl('-E')
|
||||
_pf_context['Xtoken'] = \
|
||||
re.search(b'Token : (.+)', o[1]).group(1)
|
||||
elif b'INFO:\nStatus: Disabled' in pf_status:
|
||||
pfctl('-e')
|
||||
_pf_context['started_by_sshuttle'] = True
|
||||
|
||||
def restore_firewall(self, port, family, udp):
|
||||
if family != socket.AF_INET:
|
||||
raise Exception(
|
||||
'Address family "%s" unsupported by pf method_name'
|
||||
% family_to_string(family))
|
||||
if udp:
|
||||
raise Exception("UDP not supported by pf method_name")
|
||||
|
||||
pfctl('-a sshuttle -F all')
|
||||
if osdefs.platform == "darwin":
|
||||
if _pf_context['Xtoken'] is not None:
|
||||
pfctl('-X %s' % _pf_context['Xtoken'].decode("ASCII"))
|
||||
elif _pf_context['started_by_sshuttle']:
|
||||
pfctl('-d')
|
||||
|
||||
def firewall_command(self, line):
|
||||
if line.startswith('QUERY_PF_NAT '):
|
||||
|
@ -59,6 +59,7 @@ if recvmsg == "python":
|
||||
ip = socket.inet_ntop(family, cmsg_data[start:start + length])
|
||||
dstip = (ip, port)
|
||||
break
|
||||
print("xxxxx", srcip, dstip)
|
||||
return (srcip, dstip, data)
|
||||
elif recvmsg == "socket_ext":
|
||||
def recv_udp(listener, bufsize):
|
||||
@ -105,7 +106,12 @@ class Method(BaseMethod):
|
||||
def get_supported_features(self):
|
||||
result = super(Method, self).get_supported_features()
|
||||
result.ipv6 = True
|
||||
result.udp = True
|
||||
if recvmsg is None:
|
||||
result.udp = False
|
||||
result.dns = False
|
||||
else:
|
||||
result.udp = True
|
||||
result.dns = True
|
||||
return result
|
||||
|
||||
def get_tcp_dstip(self, sock):
|
||||
@ -162,6 +168,91 @@ class Method(BaseMethod):
|
||||
tproxy_chain = 'sshuttle-t-%s' % port
|
||||
divert_chain = 'sshuttle-d-%s' % port
|
||||
|
||||
# basic cleanup/setup of chains
|
||||
self.restore_firewall(port, family, udp)
|
||||
|
||||
_ipt('-N', mark_chain)
|
||||
_ipt('-F', mark_chain)
|
||||
_ipt('-N', divert_chain)
|
||||
_ipt('-F', divert_chain)
|
||||
_ipt('-N', tproxy_chain)
|
||||
_ipt('-F', tproxy_chain)
|
||||
_ipt('-I', 'OUTPUT', '1', '-j', mark_chain)
|
||||
_ipt('-I', 'PREROUTING', '1', '-j', tproxy_chain)
|
||||
_ipt('-A', divert_chain, '-j', 'MARK', '--set-mark', '1')
|
||||
_ipt('-A', divert_chain, '-j', 'ACCEPT')
|
||||
_ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain,
|
||||
'-m', 'tcp', '-p', 'tcp')
|
||||
|
||||
if udp:
|
||||
_ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain,
|
||||
'-m', 'udp', '-p', 'udp')
|
||||
|
||||
for f, ip in [i for i in nslist if i[0] == family]:
|
||||
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
|
||||
'--dest', '%s/32' % ip,
|
||||
'-m', 'udp', '-p', 'udp', '--dport', '53')
|
||||
_ipt('-A', tproxy_chain, '-j', 'TPROXY',
|
||||
'--tproxy-mark', '0x1/0x1',
|
||||
'--dest', '%s/32' % ip,
|
||||
'-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):
|
||||
if sexclude:
|
||||
_ipt('-A', mark_chain, '-j', 'RETURN',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'tcp', '-p', 'tcp')
|
||||
_ipt('-A', tproxy_chain, '-j', 'RETURN',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'tcp', '-p', 'tcp')
|
||||
else:
|
||||
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'tcp', '-p', 'tcp')
|
||||
_ipt('-A', tproxy_chain, '-j', 'TPROXY',
|
||||
'--tproxy-mark', '0x1/0x1',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'tcp', '-p', 'tcp',
|
||||
'--on-port', str(port))
|
||||
|
||||
if udp:
|
||||
if sexclude:
|
||||
_ipt('-A', mark_chain, '-j', 'RETURN',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'udp', '-p', 'udp')
|
||||
_ipt('-A', tproxy_chain, '-j', 'RETURN',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'udp', '-p', 'udp')
|
||||
else:
|
||||
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'udp', '-p', 'udp')
|
||||
_ipt('-A', tproxy_chain, '-j', 'TPROXY',
|
||||
'--tproxy-mark', '0x1/0x1',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'udp', '-p', 'udp',
|
||||
'--on-port', str(port))
|
||||
|
||||
def restore_firewall(self, port, family, udp):
|
||||
if family not in [socket.AF_INET, socket.AF_INET6]:
|
||||
raise Exception(
|
||||
'Address family "%s" unsupported by tproxy method'
|
||||
% family_to_string(family))
|
||||
|
||||
table = "mangle"
|
||||
|
||||
def _ipt(*args):
|
||||
return ipt(family, table, *args)
|
||||
|
||||
def _ipt_ttl(*args):
|
||||
return ipt_ttl(family, table, *args)
|
||||
|
||||
mark_chain = 'sshuttle-m-%s' % port
|
||||
tproxy_chain = 'sshuttle-t-%s' % port
|
||||
divert_chain = 'sshuttle-d-%s' % port
|
||||
|
||||
# basic cleanup/setup of chains
|
||||
if ipt_chain_exists(family, table, mark_chain):
|
||||
_ipt('-D', 'OUTPUT', '-j', mark_chain)
|
||||
@ -176,81 +267,3 @@ class Method(BaseMethod):
|
||||
if ipt_chain_exists(family, table, divert_chain):
|
||||
_ipt('-F', divert_chain)
|
||||
_ipt('-X', divert_chain)
|
||||
|
||||
if subnets or dnsport:
|
||||
_ipt('-N', mark_chain)
|
||||
_ipt('-F', mark_chain)
|
||||
_ipt('-N', divert_chain)
|
||||
_ipt('-F', divert_chain)
|
||||
_ipt('-N', tproxy_chain)
|
||||
_ipt('-F', tproxy_chain)
|
||||
_ipt('-I', 'OUTPUT', '1', '-j', mark_chain)
|
||||
_ipt('-I', 'PREROUTING', '1', '-j', tproxy_chain)
|
||||
_ipt('-A', divert_chain, '-j', 'MARK', '--set-mark', '1')
|
||||
_ipt('-A', divert_chain, '-j', 'ACCEPT')
|
||||
_ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain,
|
||||
'-m', 'tcp', '-p', 'tcp')
|
||||
if subnets and udp:
|
||||
_ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain,
|
||||
'-m', 'udp', '-p', 'udp')
|
||||
|
||||
if dnsport:
|
||||
for f, ip in [i for i in nslist if i[0] == family]:
|
||||
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
|
||||
'--dest', '%s/32' % ip,
|
||||
'-m', 'udp', '-p', 'udp', '--dport', '53')
|
||||
_ipt('-A', tproxy_chain, '-j', 'TPROXY',
|
||||
'--tproxy-mark', '0x1/0x1',
|
||||
'--dest', '%s/32' % ip,
|
||||
'-m', 'udp', '-p', 'udp', '--dport', '53',
|
||||
'--on-port', str(dnsport))
|
||||
|
||||
if subnets:
|
||||
for f, swidth, sexclude, snet \
|
||||
in sorted(subnets, key=lambda s: s[1], reverse=True):
|
||||
if sexclude:
|
||||
_ipt('-A', mark_chain, '-j', 'RETURN',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'tcp', '-p', 'tcp')
|
||||
_ipt('-A', tproxy_chain, '-j', 'RETURN',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'tcp', '-p', 'tcp')
|
||||
else:
|
||||
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'tcp', '-p', 'tcp')
|
||||
_ipt('-A', tproxy_chain, '-j', 'TPROXY',
|
||||
'--tproxy-mark', '0x1/0x1',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'tcp', '-p', 'tcp',
|
||||
'--on-port', str(port))
|
||||
|
||||
if sexclude and udp:
|
||||
_ipt('-A', mark_chain, '-j', 'RETURN',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'udp', '-p', 'udp')
|
||||
_ipt('-A', tproxy_chain, '-j', 'RETURN',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'udp', '-p', 'udp')
|
||||
elif udp:
|
||||
_ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'udp', '-p', 'udp')
|
||||
_ipt('-A', tproxy_chain, '-j', 'TPROXY',
|
||||
'--tproxy-mark', '0x1/0x1',
|
||||
'--dest', '%s/%s' % (snet, swidth),
|
||||
'-m', 'udp', '-p', 'udp',
|
||||
'--on-port', str(port))
|
||||
|
||||
def check_settings(self, udp, dns):
|
||||
if udp and recvmsg is None:
|
||||
Fatal("tproxy UDP support requires recvmsg function.\n")
|
||||
|
||||
if dns and recvmsg is None:
|
||||
Fatal("tproxy DNS support requires recvmsg function.\n")
|
||||
|
||||
if udp:
|
||||
debug1("tproxy UDP support enabled.\n")
|
||||
|
||||
if dns:
|
||||
debug1("tproxy DNS support enabled.\n")
|
||||
|
@ -17,22 +17,23 @@ from sshuttle.helpers import b, log, debug1, debug2, debug3, Fatal, \
|
||||
|
||||
|
||||
def _ipmatch(ipstr):
|
||||
if ipstr == 'default':
|
||||
ipstr = '0.0.0.0/0'
|
||||
m = re.match(r'^(\d+(\.\d+(\.\d+(\.\d+)?)?)?)(?:/(\d+))?$', ipstr)
|
||||
if ipstr == b('default'):
|
||||
ipstr = b('0.0.0.0/0')
|
||||
m = re.match(b('^(\d+(\.\d+(\.\d+(\.\d+)?)?)?)(?:/(\d+))?$'), ipstr)
|
||||
if m:
|
||||
g = m.groups()
|
||||
ips = g[0]
|
||||
width = int(g[4] or 32)
|
||||
if g[1] is None:
|
||||
ips += '.0.0.0'
|
||||
ips += b('.0.0.0')
|
||||
width = min(width, 8)
|
||||
elif g[2] is None:
|
||||
ips += '.0.0'
|
||||
ips += b('.0.0')
|
||||
width = min(width, 16)
|
||||
elif g[3] is None:
|
||||
ips += '.0'
|
||||
ips += b('.0')
|
||||
width = min(width, 24)
|
||||
ips = ips.decode("ASCII")
|
||||
return (struct.unpack('!I', socket.inet_aton(ips))[0], width)
|
||||
|
||||
|
||||
@ -57,11 +58,12 @@ def _shl(n, bits):
|
||||
|
||||
|
||||
def _list_routes():
|
||||
# FIXME: IPv4 only
|
||||
argv = ['netstat', '-rn']
|
||||
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE)
|
||||
routes = []
|
||||
for line in p.stdout:
|
||||
cols = re.split(r'\s+', line.decode("ASCII"))
|
||||
cols = re.split(b('\s+'), line)
|
||||
ipw = _ipmatch(cols[0])
|
||||
if not ipw:
|
||||
continue # some lines won't be parseable; never mind
|
||||
@ -249,20 +251,20 @@ def main(latency_control):
|
||||
mux.send(0, ssnet.CMD_ROUTES, b(routepkt))
|
||||
|
||||
hw = Hostwatch()
|
||||
hw.leftover = ''
|
||||
hw.leftover = b('')
|
||||
|
||||
def hostwatch_ready(sock):
|
||||
assert(hw.pid)
|
||||
content = hw.sock.recv(4096)
|
||||
if content:
|
||||
lines = (hw.leftover + content).split('\n')
|
||||
lines = (hw.leftover + content).split(b('\n'))
|
||||
if lines[-1]:
|
||||
# no terminating newline: entry isn't complete yet!
|
||||
hw.leftover = lines.pop()
|
||||
lines.append('')
|
||||
else:
|
||||
hw.leftover = ''
|
||||
mux.send(0, ssnet.CMD_HOST_LIST, '\n'.join(lines))
|
||||
hw.leftover = b('')
|
||||
mux.send(0, ssnet.CMD_HOST_LIST, b('\n').join(lines))
|
||||
else:
|
||||
raise Fatal('hostwatch process died')
|
||||
|
||||
@ -274,7 +276,7 @@ def main(latency_control):
|
||||
mux.got_host_req = got_host_req
|
||||
|
||||
def new_channel(channel, data):
|
||||
(family, dstip, dstport) = data.decode("ASCII").split(',', 2)
|
||||
(family, dstip, dstport) = data.split(b(','), 2)
|
||||
family = int(family)
|
||||
dstport = int(dstport)
|
||||
outwrap = ssnet.connect_dst(family, dstip, dstport)
|
||||
@ -332,14 +334,20 @@ def main(latency_control):
|
||||
|
||||
if dnshandlers:
|
||||
now = time.time()
|
||||
for channel, h in list(dnshandlers.items()):
|
||||
remove = []
|
||||
for channel, h in dnshandlers.items():
|
||||
if h.timeout < now or not h.ok:
|
||||
debug3('expiring dnsreqs channel=%d\n' % channel)
|
||||
del dnshandlers[channel]
|
||||
remove.append(channel)
|
||||
h.ok = False
|
||||
for channel in remove:
|
||||
del dnshandlers[channel]
|
||||
if udphandlers:
|
||||
for channel, h in list(udphandlers.items()):
|
||||
remove = []
|
||||
for channel, h in udphandlers.items():
|
||||
if not h.ok:
|
||||
debug3('expiring UDP channel=%d\n' % channel)
|
||||
del udphandlers[channel]
|
||||
remove.append(channel)
|
||||
h.ok = False
|
||||
for channel in remove:
|
||||
del udphandlers[channel]
|
||||
|
@ -89,10 +89,10 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
|
||||
b"\n")
|
||||
|
||||
pyscript = r"""
|
||||
import sys, os;
|
||||
import sys;
|
||||
verbosity=%d;
|
||||
sys.stdin = os.fdopen(0, "rb");
|
||||
exec(compile(sys.stdin.read(%d), "assembler.py", "exec"))
|
||||
stdin=getattr(sys.stdin,"buffer",sys.stdin);
|
||||
exec(compile(stdin.read(%d), "assembler.py", "exec"))
|
||||
""" % (helpers.verbose or 0, len(content))
|
||||
pyscript = re.sub(r'\s+', ' ', pyscript.strip())
|
||||
|
||||
@ -108,7 +108,7 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
|
||||
if python:
|
||||
pycmd = "'%s' -c '%s'" % (python, pyscript)
|
||||
else:
|
||||
pycmd = ("P=python2; $P -V 2>/dev/null || P=python; "
|
||||
pycmd = ("P=python3.5; $P -V 2>/dev/null || P=python; "
|
||||
"exec \"$P\" -c '%s'") % pyscript
|
||||
argv = (sshl +
|
||||
portl +
|
||||
|
@ -227,7 +227,7 @@ conflicts between client and server.
|
||||
|
||||
Unlike most VPNs, sshuttle forwards sessions, not packets.
|
||||
That is, it uses kernel transparent proxying (`iptables
|
||||
REDIRECT` rules on Linux, or `ipfw fwd` rules on BSD) to
|
||||
REDIRECT` rules on Linux) to
|
||||
capture outgoing TCP sessions, then creates entirely
|
||||
separate TCP sessions out to the original destination at
|
||||
the other end of the tunnel.
|
||||
@ -256,24 +256,6 @@ between the two separate streams, so a tcp-based tunnel is
|
||||
fine.
|
||||
|
||||
|
||||
# BUGS
|
||||
|
||||
On MacOS 10.6 (at least up to 10.6.6), your network will
|
||||
stop responding about 10 minutes after the first time you
|
||||
start sshuttle, because of a MacOS kernel bug relating to
|
||||
arp and the net.inet.ip.scopedroute sysctl. To fix it,
|
||||
just switch your wireless off and on. Sshuttle makes the
|
||||
kernel setting it changes permanent, so this won't happen
|
||||
again, even after a reboot.
|
||||
|
||||
On MacOS, sshuttle will set the kernel boot flag
|
||||
net.inet.ip.scopedroute to 0, which interferes with OS X
|
||||
Internet Sharing and some VPN clients. To reset this flag,
|
||||
you can remove any reference to net.inet.ip.scopedroute from
|
||||
/Library/Preferences/SystemConfiguration/com.apple.Boot.plist
|
||||
and reboot.
|
||||
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
`ssh`(1), `python`(1)
|
||||
|
@ -1,9 +1,5 @@
|
||||
from mock import Mock, patch, call
|
||||
import io
|
||||
import os
|
||||
import os.path
|
||||
import shutil
|
||||
import filecmp
|
||||
|
||||
import sshuttle.firewall
|
||||
|
||||
@ -19,27 +15,27 @@ NSLIST
|
||||
10,2404:6800:4004:80c::33
|
||||
PORTS 1024,1025,1026,1027
|
||||
GO 1
|
||||
HOST 1.2.3.3,existing
|
||||
""")
|
||||
stdout = Mock()
|
||||
return stdin, stdout
|
||||
|
||||
|
||||
@patch('sshuttle.firewall.HOSTSFILE', new='tmp/hosts')
|
||||
@patch('sshuttle.firewall.hostmap', new={
|
||||
'myhost': '1.2.3.4',
|
||||
'myotherhost': '1.2.3.5',
|
||||
})
|
||||
def test_rewrite_etc_hosts():
|
||||
if not os.path.isdir("tmp"):
|
||||
os.mkdir("tmp")
|
||||
def test_rewrite_etc_hosts(tmpdir):
|
||||
orig_hosts = tmpdir.join("hosts.orig")
|
||||
orig_hosts.write("1.2.3.3 existing\n")
|
||||
|
||||
with open("tmp/hosts.orig", "w") as f:
|
||||
f.write("1.2.3.3 existing\n")
|
||||
new_hosts = tmpdir.join("hosts")
|
||||
orig_hosts.copy(new_hosts)
|
||||
|
||||
shutil.copyfile("tmp/hosts.orig", "tmp/hosts")
|
||||
hostmap = {
|
||||
'myhost': '1.2.3.4',
|
||||
'myotherhost': '1.2.3.5',
|
||||
}
|
||||
with patch('sshuttle.firewall.HOSTSFILE', new=str(new_hosts)):
|
||||
sshuttle.firewall.rewrite_etc_hosts(hostmap, 10)
|
||||
|
||||
sshuttle.firewall.rewrite_etc_hosts(10)
|
||||
with open("tmp/hosts") as f:
|
||||
with new_hosts.open() as f:
|
||||
line = f.readline()
|
||||
s = line.split()
|
||||
assert s == ['1.2.3.3', 'existing']
|
||||
@ -57,39 +53,37 @@ def test_rewrite_etc_hosts():
|
||||
line = f.readline()
|
||||
assert line == ""
|
||||
|
||||
sshuttle.firewall.restore_etc_hosts(10)
|
||||
assert filecmp.cmp("tmp/hosts.orig", "tmp/hosts", shallow=False) is True
|
||||
with patch('sshuttle.firewall.HOSTSFILE', new=str(new_hosts)):
|
||||
sshuttle.firewall.restore_etc_hosts(10)
|
||||
assert orig_hosts.computehash() == new_hosts.computehash()
|
||||
|
||||
|
||||
@patch('sshuttle.firewall.HOSTSFILE', new='tmp/hosts')
|
||||
@patch('sshuttle.firewall.rewrite_etc_hosts')
|
||||
@patch('sshuttle.firewall.setup_daemon')
|
||||
@patch('sshuttle.firewall.get_method')
|
||||
def test_main(mock_get_method, mock_setup_daemon):
|
||||
def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts):
|
||||
stdin, stdout = setup_daemon()
|
||||
mock_setup_daemon.return_value = stdin, stdout
|
||||
|
||||
if not os.path.isdir("tmp"):
|
||||
os.mkdir("tmp")
|
||||
mock_get_method("not_auto").name = "test"
|
||||
mock_get_method.reset_mock()
|
||||
|
||||
sshuttle.firewall.main("test", False)
|
||||
sshuttle.firewall.main("not_auto", False)
|
||||
|
||||
with open("tmp/hosts") as f:
|
||||
line = f.readline()
|
||||
s = line.split()
|
||||
assert s == ['1.2.3.3', 'existing']
|
||||
assert mock_rewrite_etc_hosts.mock_calls == [
|
||||
call({'1.2.3.3': 'existing'}, 1024),
|
||||
call({}, 1024),
|
||||
]
|
||||
|
||||
line = f.readline()
|
||||
assert line == ""
|
||||
|
||||
stdout.mock_calls == [
|
||||
assert stdout.mock_calls == [
|
||||
call.write('READY test\n'),
|
||||
call.flush(),
|
||||
call.write('STARTED\n'),
|
||||
call.flush()
|
||||
]
|
||||
mock_setup_daemon.mock_calls == [call()]
|
||||
mock_get_method.mock_calls == [
|
||||
call('test'),
|
||||
assert mock_setup_daemon.mock_calls == [call()]
|
||||
assert mock_get_method.mock_calls == [
|
||||
call('not_auto'),
|
||||
call().setup_firewall(
|
||||
1024, 1026,
|
||||
[(10, u'2404:6800:4004:80c::33')],
|
||||
@ -103,7 +97,6 @@ def test_main(mock_get_method, mock_setup_daemon):
|
||||
2,
|
||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
||||
True),
|
||||
call().setup_firewall()(),
|
||||
call().setup_firewall(1024, 0, [], 10, [], True),
|
||||
call().setup_firewall(1025, 0, [], 2, [], True),
|
||||
call().restore_firewall(1024, 10, True),
|
||||
call().restore_firewall(1025, 2, True),
|
||||
]
|
||||
|
@ -11,12 +11,32 @@ import sshuttle.helpers
|
||||
@patch('sshuttle.helpers.sys.stderr')
|
||||
def test_log(mock_stderr, mock_stdout):
|
||||
sshuttle.helpers.log("message")
|
||||
sshuttle.helpers.log("abc")
|
||||
sshuttle.helpers.log("message 1\n")
|
||||
sshuttle.helpers.log("message 2\nline2\nline3\n")
|
||||
sshuttle.helpers.log("message 3\nline2\nline3")
|
||||
assert mock_stdout.mock_calls == [
|
||||
call.flush(),
|
||||
call.flush(),
|
||||
call.flush(),
|
||||
call.flush(),
|
||||
call.flush(),
|
||||
]
|
||||
assert mock_stderr.mock_calls == [
|
||||
call.write('prefix: message'),
|
||||
call.flush(),
|
||||
call.write('prefix: abc'),
|
||||
call.flush(),
|
||||
call.write('prefix: message 1\n'),
|
||||
call.flush(),
|
||||
call.write('prefix: message 2\n'),
|
||||
call.write('---> line2\n'),
|
||||
call.write('---> line3\n'),
|
||||
call.flush(),
|
||||
call.write('prefix: message 3\n'),
|
||||
call.write('---> line2\n'),
|
||||
call.write('---> line3\n'),
|
||||
call.flush(),
|
||||
]
|
||||
|
||||
|
||||
|
@ -3,6 +3,7 @@ from mock import Mock, patch, call
|
||||
import socket
|
||||
import struct
|
||||
|
||||
from sshuttle.helpers import Fatal
|
||||
from sshuttle.methods import get_method
|
||||
|
||||
|
||||
@ -11,6 +12,7 @@ def test_get_supported_features():
|
||||
features = method.get_supported_features()
|
||||
assert not features.ipv6
|
||||
assert not features.udp
|
||||
assert features.dns
|
||||
|
||||
|
||||
def test_get_tcp_dstip():
|
||||
@ -52,10 +54,18 @@ def test_setup_udp_listener():
|
||||
assert listener.mock_calls == []
|
||||
|
||||
|
||||
def test_check_settings():
|
||||
def test_assert_features():
|
||||
method = get_method('nat')
|
||||
method.check_settings(True, True)
|
||||
method.check_settings(False, True)
|
||||
features = method.get_supported_features()
|
||||
method.assert_features(features)
|
||||
|
||||
features.udp = True
|
||||
with pytest.raises(Fatal):
|
||||
method.assert_features(features)
|
||||
|
||||
features.ipv6 = True
|
||||
with pytest.raises(Fatal):
|
||||
method.assert_features(features)
|
||||
|
||||
|
||||
def test_firewall_command():
|
||||
@ -129,7 +139,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
||||
mock_ipt_ttl.reset_mock()
|
||||
mock_ipt.reset_mock()
|
||||
|
||||
method.setup_firewall(1025, 0, [], 2, [], False)
|
||||
method.restore_firewall(1025, 2, False)
|
||||
assert mock_ipt_chain_exists.mock_calls == [
|
||||
call(2, 'nat', 'sshuttle-1025')
|
||||
]
|
||||
|
@ -3,6 +3,8 @@ from mock import Mock, patch, call, ANY
|
||||
import socket
|
||||
|
||||
from sshuttle.methods import get_method
|
||||
from sshuttle.helpers import Fatal
|
||||
from sshuttle.methods.pf import OsDefs
|
||||
|
||||
|
||||
def test_get_supported_features():
|
||||
@ -10,8 +12,10 @@ def test_get_supported_features():
|
||||
features = method.get_supported_features()
|
||||
assert not features.ipv6
|
||||
assert not features.udp
|
||||
assert features.dns
|
||||
|
||||
|
||||
@patch('sshuttle.helpers.verbose', new=3)
|
||||
def test_get_tcp_dstip():
|
||||
sock = Mock()
|
||||
sock.getpeername.return_value = ("127.0.0.1", 1024)
|
||||
@ -20,7 +24,7 @@ def test_get_tcp_dstip():
|
||||
|
||||
firewall = Mock()
|
||||
firewall.pfile.readline.return_value = \
|
||||
"QUERY_PF_NAT_SUCCESS 127.0.0.3,1026\n"
|
||||
b"QUERY_PF_NAT_SUCCESS 127.0.0.3,1026\n"
|
||||
|
||||
method = get_method('pf')
|
||||
method.set_firewall(firewall)
|
||||
@ -31,7 +35,7 @@ def test_get_tcp_dstip():
|
||||
call.getsockname(),
|
||||
]
|
||||
assert firewall.mock_calls == [
|
||||
call.pfile.write('QUERY_PF_NAT 2,6,127.0.0.1,1024,127.0.0.2,1025\n'),
|
||||
call.pfile.write(b'QUERY_PF_NAT 2,6,127.0.0.1,1024,127.0.0.2,1025\n'),
|
||||
call.pfile.flush(),
|
||||
call.pfile.readline()
|
||||
]
|
||||
@ -67,16 +71,25 @@ def test_setup_udp_listener():
|
||||
assert listener.mock_calls == []
|
||||
|
||||
|
||||
def test_check_settings():
|
||||
def test_assert_features():
|
||||
method = get_method('pf')
|
||||
method.check_settings(True, True)
|
||||
method.check_settings(False, True)
|
||||
features = method.get_supported_features()
|
||||
method.assert_features(features)
|
||||
|
||||
features.udp = True
|
||||
with pytest.raises(Fatal):
|
||||
method.assert_features(features)
|
||||
|
||||
features.ipv6 = True
|
||||
with pytest.raises(Fatal):
|
||||
method.assert_features(features)
|
||||
|
||||
|
||||
@patch('sshuttle.methods.pf.osdefs', OsDefs('darwin'))
|
||||
@patch('sshuttle.methods.pf.sys.stdout')
|
||||
@patch('sshuttle.methods.pf.ioctl')
|
||||
@patch('sshuttle.methods.pf.pf_get_dev')
|
||||
def test_firewall_command(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
||||
def test_firewall_command_darwin(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
||||
method = get_method('pf')
|
||||
assert not method.firewall_command("somthing")
|
||||
|
||||
@ -87,7 +100,7 @@ def test_firewall_command(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
||||
|
||||
assert mock_pf_get_dev.mock_calls == [call()]
|
||||
assert mock_ioctl.mock_calls == [
|
||||
call(mock_pf_get_dev(), 3226747927, ANY),
|
||||
call(mock_pf_get_dev(), 0xc0544417, ANY),
|
||||
]
|
||||
assert mock_stdout.mock_calls == [
|
||||
call.write('QUERY_PF_NAT_SUCCESS 0.0.0.0,0\n'),
|
||||
@ -95,13 +108,46 @@ def test_firewall_command(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
||||
]
|
||||
|
||||
|
||||
# FIXME - test fails with platform=='darwin' due re.search not liking Mock
|
||||
# objects.
|
||||
@patch('sshuttle.methods.pf.sys.platform', 'not_darwin')
|
||||
@patch('sshuttle.methods.pf.osdefs', OsDefs('notdarwin'))
|
||||
@patch('sshuttle.methods.pf.sys.stdout')
|
||||
@patch('sshuttle.methods.pf.ioctl')
|
||||
@patch('sshuttle.methods.pf.pf_get_dev')
|
||||
def test_firewall_command_notdarwin(mock_pf_get_dev, mock_ioctl, mock_stdout):
|
||||
method = get_method('pf')
|
||||
assert not method.firewall_command("somthing")
|
||||
|
||||
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
|
||||
socket.AF_INET, socket.IPPROTO_TCP,
|
||||
"127.0.0.1", 1025, "127.0.0.2", 1024)
|
||||
assert method.firewall_command(command)
|
||||
|
||||
assert mock_pf_get_dev.mock_calls == [call()]
|
||||
assert mock_ioctl.mock_calls == [
|
||||
call(mock_pf_get_dev(), 0xc04c4417, ANY),
|
||||
]
|
||||
assert mock_stdout.mock_calls == [
|
||||
call.write('QUERY_PF_NAT_SUCCESS 0.0.0.0,0\n'),
|
||||
call.flush(),
|
||||
]
|
||||
|
||||
|
||||
def pfctl(args, stdin=None):
|
||||
if args == '-s all':
|
||||
return (b'INFO:\nStatus: Disabled\nanother mary had a little lamb\n',
|
||||
b'little lamb\n')
|
||||
if args == '-E':
|
||||
return (b'\n', b'Token : abcdefg\n')
|
||||
return None
|
||||
|
||||
|
||||
@patch('sshuttle.helpers.verbose', new=3)
|
||||
@patch('sshuttle.methods.pf.osdefs', OsDefs('darwin'))
|
||||
@patch('sshuttle.methods.pf.pfctl')
|
||||
@patch('sshuttle.methods.pf.ioctl')
|
||||
@patch('sshuttle.methods.pf.pf_get_dev')
|
||||
def test_setup_firewall(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
mock_pfctl.side_effect = pfctl
|
||||
|
||||
method = get_method('pf')
|
||||
assert method.name == 'pf'
|
||||
|
||||
@ -138,23 +184,119 @@ def test_setup_firewall(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
||||
False)
|
||||
assert mock_ioctl.mock_calls == [
|
||||
call(mock_pf_get_dev(), 3295691827, ANY),
|
||||
call(mock_pf_get_dev(), 3424666650, ANY),
|
||||
call(mock_pf_get_dev(), 3424666650, ANY),
|
||||
call(mock_pf_get_dev(), 3295691827, ANY),
|
||||
call(mock_pf_get_dev(), 3424666650, ANY),
|
||||
call(mock_pf_get_dev(), 3424666650, ANY),
|
||||
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||
call(mock_pf_get_dev(), 0xCC20441A, ANY),
|
||||
call(mock_pf_get_dev(), 0xCC20441A, ANY),
|
||||
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||
call(mock_pf_get_dev(), 0xCC20441A, ANY),
|
||||
call(mock_pf_get_dev(), 0xCC20441A, ANY),
|
||||
]
|
||||
assert mock_pfctl.mock_calls == [
|
||||
call('-s all'),
|
||||
call('-a sshuttle -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 proto tcp '
|
||||
b'to <forward_subnets> -> 127.0.0.1 port 1025\n'
|
||||
b'rdr pass on lo0 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 route-to lo0 inet proto udp '
|
||||
b'to <dns_servers> port 53 keep state\n'),
|
||||
call('-E'),
|
||||
]
|
||||
# FIXME - needs more work
|
||||
# print(mock_pfctl.mock_calls)
|
||||
# assert mock_pfctl.mock_calls == []
|
||||
mock_pf_get_dev.reset_mock()
|
||||
mock_ioctl.reset_mock()
|
||||
mock_pfctl.reset_mock()
|
||||
|
||||
method.setup_firewall(1025, 0, [], 2, [], False)
|
||||
method.restore_firewall(1025, 2, False)
|
||||
assert mock_ioctl.mock_calls == []
|
||||
assert mock_pfctl.mock_calls == [call('-a sshuttle -F all')]
|
||||
assert mock_pfctl.mock_calls == [
|
||||
call('-a sshuttle -F all'),
|
||||
call("-X abcdefg"),
|
||||
]
|
||||
mock_pf_get_dev.reset_mock()
|
||||
mock_pfctl.reset_mock()
|
||||
mock_ioctl.reset_mock()
|
||||
|
||||
|
||||
@patch('sshuttle.helpers.verbose', new=3)
|
||||
@patch('sshuttle.methods.pf.osdefs', OsDefs('notdarwin'))
|
||||
@patch('sshuttle.methods.pf.pfctl')
|
||||
@patch('sshuttle.methods.pf.ioctl')
|
||||
@patch('sshuttle.methods.pf.pf_get_dev')
|
||||
def test_setup_firewall_notdarwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
|
||||
mock_pfctl.side_effect = pfctl
|
||||
|
||||
method = get_method('pf')
|
||||
assert method.name == 'pf'
|
||||
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
method.setup_firewall(
|
||||
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')],
|
||||
True)
|
||||
assert str(excinfo.value) \
|
||||
== 'Address family "AF_INET6" unsupported by pf method_name'
|
||||
assert mock_pf_get_dev.mock_calls == []
|
||||
assert mock_ioctl.mock_calls == []
|
||||
assert mock_pfctl.mock_calls == []
|
||||
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
method.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')],
|
||||
True)
|
||||
assert str(excinfo.value) == 'UDP not supported by pf method_name'
|
||||
assert mock_pf_get_dev.mock_calls == []
|
||||
assert mock_ioctl.mock_calls == []
|
||||
assert mock_pfctl.mock_calls == []
|
||||
|
||||
method.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')],
|
||||
False)
|
||||
assert mock_ioctl.mock_calls == [
|
||||
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||
call(mock_pf_get_dev(), 0xCBE0441A, ANY),
|
||||
call(mock_pf_get_dev(), 0xCBE0441A, ANY),
|
||||
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||
call(mock_pf_get_dev(), 0xCBE0441A, ANY),
|
||||
call(mock_pf_get_dev(), 0xCBE0441A, ANY),
|
||||
]
|
||||
assert mock_pfctl.mock_calls == [
|
||||
call('-s all'),
|
||||
call('-a sshuttle -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 proto tcp '
|
||||
b'to <forward_subnets> -> 127.0.0.1 port 1025\n'
|
||||
b'rdr pass on lo0 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 route-to lo0 inet proto udp '
|
||||
b'to <dns_servers> port 53 keep state\n'),
|
||||
call('-e'),
|
||||
]
|
||||
mock_pf_get_dev.reset_mock()
|
||||
mock_ioctl.reset_mock()
|
||||
mock_pfctl.reset_mock()
|
||||
|
||||
method.restore_firewall(1025, 2, False)
|
||||
assert mock_ioctl.mock_calls == []
|
||||
assert mock_pfctl.mock_calls == [
|
||||
call('-a sshuttle -F all'),
|
||||
call("-d"),
|
||||
]
|
||||
mock_pf_get_dev.reset_mock()
|
||||
mock_pfctl.reset_mock()
|
||||
mock_ioctl.reset_mock()
|
||||
|
@ -3,11 +3,22 @@ from mock import Mock, patch, call
|
||||
from sshuttle.methods import get_method
|
||||
|
||||
|
||||
def test_get_supported_features():
|
||||
@patch("sshuttle.methods.tproxy.recvmsg")
|
||||
def test_get_supported_features_recvmsg(mock_recvmsg):
|
||||
method = get_method('tproxy')
|
||||
features = method.get_supported_features()
|
||||
assert features.ipv6
|
||||
assert features.udp
|
||||
assert features.dns
|
||||
|
||||
|
||||
@patch("sshuttle.methods.tproxy.recvmsg", None)
|
||||
def test_get_supported_features_norecvmsg():
|
||||
method = get_method('tproxy')
|
||||
features = method.get_supported_features()
|
||||
assert features.ipv6
|
||||
assert not features.udp
|
||||
assert not features.dns
|
||||
|
||||
|
||||
def test_get_tcp_dstip():
|
||||
@ -66,10 +77,10 @@ def test_setup_udp_listener():
|
||||
]
|
||||
|
||||
|
||||
def test_check_settings():
|
||||
def test_assert_features():
|
||||
method = get_method('tproxy')
|
||||
method.check_settings(True, True)
|
||||
method.check_settings(False, True)
|
||||
features = method.get_supported_features()
|
||||
method.assert_features(features)
|
||||
|
||||
|
||||
def test_firewall_command():
|
||||
@ -160,7 +171,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
||||
mock_ipt_ttl.reset_mock()
|
||||
mock_ipt.reset_mock()
|
||||
|
||||
method.setup_firewall(1025, 0, [], 10, [], True)
|
||||
method.restore_firewall(1025, 10, True)
|
||||
assert mock_ipt_chain_exists.mock_calls == [
|
||||
call(10, 'mangle', 'sshuttle-m-1025'),
|
||||
call(10, 'mangle', 'sshuttle-t-1025'),
|
||||
@ -250,7 +261,7 @@ def test_setup_firewall(mock_ipt_chain_exists, mock_ipt_ttl, mock_ipt):
|
||||
mock_ipt_ttl.reset_mock()
|
||||
mock_ipt.reset_mock()
|
||||
|
||||
method.setup_firewall(1025, 0, [], 2, [], True)
|
||||
method.restore_firewall(1025, 2, True)
|
||||
assert mock_ipt_chain_exists.mock_calls == [
|
||||
call(2, 'mangle', 'sshuttle-m-1025'),
|
||||
call(2, 'mangle', 'sshuttle-t-1025'),
|
||||
|
Loading…
Reference in New Issue
Block a user