mirror of
https://github.com/sshuttle/sshuttle.git
synced 2025-07-04 16:50:34 +02:00
Compare commits
59 Commits
Author | SHA1 | Date | |
---|---|---|---|
45f572f7a8 | |||
e7fe040e10 | |||
2e237b8fbe | |||
098916a8de | |||
d3624332dc | |||
b4b283b214 | |||
1c46f25e13 | |||
11838d65c2 | |||
e433c599e4 | |||
ba60d22478 | |||
3db38c992a | |||
1e81bf3dfc | |||
7362ba9f52 | |||
b207d1d0d6 | |||
56e3b22820 | |||
02fa49627f | |||
ce5187100c | |||
bdc7d3a97c | |||
90654b4fb9 | |||
6b4e36c528 | |||
eed917f062 | |||
74f2d9ca7e | |||
1e04eb1616 | |||
117afc7a68 | |||
c61984088b | |||
e63e121354 | |||
2b235331d0 | |||
2eeea9536a | |||
9a77d03edf | |||
4fdd715bc1 | |||
bea723c598 | |||
1ae4fce6b3 | |||
118171af7f | |||
3367124e6b | |||
aaa6062329 | |||
da4ce19121 | |||
12d4b304c3 | |||
bd97506f7d | |||
53c07f7d90 | |||
7e0c1534df | |||
a3fbf860ff | |||
7a9e36d211 | |||
65e81d51c6 | |||
43084eb49a | |||
bbb4d31c3f | |||
f7682d4c33 | |||
d07a775d50 | |||
50a6e87237 | |||
ed0a92e714 | |||
36a1d7ead9 | |||
43d6ad6a51 | |||
5ab76a6ba9 | |||
61f9ae6fb4 | |||
191df92824 | |||
6dfbc467c0 | |||
c06c972039 | |||
da62fe5b80 | |||
698351cf44 | |||
13457c773b |
13
CHANGES.rst
Normal file
13
CHANGES.rst
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Release 0.75 (Jan 12, 2016)
|
||||||
|
===========================
|
||||||
|
|
||||||
|
* Revert change that broke sshuttle entry point.
|
||||||
|
|
||||||
|
|
||||||
|
Release 0.74 (Jan 10, 2016)
|
||||||
|
===========================
|
||||||
|
|
||||||
|
* Add CHANGES.rst file.
|
||||||
|
* Numerous bug fixes.
|
||||||
|
* Python 3.5 fixes.
|
||||||
|
* PF fixes, especially for BSD.
|
55
README.rst
55
README.rst
@ -43,21 +43,13 @@ Client side Requirements
|
|||||||
| | | * IPv6 TCP + |
|
| | | * IPv6 TCP + |
|
||||||
| | | * IPv6 UDP + |
|
| | | * 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. |
|
| 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
|
Server side Requirements
|
||||||
------------------------
|
------------------------
|
||||||
Python 2.7 or Python 3.5. This should match what is used on the client side.
|
Python 2.7 or Python 3.5.
|
||||||
|
|
||||||
|
|
||||||
Additional Suggested Software
|
Additional Suggested Software
|
||||||
@ -80,25 +72,42 @@ later.
|
|||||||
|
|
||||||
There are some things you need to consider for TPROXY to work:
|
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
|
- The following commands need to be run first as root. This only needs to be
|
||||||
done once after booting up::
|
done once after booting up::
|
||||||
|
|
||||||
ip route add local default dev lo table 100
|
ip route add local default dev lo table 100
|
||||||
ip rule add fwmark 1 lookup 100
|
ip rule add fwmark 1 lookup 100
|
||||||
ip -6 route add local default dev lo table 100
|
ip -6 route add local default dev lo table 100
|
||||||
ip -6 rule add fwmark 1 lookup 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
|
- You may need to exclude the IP address of the server you are connecting to.
|
||||||
connection does not have IPv6 support, no IPv6 routes will exist, IPv6
|
Otherwise sshuttle may attempt to intercept the ssh packets, which will not
|
||||||
packets will not be generated and sshuttle cannot intercept them. Add some
|
work. Use the `--exclude` parameter for this.
|
||||||
dummy routes to external interfaces. Make sure they get removed however
|
|
||||||
after sshuttle exits.
|
- 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
|
Obtaining sshuttle
|
||||||
|
@ -1 +1 @@
|
|||||||
0.73
|
0.75
|
||||||
|
4
run
4
run
@ -1,6 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
if python2 -V 2>/dev/null; then
|
if python3.5 -V 2>/dev/null; then
|
||||||
exec python2 -m "sshuttle" "$@"
|
exec python3 -m "sshuttle" "$@"
|
||||||
else
|
else
|
||||||
exec python -m "sshuttle" "$@"
|
exec python -m "sshuttle" "$@"
|
||||||
fi
|
fi
|
||||||
|
3
setup.py
3
setup.py
@ -28,7 +28,7 @@ setup(
|
|||||||
url='https://github.com/sshuttle/sshuttle',
|
url='https://github.com/sshuttle/sshuttle',
|
||||||
author='Brian May',
|
author='Brian May',
|
||||||
author_email='brian@linuxpenguins.xyz',
|
author_email='brian@linuxpenguins.xyz',
|
||||||
description='Transparent proxy server that works as a poor man\'s VPN.',
|
description='Full-featured" VPN over an SSH tunnel',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
license="GPL2+",
|
license="GPL2+",
|
||||||
long_description=open('README.rst').read(),
|
long_description=open('README.rst').read(),
|
||||||
@ -48,5 +48,6 @@ setup(
|
|||||||
'sshuttle = sshuttle.__main__',
|
'sshuttle = sshuttle.__main__',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
tests_require=['pytest', 'mock'],
|
||||||
keywords="ssh vpn",
|
keywords="ssh vpn",
|
||||||
)
|
)
|
||||||
|
@ -6,6 +6,7 @@ import sshuttle.options as options
|
|||||||
import sshuttle.client as client
|
import sshuttle.client as client
|
||||||
import sshuttle.firewall as firewall
|
import sshuttle.firewall as firewall
|
||||||
import sshuttle.hostwatch as hostwatch
|
import sshuttle.hostwatch as hostwatch
|
||||||
|
import sshuttle.ssyslog as ssyslog
|
||||||
from sshuttle.helpers import family_ip_tuple, log, Fatal
|
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
|
N,auto-nets automatically determine subnets to route
|
||||||
dns capture local DNS requests and forward to the remote DNS server
|
dns capture local DNS requests and forward to the remote DNS server
|
||||||
ns-hosts= capture and forward remote DNS requests to the following servers
|
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
|
python= path to python interpreter on the remote server
|
||||||
r,remote= ssh hostname (and optional username) of remote sshuttle server
|
r,remote= ssh hostname (and optional username) of remote sshuttle server
|
||||||
x,exclude= exclude this subnet (can be used more than once)
|
x,exclude= exclude this subnet (can be used more than once)
|
||||||
@ -145,7 +146,7 @@ if opt.daemon:
|
|||||||
if opt.wrap:
|
if opt.wrap:
|
||||||
import sshuttle.ssnet as ssnet
|
import sshuttle.ssnet as ssnet
|
||||||
ssnet.MAX_CHANNEL = int(opt.wrap)
|
ssnet.MAX_CHANNEL = int(opt.wrap)
|
||||||
helpers.verbose = opt.verbose
|
helpers.verbose = opt.verbose or 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if opt.firewall:
|
if opt.firewall:
|
||||||
@ -181,7 +182,7 @@ try:
|
|||||||
includes = parse_subnet_file(opt.subnets)
|
includes = parse_subnet_file(opt.subnets)
|
||||||
if not opt.method:
|
if not opt.method:
|
||||||
method_name = "auto"
|
method_name = "auto"
|
||||||
elif opt.method in ["auto", "nat", "tproxy", "ipfw", "pf"]:
|
elif opt.method in ["auto", "nat", "tproxy", "pf"]:
|
||||||
method_name = opt.method
|
method_name = opt.method
|
||||||
else:
|
else:
|
||||||
o.fatal("method_name %s not supported" % opt.method)
|
o.fatal("method_name %s not supported" % opt.method)
|
||||||
@ -197,6 +198,9 @@ try:
|
|||||||
ipport_v6 = parse_ipport6(ip)
|
ipport_v6 = parse_ipport6(ip)
|
||||||
else:
|
else:
|
||||||
ipport_v4 = parse_ipport4(ip)
|
ipport_v4 = parse_ipport4(ip)
|
||||||
|
if opt.syslog:
|
||||||
|
ssyslog.start_syslog()
|
||||||
|
ssyslog.stderr_to_syslog()
|
||||||
return_code = client.main(ipport_v6, ipport_v4,
|
return_code = client.main(ipport_v6, ipport_v4,
|
||||||
opt.ssh_cmd,
|
opt.ssh_cmd,
|
||||||
remotename,
|
remotename,
|
||||||
@ -209,7 +213,7 @@ try:
|
|||||||
opt.auto_nets,
|
opt.auto_nets,
|
||||||
parse_subnets(includes),
|
parse_subnets(includes),
|
||||||
parse_subnets(excludes),
|
parse_subnets(excludes),
|
||||||
opt.syslog, opt.daemon, opt.pidfile)
|
opt.daemon, opt.pidfile)
|
||||||
|
|
||||||
if return_code == 0:
|
if return_code == 0:
|
||||||
log('Normal exit code, exiting...')
|
log('Normal exit code, exiting...')
|
||||||
|
@ -4,13 +4,15 @@ import imp
|
|||||||
|
|
||||||
z = zlib.decompressobj()
|
z = zlib.decompressobj()
|
||||||
while 1:
|
while 1:
|
||||||
name = sys.stdin.readline().strip()
|
name = stdin.readline().strip()
|
||||||
if name:
|
if name:
|
||||||
nbytes = int(sys.stdin.readline())
|
name = name.decode("ASCII")
|
||||||
|
|
||||||
|
nbytes = int(stdin.readline())
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
sys.stderr.write('server: assembling %r (%d bytes)\n'
|
sys.stderr.write('server: assembling %r (%d bytes)\n'
|
||||||
% (name, nbytes))
|
% (name, nbytes))
|
||||||
content = z.decompress(sys.stdin.read(nbytes))
|
content = z.decompress(stdin.read(nbytes))
|
||||||
|
|
||||||
module = imp.new_module(name)
|
module = imp.new_module(name)
|
||||||
parent, _, parent_name = name.rpartition(".")
|
parent, _, parent_name = name.rpartition(".")
|
||||||
|
@ -10,10 +10,11 @@ import sshuttle.ssnet as ssnet
|
|||||||
import sshuttle.ssh as ssh
|
import sshuttle.ssh as ssh
|
||||||
import sshuttle.ssyslog as ssyslog
|
import sshuttle.ssyslog as ssyslog
|
||||||
import sys
|
import sys
|
||||||
|
import platform
|
||||||
from sshuttle.ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
|
from sshuttle.ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
|
||||||
from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, islocal, \
|
from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, islocal, \
|
||||||
resolvconf_nameservers
|
resolvconf_nameservers
|
||||||
from sshuttle.methods import get_method
|
from sshuttle.methods import get_method, Features
|
||||||
|
|
||||||
_extra_fd = os.open('/dev/null', os.O_RDONLY)
|
_extra_fd = os.open('/dev/null', os.O_RDONLY)
|
||||||
|
|
||||||
@ -66,7 +67,7 @@ def daemonize():
|
|||||||
|
|
||||||
outfd = os.open(_pidname, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o666)
|
outfd = os.open(_pidname, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o666)
|
||||||
try:
|
try:
|
||||||
os.write(outfd, '%d\n' % os.getpid())
|
os.write(outfd, b'%d\n' % os.getpid())
|
||||||
finally:
|
finally:
|
||||||
os.close(outfd)
|
os.close(outfd)
|
||||||
os.chdir("/")
|
os.chdir("/")
|
||||||
@ -80,8 +81,6 @@ def daemonize():
|
|||||||
os.dup2(si.fileno(), 1)
|
os.dup2(si.fileno(), 1)
|
||||||
si.close()
|
si.close()
|
||||||
|
|
||||||
ssyslog.stderr_to_syslog()
|
|
||||||
|
|
||||||
|
|
||||||
def daemon_cleanup():
|
def daemon_cleanup():
|
||||||
try:
|
try:
|
||||||
@ -259,8 +258,8 @@ class FirewallClient:
|
|||||||
raise Fatal('%r expected STARTED, got %r' % (self.argv, line))
|
raise Fatal('%r expected STARTED, got %r' % (self.argv, line))
|
||||||
|
|
||||||
def sethostip(self, hostname, ip):
|
def sethostip(self, hostname, ip):
|
||||||
assert(not re.search(r'[^-\w]', hostname))
|
assert(not re.search(b'[^-\w]', hostname))
|
||||||
assert(not re.search(r'[^0-9.]', ip))
|
assert(not re.search(b'[^0-9.]', ip))
|
||||||
self.pfile.write(b'HOST %s,%s\n' % (hostname, ip))
|
self.pfile.write(b'HOST %s,%s\n' % (hostname, ip))
|
||||||
self.pfile.flush()
|
self.pfile.flush()
|
||||||
|
|
||||||
@ -276,18 +275,25 @@ udp_by_src = {}
|
|||||||
|
|
||||||
|
|
||||||
def expire_connections(now, mux):
|
def expire_connections(now, mux):
|
||||||
|
remove = []
|
||||||
for chan, timeout in dnsreqs.items():
|
for chan, timeout in dnsreqs.items():
|
||||||
if timeout < now:
|
if timeout < now:
|
||||||
debug3('expiring dnsreqs channel=%d\n' % chan)
|
debug3('expiring dnsreqs channel=%d\n' % chan)
|
||||||
|
remove.append(chan)
|
||||||
del mux.channels[chan]
|
del mux.channels[chan]
|
||||||
del dnsreqs[chan]
|
for chan in remove:
|
||||||
|
del dnsreqs[chan]
|
||||||
debug3('Remaining DNS requests: %d\n' % len(dnsreqs))
|
debug3('Remaining DNS requests: %d\n' % len(dnsreqs))
|
||||||
|
|
||||||
|
remove = []
|
||||||
for peer, (chan, timeout) in udp_by_src.items():
|
for peer, (chan, timeout) in udp_by_src.items():
|
||||||
if timeout < now:
|
if timeout < now:
|
||||||
debug3('expiring UDP channel channel=%d peer=%r\n' % (chan, peer))
|
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 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))
|
debug3('Remaining UDP channels: %d\n' % len(udp_by_src))
|
||||||
|
|
||||||
|
|
||||||
@ -329,7 +335,7 @@ def onaccept_tcp(listener, method, mux, handlers):
|
|||||||
|
|
||||||
|
|
||||||
def udp_done(chan, data, method, sock, dstip):
|
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))
|
srcip = (src, int(srcport))
|
||||||
debug3('doing send from %r to %r\n' % (srcip, dstip,))
|
debug3('doing send from %r to %r\n' % (srcip, dstip,))
|
||||||
method.send_udp(sock, srcip, dstip, data)
|
method.send_udp(sock, srcip, dstip, data)
|
||||||
@ -348,10 +354,10 @@ def onaccept_udp(listener, method, mux, handlers):
|
|||||||
chan = mux.next_channel()
|
chan = mux.next_channel()
|
||||||
mux.channels[chan] = lambda cmd, data: udp_done(
|
mux.channels[chan] = lambda cmd, data: udp_done(
|
||||||
chan, data, method, listener, dstip=srcip)
|
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
|
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)
|
mux.send(chan, ssnet.CMD_UDP_DATA, hdr + data)
|
||||||
|
|
||||||
expire_connections(now, mux)
|
expire_connections(now, mux)
|
||||||
@ -381,8 +387,10 @@ def ondns(listener, method, mux, handlers):
|
|||||||
|
|
||||||
def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
||||||
python, latency_control,
|
python, latency_control,
|
||||||
dns_listener, seed_hosts, auto_nets,
|
dns_listener, seed_hosts, auto_nets, daemon):
|
||||||
syslog, daemon):
|
|
||||||
|
debug1('Starting client with Python version %s\n'
|
||||||
|
% platform.python_version())
|
||||||
|
|
||||||
method = fw.method
|
method = fw.method
|
||||||
|
|
||||||
@ -429,21 +437,26 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
|||||||
if initstring != expected:
|
if initstring != expected:
|
||||||
raise Fatal('expected server init string %r; got %r'
|
raise Fatal('expected server init string %r; got %r'
|
||||||
% (expected, initstring))
|
% (expected, initstring))
|
||||||
debug1('connected.\n')
|
log('Connected.\n')
|
||||||
print('Connected.')
|
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
if daemon:
|
if daemon:
|
||||||
daemonize()
|
daemonize()
|
||||||
log('daemonizing (%s).\n' % _pidname)
|
log('daemonizing (%s).\n' % _pidname)
|
||||||
elif syslog:
|
|
||||||
debug1('switching to syslog.\n')
|
|
||||||
ssyslog.stderr_to_syslog()
|
|
||||||
|
|
||||||
def onroutes(routestr):
|
def onroutes(routestr):
|
||||||
if auto_nets:
|
if auto_nets:
|
||||||
for line in routestr.strip().split('\n'):
|
for line in routestr.strip().split(b'\n'):
|
||||||
(family, ip, width) = line.split(',', 2)
|
(family, ip, width) = line.split(b',', 2)
|
||||||
fw.auto_nets.append((int(family), ip, int(width)))
|
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
|
# we definitely want to do this *after* starting ssh, or we might end
|
||||||
# up intercepting the ssh connection!
|
# up intercepting the ssh connection!
|
||||||
@ -460,7 +473,7 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
|||||||
debug2('got host list: %r\n' % hostlist)
|
debug2('got host list: %r\n' % hostlist)
|
||||||
for line in hostlist.strip().split():
|
for line in hostlist.strip().split():
|
||||||
if line:
|
if line:
|
||||||
name, ip = line.split(',', 1)
|
name, ip = line.split(b',', 1)
|
||||||
fw.sethostip(name, ip)
|
fw.sethostip(name, ip)
|
||||||
mux.got_host_list = onhostlist
|
mux.got_host_list = onhostlist
|
||||||
|
|
||||||
@ -474,7 +487,7 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
|||||||
|
|
||||||
if seed_hosts is not None:
|
if seed_hosts is not None:
|
||||||
debug1('seed_hosts: %r\n' % seed_hosts)
|
debug1('seed_hosts: %r\n' % seed_hosts)
|
||||||
mux.send(0, ssnet.CMD_HOST_REQ, '\n'.join(seed_hosts))
|
mux.send(0, ssnet.CMD_HOST_REQ, b'\n'.join(seed_hosts))
|
||||||
|
|
||||||
while 1:
|
while 1:
|
||||||
rv = serverproc.poll()
|
rv = serverproc.poll()
|
||||||
@ -489,10 +502,8 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
|||||||
def main(listenip_v6, listenip_v4,
|
def main(listenip_v6, listenip_v4,
|
||||||
ssh_cmd, remotename, python, latency_control, dns, nslist,
|
ssh_cmd, remotename, python, latency_control, dns, nslist,
|
||||||
method_name, seed_hosts, auto_nets,
|
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:
|
if daemon:
|
||||||
try:
|
try:
|
||||||
check_daemon(pidfile)
|
check_daemon(pidfile)
|
||||||
@ -503,19 +514,45 @@ def main(listenip_v6, listenip_v4,
|
|||||||
|
|
||||||
fw = FirewallClient(method_name)
|
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 listenip_v6 == "auto":
|
||||||
if features.ipv6:
|
if avail.ipv6:
|
||||||
listenip_v6 = ('::1', 0)
|
listenip_v6 = ('::1', 0)
|
||||||
else:
|
else:
|
||||||
listenip_v6 = None
|
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":
|
if listenip_v4 == "auto":
|
||||||
listenip_v4 = ('127.0.0.1', 0)
|
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 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
|
# if both ports given, no need to search for a spare port
|
||||||
ports = [0, ]
|
ports = [0, ]
|
||||||
@ -534,7 +571,7 @@ def main(listenip_v6, listenip_v4,
|
|||||||
tcp_listener = MultiListener()
|
tcp_listener = MultiListener()
|
||||||
tcp_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
tcp_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
|
||||||
if udp:
|
if required.udp:
|
||||||
udp_listener = MultiListener(socket.SOCK_DGRAM)
|
udp_listener = MultiListener(socket.SOCK_DGRAM)
|
||||||
udp_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
udp_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
else:
|
else:
|
||||||
@ -582,10 +619,7 @@ def main(listenip_v6, listenip_v4,
|
|||||||
udp_listener.print_listening("UDP redirector")
|
udp_listener.print_listening("UDP redirector")
|
||||||
|
|
||||||
bound = False
|
bound = False
|
||||||
if dns or nslist:
|
if required.dns:
|
||||||
if dns:
|
|
||||||
nslist += resolvconf_nameservers()
|
|
||||||
dns = True
|
|
||||||
# search for spare port for DNS
|
# search for spare port for DNS
|
||||||
debug2('Binding DNS:')
|
debug2('Binding DNS:')
|
||||||
ports = range(12300, 9000, -1)
|
ports = range(12300, 9000, -1)
|
||||||
@ -626,22 +660,45 @@ def main(listenip_v6, listenip_v4,
|
|||||||
dnsport_v4 = 0
|
dnsport_v4 = 0
|
||||||
dns_listener = None
|
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)
|
fw.method.setup_tcp_listener(tcp_listener)
|
||||||
if udp_listener:
|
if udp_listener:
|
||||||
fw.method.setup_udp_listener(udp_listener)
|
fw.method.setup_udp_listener(udp_listener)
|
||||||
if dns_listener:
|
if dns_listener:
|
||||||
fw.method.setup_udp_listener(dns_listener)
|
fw.method.setup_udp_listener(dns_listener)
|
||||||
|
|
||||||
|
# start the firewall
|
||||||
fw.setup(subnets_include, subnets_exclude, nslist,
|
fw.setup(subnets_include, subnets_exclude, nslist,
|
||||||
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4,
|
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4,
|
||||||
udp)
|
required.udp)
|
||||||
|
|
||||||
|
# start the client process
|
||||||
try:
|
try:
|
||||||
return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
||||||
python, latency_control, dns_listener,
|
python, latency_control, dns_listener,
|
||||||
seed_hosts, auto_nets, syslog,
|
seed_hosts, auto_nets, daemon)
|
||||||
daemon)
|
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
if daemon:
|
if daemon:
|
||||||
|
@ -4,14 +4,15 @@ import signal
|
|||||||
import sshuttle.ssyslog as ssyslog
|
import sshuttle.ssyslog as ssyslog
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
|
import traceback
|
||||||
from sshuttle.helpers import debug1, debug2, Fatal
|
from sshuttle.helpers import debug1, debug2, Fatal
|
||||||
from sshuttle.methods import get_auto_method, get_method
|
from sshuttle.methods import get_auto_method, get_method
|
||||||
|
|
||||||
hostmap = {}
|
|
||||||
HOSTSFILE = '/etc/hosts'
|
HOSTSFILE = '/etc/hosts'
|
||||||
|
|
||||||
|
|
||||||
def rewrite_etc_hosts(port):
|
def rewrite_etc_hosts(hostmap, port):
|
||||||
BAKFILE = '%s.sbak' % HOSTSFILE
|
BAKFILE = '%s.sbak' % HOSTSFILE
|
||||||
APPEND = '# sshuttle-firewall-%d AUTOCREATED' % port
|
APPEND = '# sshuttle-firewall-%d AUTOCREATED' % port
|
||||||
old_content = ''
|
old_content = ''
|
||||||
@ -36,19 +37,17 @@ def rewrite_etc_hosts(port):
|
|||||||
f.write('%-30s %s\n' % ('%s %s' % (ip, name), APPEND))
|
f.write('%-30s %s\n' % ('%s %s' % (ip, name), APPEND))
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
if st:
|
if st is not None:
|
||||||
os.chown(tmpname, st.st_uid, st.st_gid)
|
os.chown(tmpname, st.st_uid, st.st_gid)
|
||||||
os.chmod(tmpname, st.st_mode)
|
os.chmod(tmpname, st.st_mode)
|
||||||
else:
|
else:
|
||||||
os.chown(tmpname, 0, 0)
|
os.chown(tmpname, 0, 0)
|
||||||
os.chmod(tmpname, 0o644)
|
os.chmod(tmpname, 0o600)
|
||||||
os.rename(tmpname, HOSTSFILE)
|
os.rename(tmpname, HOSTSFILE)
|
||||||
|
|
||||||
|
|
||||||
def restore_etc_hosts(port):
|
def restore_etc_hosts(port):
|
||||||
global hostmap
|
rewrite_etc_hosts({}, port)
|
||||||
hostmap = {}
|
|
||||||
rewrite_etc_hosts(port)
|
|
||||||
|
|
||||||
|
|
||||||
# Isolate function that needs to be replaced for tests
|
# Isolate function that needs to be replaced for tests
|
||||||
@ -85,6 +84,10 @@ def setup_daemon():
|
|||||||
# are hopefully harmless.
|
# are hopefully harmless.
|
||||||
def main(method_name, syslog):
|
def main(method_name, syslog):
|
||||||
stdin, stdout = setup_daemon()
|
stdin, stdout = setup_daemon()
|
||||||
|
hostmap = {}
|
||||||
|
|
||||||
|
debug1('firewall manager: Starting firewall with Python version %s\n'
|
||||||
|
% platform.python_version())
|
||||||
|
|
||||||
if method_name == "auto":
|
if method_name == "auto":
|
||||||
method = get_auto_method()
|
method = get_auto_method()
|
||||||
@ -95,7 +98,7 @@ def main(method_name, syslog):
|
|||||||
ssyslog.start_syslog()
|
ssyslog.start_syslog()
|
||||||
ssyslog.stderr_to_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.write('READY %s\n' % method.name)
|
||||||
stdout.flush()
|
stdout.flush()
|
||||||
|
|
||||||
@ -120,7 +123,7 @@ def main(method_name, syslog):
|
|||||||
except:
|
except:
|
||||||
raise Fatal('firewall: expected route or NSLIST but got %r' % line)
|
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))
|
||||||
debug2('Got subnets: %r\n' % subnets)
|
debug2('firewall manager: Got subnets: %r\n' % subnets)
|
||||||
|
|
||||||
nslist = []
|
nslist = []
|
||||||
if line != 'NSLIST\n':
|
if line != 'NSLIST\n':
|
||||||
@ -136,8 +139,8 @@ def main(method_name, syslog):
|
|||||||
except:
|
except:
|
||||||
raise Fatal('firewall: expected nslist or PORTS but got %r' % line)
|
raise Fatal('firewall: expected nslist or PORTS but got %r' % line)
|
||||||
nslist.append((int(family), ip))
|
nslist.append((int(family), ip))
|
||||||
debug2('Got partial nslist: %r\n' % nslist)
|
debug2('firewall manager: Got partial nslist: %r\n' % nslist)
|
||||||
debug2('Got nslist: %r\n' % nslist)
|
debug2('firewall manager: Got nslist: %r\n' % nslist)
|
||||||
|
|
||||||
if not line.startswith('PORTS '):
|
if not line.startswith('PORTS '):
|
||||||
raise Fatal('firewall: expected PORTS but got %r' % line)
|
raise Fatal('firewall: expected PORTS but got %r' % line)
|
||||||
@ -159,7 +162,7 @@ def main(method_name, syslog):
|
|||||||
assert(dnsport_v4 >= 0)
|
assert(dnsport_v4 >= 0)
|
||||||
assert(dnsport_v4 <= 65535)
|
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))
|
% (port_v6, port_v4, dnsport_v6, dnsport_v4))
|
||||||
|
|
||||||
line = stdin.readline(128)
|
line = stdin.readline(128)
|
||||||
@ -170,29 +173,27 @@ def main(method_name, syslog):
|
|||||||
|
|
||||||
_, _, udp = line.partition(" ")
|
_, _, udp = line.partition(" ")
|
||||||
udp = bool(int(udp))
|
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:
|
try:
|
||||||
do_wait = None
|
debug1('firewall manager: setting up.\n')
|
||||||
debug1('firewall manager: starting transproxy.\n')
|
|
||||||
|
|
||||||
nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6]
|
if len(subnets_v6) > 0 or len(nslist_v6) > 0:
|
||||||
subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6]
|
debug2('firewall manager: setting up IPv6.\n')
|
||||||
if port_v6 > 0:
|
method.setup_firewall(
|
||||||
do_wait = method.setup_firewall(
|
|
||||||
port_v6, dnsport_v6, nslist_v6,
|
port_v6, dnsport_v6, nslist_v6,
|
||||||
socket.AF_INET6, subnets_v6, udp)
|
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]
|
if len(subnets_v4) > 0 or len(nslist_v4) > 0:
|
||||||
subnets_v4 = [i for i in subnets if i[0] == socket.AF_INET]
|
debug2('firewall manager: setting up IPv4.\n')
|
||||||
if port_v4 > 0:
|
method.setup_firewall(
|
||||||
do_wait = method.setup_firewall(
|
|
||||||
port_v4, dnsport_v4, nslist_v4,
|
port_v4, dnsport_v4, nslist_v4,
|
||||||
socket.AF_INET, subnets_v4, udp)
|
socket.AF_INET, subnets_v4, udp)
|
||||||
elif len(subnets_v4) > 0:
|
|
||||||
debug1('IPv4 subnets defined but IPv4 disabled\n')
|
|
||||||
|
|
||||||
stdout.write('STARTED\n')
|
stdout.write('STARTED\n')
|
||||||
|
|
||||||
@ -207,16 +208,15 @@ def main(method_name, syslog):
|
|||||||
# to stay running so that we don't need a *second* password
|
# to stay running so that we don't need a *second* password
|
||||||
# authentication at shutdown time - that cleanup is important!
|
# authentication at shutdown time - that cleanup is important!
|
||||||
while 1:
|
while 1:
|
||||||
if do_wait is not None:
|
|
||||||
do_wait()
|
|
||||||
line = stdin.readline(128)
|
line = stdin.readline(128)
|
||||||
if line.startswith('HOST '):
|
if line.startswith('HOST '):
|
||||||
(name, ip) = line[5:].strip().split(',', 1)
|
(name, ip) = line[5:].strip().split(',', 1)
|
||||||
hostmap[name] = ip
|
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:
|
elif line:
|
||||||
if not method.firewall_command(line):
|
if not method.firewall_command(line):
|
||||||
raise Fatal('expected EOF, got %r' % line)
|
raise Fatal('firewall: expected command, got %r' % line)
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
finally:
|
finally:
|
||||||
@ -224,8 +224,41 @@ def main(method_name, syslog):
|
|||||||
debug1('firewall manager: undoing changes.\n')
|
debug1('firewall manager: undoing changes.\n')
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
if port_v6:
|
|
||||||
method.setup_firewall(port_v6, 0, [], socket.AF_INET6, [], udp)
|
try:
|
||||||
if port_v4:
|
if len(subnets_v6) > 0 or len(nslist_v6) > 0:
|
||||||
method.setup_firewall(port_v4, 0, [], socket.AF_INET, [], udp)
|
debug2('firewall manager: undoing IPv6 changes.\n')
|
||||||
restore_etc_hosts(port_v6 or port_v4)
|
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
|
||||||
|
@ -7,9 +7,17 @@ verbose = 0
|
|||||||
|
|
||||||
|
|
||||||
def log(s):
|
def log(s):
|
||||||
|
global logprefix
|
||||||
try:
|
try:
|
||||||
sys.stdout.flush()
|
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()
|
sys.stderr.flush()
|
||||||
except IOError:
|
except IOError:
|
||||||
# this could happen if stderr gets forcibly disconnected, eg. because
|
# this could happen if stderr gets forcibly disconnected, eg. because
|
||||||
|
@ -5,6 +5,7 @@ import select
|
|||||||
import errno
|
import errno
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import platform
|
||||||
|
|
||||||
import subprocess as ssubprocess
|
import subprocess as ssubprocess
|
||||||
import sshuttle.helpers as helpers
|
import sshuttle.helpers as helpers
|
||||||
@ -35,8 +36,9 @@ def write_host_cache():
|
|||||||
try:
|
try:
|
||||||
f = open(tmpname, 'wb')
|
f = open(tmpname, 'wb')
|
||||||
for name, ip in sorted(hostnames.items()):
|
for name, ip in sorted(hostnames.items()):
|
||||||
f.write('%s,%s\n' % (name, ip))
|
f.write(('%s,%s\n' % (name, ip)).encode("ASCII"))
|
||||||
f.close()
|
f.close()
|
||||||
|
os.chmod(tmpname, 0o600)
|
||||||
os.rename(tmpname, CACHEFILE)
|
os.rename(tmpname, CACHEFILE)
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
@ -120,7 +122,7 @@ def _check_netstat():
|
|||||||
argv = ['netstat', '-n']
|
argv = ['netstat', '-n']
|
||||||
try:
|
try:
|
||||||
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
|
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
|
||||||
content = p.stdout.read()
|
content = p.stdout.read().decode("ASCII")
|
||||||
p.wait()
|
p.wait()
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
log('%r failed: %r\n' % (argv, e))
|
log('%r failed: %r\n' % (argv, e))
|
||||||
@ -254,6 +256,9 @@ def hw_main(seed_hosts):
|
|||||||
else:
|
else:
|
||||||
helpers.logprefix = 'hostwatch: '
|
helpers.logprefix = 'hostwatch: '
|
||||||
|
|
||||||
|
debug1('Starting hostwatch with Python version %s\n'
|
||||||
|
% platform.python_version())
|
||||||
|
|
||||||
read_host_cache()
|
read_host_cache()
|
||||||
|
|
||||||
_enqueue(_check_etc_hosts)
|
_enqueue(_check_etc_hosts)
|
||||||
|
@ -39,6 +39,7 @@ class BaseMethod(object):
|
|||||||
result = Features()
|
result = Features()
|
||||||
result.ipv6 = False
|
result.ipv6 = False
|
||||||
result.udp = False
|
result.udp = False
|
||||||
|
result.dns = True
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_tcp_dstip(self, sock):
|
def get_tcp_dstip(self, sock):
|
||||||
@ -61,13 +62,20 @@ class BaseMethod(object):
|
|||||||
def setup_udp_listener(self, udp_listener):
|
def setup_udp_listener(self, udp_listener):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def check_settings(self, udp, dns):
|
def assert_features(self, features):
|
||||||
if udp:
|
avail = self.get_supported_features()
|
||||||
Fatal("UDP support not supported with method %s.\n" % self.name)
|
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):
|
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def restore_firewall(self, port, family, udp):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
def firewall_command(self, line):
|
def firewall_command(self, line):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -86,14 +94,12 @@ def get_method(method_name):
|
|||||||
|
|
||||||
|
|
||||||
def get_auto_method():
|
def get_auto_method():
|
||||||
if _program_exists('ipfw'):
|
if _program_exists('iptables'):
|
||||||
method_name = "ipfw"
|
|
||||||
elif _program_exists('iptables'):
|
|
||||||
method_name = "nat"
|
method_name = "nat"
|
||||||
elif _program_exists('pfctl'):
|
elif _program_exists('pfctl'):
|
||||||
method_name = "pf"
|
method_name = "pf"
|
||||||
else:
|
else:
|
||||||
raise Fatal(
|
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)
|
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
|
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
|
# basic cleanup/setup of chains
|
||||||
if ipt_chain_exists(family, table, chain):
|
if ipt_chain_exists(family, table, chain):
|
||||||
nonfatal(_ipt, '-D', 'OUTPUT', '-j', chain)
|
nonfatal(_ipt, '-D', 'OUTPUT', '-j', chain)
|
||||||
nonfatal(_ipt, '-D', 'PREROUTING', '-j', chain)
|
nonfatal(_ipt, '-D', 'PREROUTING', '-j', chain)
|
||||||
nonfatal(_ipt, '-F', chain)
|
nonfatal(_ipt, '-F', chain)
|
||||||
_ipt('-X', 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 fcntl import ioctl
|
||||||
from ctypes import c_char, c_uint8, c_uint16, c_uint32, Union, Structure, \
|
from ctypes import c_char, c_uint8, c_uint16, c_uint32, Union, Structure, \
|
||||||
sizeof, addressof, memmove
|
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
|
from sshuttle.methods import BaseMethod
|
||||||
|
|
||||||
|
|
||||||
@ -24,67 +24,88 @@ def pfctl(args, stdin=None):
|
|||||||
|
|
||||||
return o
|
return o
|
||||||
|
|
||||||
_pf_context = {'started_by_sshuttle': False, 'Xtoken': ''}
|
_pf_context = {'started_by_sshuttle': False, 'Xtoken': None}
|
||||||
|
|
||||||
|
|
||||||
# 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_fd = 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():
|
def pf_get_dev():
|
||||||
global _pf_fd
|
global _pf_fd
|
||||||
if _pf_fd is None:
|
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)
|
assert len(packed_src_ip) == len(packed_dst_ip)
|
||||||
length = len(packed_src_ip)
|
length = len(packed_src_ip)
|
||||||
|
|
||||||
pnl = pfioc_natlook()
|
pnl = osdefs.pfioc_natlook()
|
||||||
pnl.proto = proto
|
pnl.proto = proto
|
||||||
pnl.direction = PF_OUT
|
pnl.direction = osdefs.PF_OUT
|
||||||
pnl.af = family
|
pnl.af = family
|
||||||
memmove(addressof(pnl.saddr), packed_src_ip, length)
|
memmove(addressof(pnl.saddr), packed_src_ip, length)
|
||||||
pnl.sxport.port = socket.htons(src_port)
|
|
||||||
memmove(addressof(pnl.daddr), packed_dst_ip, length)
|
memmove(addressof(pnl.daddr), packed_dst_ip, length)
|
||||||
|
pnl.sxport.port = socket.htons(src_port)
|
||||||
pnl.dxport.port = socket.htons(dst_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)))
|
(c_char * sizeof(pnl)).from_address(addressof(pnl)))
|
||||||
|
|
||||||
ip = socket.inet_ntop(
|
ip = socket.inet_ntop(
|
||||||
@ -125,26 +146,26 @@ def pf_add_anchor_rule(type, name):
|
|||||||
ACTION_OFFSET = 0
|
ACTION_OFFSET = 0
|
||||||
POOL_TICKET_OFFSET = 8
|
POOL_TICKET_OFFSET = 8
|
||||||
ANCHOR_CALL_OFFSET = 1040
|
ANCHOR_CALL_OFFSET = 1040
|
||||||
RULE_ACTION_OFFSET = 3068
|
RULE_ACTION_OFFSET = 3068 if osdefs.platform == 'darwin' else 2968
|
||||||
|
|
||||||
pr = pfioc_rule()
|
pr = osdefs.pfioc_rule()
|
||||||
ppa = pfioc_pooladdr()
|
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) + POOL_TICKET_OFFSET, ppa[4:8], 4) # pool_ticket
|
||||||
memmove(addressof(pr) + ANCHOR_CALL_OFFSET, name,
|
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,
|
memmove(addressof(pr) + RULE_ACTION_OFFSET,
|
||||||
struct.pack('I', type), 4) # rule.action = type
|
struct.pack('I', type), 4) # rule.action = type
|
||||||
|
|
||||||
memmove(addressof(pr) + ACTION_OFFSET, struct.pack(
|
memmove(addressof(pr) + ACTION_OFFSET, struct.pack(
|
||||||
'I', PF_CHANGE_GET_TICKET), 4) # action = PF_CHANGE_GET_TICKET
|
'I', osdefs.PF_CHANGE_GET_TICKET), 4) # action = PF_CHANGE_GET_TICKET
|
||||||
ioctl(pf_get_dev(), DIOCCHANGERULE, pr)
|
ioctl(pf_get_dev(), osdefs.DIOCCHANGERULE, pr)
|
||||||
|
|
||||||
memmove(addressof(pr) + ACTION_OFFSET, struct.pack(
|
memmove(addressof(pr) + ACTION_OFFSET, struct.pack(
|
||||||
'I', PF_CHANGE_ADD_TAIL), 4) # action = PF_CHANGE_ADD_TAIL
|
'I', osdefs.PF_CHANGE_ADD_TAIL), 4) # action = PF_CHANGE_ADD_TAIL
|
||||||
ioctl(pf_get_dev(), DIOCCHANGERULE, pr)
|
ioctl(pf_get_dev(), osdefs.DIOCCHANGERULE, pr)
|
||||||
|
|
||||||
|
|
||||||
class Method(BaseMethod):
|
class Method(BaseMethod):
|
||||||
@ -156,19 +177,20 @@ class Method(BaseMethod):
|
|||||||
proxy = sock.getsockname()
|
proxy = sock.getsockname()
|
||||||
|
|
||||||
argv = (sock.family, socket.IPPROTO_TCP,
|
argv = (sock.family, socket.IPPROTO_TCP,
|
||||||
peer[0], peer[1], proxy[0], proxy[1])
|
peer[0].encode("ASCII"), peer[1],
|
||||||
pfile.write("QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % argv)
|
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()
|
pfile.flush()
|
||||||
line = pfile.readline()
|
in_line = pfile.readline()
|
||||||
debug2("QUERY_PF_NAT %d,%d,%s,%d,%s,%d" % argv + ' > ' + line)
|
debug2(out_line.decode("ASCII") + ' > ' + in_line.decode("ASCII"))
|
||||||
if line.startswith('QUERY_PF_NAT_SUCCESS '):
|
if in_line.startswith(b'QUERY_PF_NAT_SUCCESS '):
|
||||||
(ip, port) = line[21:].split(',')
|
(ip, port) = in_line[21:].split(b',')
|
||||||
return (ip, int(port))
|
return (ip.decode("ASCII"), int(port))
|
||||||
|
|
||||||
return sock.getsockname()
|
return sock.getsockname()
|
||||||
|
|
||||||
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
|
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
|
||||||
global _pf_started_by_sshuttle
|
|
||||||
tables = []
|
tables = []
|
||||||
translating_rules = []
|
translating_rules = []
|
||||||
filtering_rules = []
|
filtering_rules = []
|
||||||
@ -180,57 +202,72 @@ class Method(BaseMethod):
|
|||||||
if udp:
|
if udp:
|
||||||
raise Exception("UDP not supported by pf method_name")
|
raise Exception("UDP not supported by pf method_name")
|
||||||
|
|
||||||
if subnets:
|
if len(subnets) > 0:
|
||||||
includes = []
|
includes = []
|
||||||
# If a given subnet is both included and excluded, list the
|
# If a given subnet is both included and excluded, list the
|
||||||
# exclusion first; the table will ignore the second, opposite
|
# exclusion first; the table will ignore the second, opposite
|
||||||
# definition
|
# definition
|
||||||
for f, swidth, sexclude, snet in sorted(
|
for f, swidth, sexclude, snet in sorted(
|
||||||
subnets, key=lambda s: (s[1], s[2]), reverse=True):
|
subnets, key=lambda s: (s[1], s[2]), reverse=True):
|
||||||
includes.append("%s%s/%s" %
|
includes.append(b"%s%s/%d" %
|
||||||
("!" if sexclude else "", snet, swidth))
|
(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(
|
translating_rules.append(
|
||||||
'rdr pass on lo0 proto tcp '
|
b'rdr pass on lo0 proto tcp '
|
||||||
'to <forward_subnets> -> 127.0.0.1 port %r' % port)
|
b'to <forward_subnets> -> 127.0.0.1 port %r' % port)
|
||||||
filtering_rules.append(
|
filtering_rules.append(
|
||||||
'pass out route-to lo0 inet proto tcp '
|
b'pass out route-to lo0 inet proto tcp '
|
||||||
'to <forward_subnets> keep state')
|
b'to <forward_subnets> keep state')
|
||||||
|
|
||||||
if dnsport:
|
if len(nslist) > 0:
|
||||||
tables.append('table <dns_servers> {%s}' % ','.join(
|
tables.append(
|
||||||
[ns[1] for ns in nslist]))
|
b'table <dns_servers> {%s}' %
|
||||||
translating_rules.append(
|
b','.join([ns[1].encode("ASCII") for ns in nslist]))
|
||||||
'rdr pass on lo0 proto udp to '
|
translating_rules.append(
|
||||||
'<dns_servers> port 53 -> 127.0.0.1 port %r' % dnsport)
|
b'rdr pass on lo0 proto udp to '
|
||||||
filtering_rules.append(
|
b'<dns_servers> port 53 -> 127.0.0.1 port %r' % dnsport)
|
||||||
'pass out route-to lo0 inet proto udp to '
|
filtering_rules.append(
|
||||||
'<dns_servers> port 53 keep state')
|
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) \
|
rules = b'\n'.join(tables + translating_rules + filtering_rules) \
|
||||||
+ '\n'
|
+ b'\n'
|
||||||
|
assert isinstance(rules, bytes)
|
||||||
|
debug3("rules:\n" + rules.decode("ASCII"))
|
||||||
|
|
||||||
pf_status = pfctl('-s all')[0]
|
pf_status = pfctl('-s all')[0]
|
||||||
if '\nrdr-anchor "sshuttle" all\n' not in pf_status:
|
if b'\nrdr-anchor "sshuttle" all\n' not in pf_status:
|
||||||
pf_add_anchor_rule(PF_RDR, "sshuttle")
|
pf_add_anchor_rule(osdefs.PF_RDR, b"sshuttle")
|
||||||
if '\nanchor "sshuttle" all\n' not in pf_status:
|
if b'\nanchor "sshuttle" all\n' not in pf_status:
|
||||||
pf_add_anchor_rule(PF_PASS, "sshuttle")
|
pf_add_anchor_rule(osdefs.PF_PASS, b"sshuttle")
|
||||||
|
|
||||||
pfctl('-a sshuttle -f /dev/stdin', rules)
|
pfctl('-a sshuttle -f /dev/stdin', rules)
|
||||||
if sys.platform == "darwin":
|
if osdefs.platform == "darwin":
|
||||||
o = pfctl('-E')
|
o = pfctl('-E')
|
||||||
_pf_context['Xtoken'] = \
|
_pf_context['Xtoken'] = \
|
||||||
re.search(r'Token : (.+)', o[1]).group(1)
|
re.search(b'Token : (.+)', o[1]).group(1)
|
||||||
elif 'INFO:\nStatus: Disabled' in pf_status:
|
elif b'INFO:\nStatus: Disabled' in pf_status:
|
||||||
pfctl('-e')
|
pfctl('-e')
|
||||||
_pf_context['started_by_sshuttle'] = True
|
_pf_context['started_by_sshuttle'] = True
|
||||||
else:
|
|
||||||
pfctl('-a sshuttle -F all')
|
def restore_firewall(self, port, family, udp):
|
||||||
if sys.platform == "darwin":
|
if family != socket.AF_INET:
|
||||||
pfctl('-X %s' % _pf_context['Xtoken'])
|
raise Exception(
|
||||||
elif _pf_context['started_by_sshuttle']:
|
'Address family "%s" unsupported by pf method_name'
|
||||||
pfctl('-d')
|
% 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):
|
def firewall_command(self, line):
|
||||||
if line.startswith('QUERY_PF_NAT '):
|
if line.startswith('QUERY_PF_NAT '):
|
||||||
|
@ -59,6 +59,7 @@ if recvmsg == "python":
|
|||||||
ip = socket.inet_ntop(family, cmsg_data[start:start + length])
|
ip = socket.inet_ntop(family, cmsg_data[start:start + length])
|
||||||
dstip = (ip, port)
|
dstip = (ip, port)
|
||||||
break
|
break
|
||||||
|
print("xxxxx", srcip, dstip)
|
||||||
return (srcip, dstip, data)
|
return (srcip, dstip, data)
|
||||||
elif recvmsg == "socket_ext":
|
elif recvmsg == "socket_ext":
|
||||||
def recv_udp(listener, bufsize):
|
def recv_udp(listener, bufsize):
|
||||||
@ -105,7 +106,12 @@ class Method(BaseMethod):
|
|||||||
def get_supported_features(self):
|
def get_supported_features(self):
|
||||||
result = super(Method, self).get_supported_features()
|
result = super(Method, self).get_supported_features()
|
||||||
result.ipv6 = True
|
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
|
return result
|
||||||
|
|
||||||
def get_tcp_dstip(self, sock):
|
def get_tcp_dstip(self, sock):
|
||||||
@ -162,6 +168,91 @@ class Method(BaseMethod):
|
|||||||
tproxy_chain = 'sshuttle-t-%s' % port
|
tproxy_chain = 'sshuttle-t-%s' % port
|
||||||
divert_chain = 'sshuttle-d-%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
|
# basic cleanup/setup of chains
|
||||||
if ipt_chain_exists(family, table, mark_chain):
|
if ipt_chain_exists(family, table, mark_chain):
|
||||||
_ipt('-D', 'OUTPUT', '-j', mark_chain)
|
_ipt('-D', 'OUTPUT', '-j', mark_chain)
|
||||||
@ -176,81 +267,3 @@ class Method(BaseMethod):
|
|||||||
if ipt_chain_exists(family, table, divert_chain):
|
if ipt_chain_exists(family, table, divert_chain):
|
||||||
_ipt('-F', divert_chain)
|
_ipt('-F', divert_chain)
|
||||||
_ipt('-X', 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")
|
|
||||||
|
@ -5,6 +5,7 @@ import traceback
|
|||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
|
|
||||||
import sshuttle.ssnet as ssnet
|
import sshuttle.ssnet as ssnet
|
||||||
import sshuttle.helpers as helpers
|
import sshuttle.helpers as helpers
|
||||||
@ -16,22 +17,23 @@ from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, \
|
|||||||
|
|
||||||
|
|
||||||
def _ipmatch(ipstr):
|
def _ipmatch(ipstr):
|
||||||
if ipstr == 'default':
|
if ipstr == b'default':
|
||||||
ipstr = '0.0.0.0/0'
|
ipstr = b'0.0.0.0/0'
|
||||||
m = re.match(r'^(\d+(\.\d+(\.\d+(\.\d+)?)?)?)(?:/(\d+))?$', ipstr)
|
m = re.match(b'^(\d+(\.\d+(\.\d+(\.\d+)?)?)?)(?:/(\d+))?$', ipstr)
|
||||||
if m:
|
if m:
|
||||||
g = m.groups()
|
g = m.groups()
|
||||||
ips = g[0]
|
ips = g[0]
|
||||||
width = int(g[4] or 32)
|
width = int(g[4] or 32)
|
||||||
if g[1] is None:
|
if g[1] is None:
|
||||||
ips += '.0.0.0'
|
ips += b'.0.0.0'
|
||||||
width = min(width, 8)
|
width = min(width, 8)
|
||||||
elif g[2] is None:
|
elif g[2] is None:
|
||||||
ips += '.0.0'
|
ips += b'.0.0'
|
||||||
width = min(width, 16)
|
width = min(width, 16)
|
||||||
elif g[3] is None:
|
elif g[3] is None:
|
||||||
ips += '.0'
|
ips += b'.0'
|
||||||
width = min(width, 24)
|
width = min(width, 24)
|
||||||
|
ips = ips.decode("ASCII")
|
||||||
return (struct.unpack('!I', socket.inet_aton(ips))[0], width)
|
return (struct.unpack('!I', socket.inet_aton(ips))[0], width)
|
||||||
|
|
||||||
|
|
||||||
@ -56,11 +58,12 @@ def _shl(n, bits):
|
|||||||
|
|
||||||
|
|
||||||
def _list_routes():
|
def _list_routes():
|
||||||
|
# FIXME: IPv4 only
|
||||||
argv = ['netstat', '-rn']
|
argv = ['netstat', '-rn']
|
||||||
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE)
|
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE)
|
||||||
routes = []
|
routes = []
|
||||||
for line in p.stdout:
|
for line in p.stdout:
|
||||||
cols = re.split(r'\s+', line)
|
cols = re.split(b'\s+', line)
|
||||||
ipw = _ipmatch(cols[0])
|
ipw = _ipmatch(cols[0])
|
||||||
if not ipw:
|
if not ipw:
|
||||||
continue # some lines won't be parseable; never mind
|
continue # some lines won't be parseable; never mind
|
||||||
@ -214,6 +217,9 @@ class UdpProxy(Handler):
|
|||||||
|
|
||||||
|
|
||||||
def main(latency_control):
|
def main(latency_control):
|
||||||
|
debug1('Starting server with Python version %s\n'
|
||||||
|
% platform.python_version())
|
||||||
|
|
||||||
if helpers.verbose >= 1:
|
if helpers.verbose >= 1:
|
||||||
helpers.logprefix = ' s: '
|
helpers.logprefix = ' s: '
|
||||||
else:
|
else:
|
||||||
@ -235,26 +241,26 @@ def main(latency_control):
|
|||||||
socket.fromfd(sys.stdout.fileno(),
|
socket.fromfd(sys.stdout.fileno(),
|
||||||
socket.AF_INET, socket.SOCK_STREAM))
|
socket.AF_INET, socket.SOCK_STREAM))
|
||||||
handlers.append(mux)
|
handlers.append(mux)
|
||||||
routepkt = ''
|
routepkt = b''
|
||||||
for r in routes:
|
for r in routes:
|
||||||
routepkt += '%d,%s,%d\n' % r
|
routepkt += b'%d,%s,%d\n' % (r[0], r[1].encode("ASCII"), r[2])
|
||||||
mux.send(0, ssnet.CMD_ROUTES, routepkt)
|
mux.send(0, ssnet.CMD_ROUTES, routepkt)
|
||||||
|
|
||||||
hw = Hostwatch()
|
hw = Hostwatch()
|
||||||
hw.leftover = ''
|
hw.leftover = b''
|
||||||
|
|
||||||
def hostwatch_ready(sock):
|
def hostwatch_ready(sock):
|
||||||
assert(hw.pid)
|
assert(hw.pid)
|
||||||
content = hw.sock.recv(4096)
|
content = hw.sock.recv(4096)
|
||||||
if content:
|
if content:
|
||||||
lines = (hw.leftover + content).split('\n')
|
lines = (hw.leftover + content).split(b'\n')
|
||||||
if lines[-1]:
|
if lines[-1]:
|
||||||
# no terminating newline: entry isn't complete yet!
|
# no terminating newline: entry isn't complete yet!
|
||||||
hw.leftover = lines.pop()
|
hw.leftover = lines.pop()
|
||||||
lines.append('')
|
lines.append('')
|
||||||
else:
|
else:
|
||||||
hw.leftover = ''
|
hw.leftover = b''
|
||||||
mux.send(0, ssnet.CMD_HOST_LIST, '\n'.join(lines))
|
mux.send(0, ssnet.CMD_HOST_LIST, b'\n'.join(lines))
|
||||||
else:
|
else:
|
||||||
raise Fatal('hostwatch process died')
|
raise Fatal('hostwatch process died')
|
||||||
|
|
||||||
@ -266,7 +272,7 @@ def main(latency_control):
|
|||||||
mux.got_host_req = got_host_req
|
mux.got_host_req = got_host_req
|
||||||
|
|
||||||
def new_channel(channel, data):
|
def new_channel(channel, data):
|
||||||
(family, dstip, dstport) = data.split(',', 2)
|
(family, dstip, dstport) = data.split(b',', 2)
|
||||||
family = int(family)
|
family = int(family)
|
||||||
dstport = int(dstport)
|
dstport = int(dstport)
|
||||||
outwrap = ssnet.connect_dst(family, dstip, dstport)
|
outwrap = ssnet.connect_dst(family, dstip, dstport)
|
||||||
@ -324,14 +330,20 @@ def main(latency_control):
|
|||||||
|
|
||||||
if dnshandlers:
|
if dnshandlers:
|
||||||
now = time.time()
|
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:
|
if h.timeout < now or not h.ok:
|
||||||
debug3('expiring dnsreqs channel=%d\n' % channel)
|
debug3('expiring dnsreqs channel=%d\n' % channel)
|
||||||
del dnshandlers[channel]
|
remove.append(channel)
|
||||||
h.ok = False
|
h.ok = False
|
||||||
|
for channel in remove:
|
||||||
|
del dnshandlers[channel]
|
||||||
if udphandlers:
|
if udphandlers:
|
||||||
for channel, h in list(udphandlers.items()):
|
remove = []
|
||||||
|
for channel, h in udphandlers.items():
|
||||||
if not h.ok:
|
if not h.ok:
|
||||||
debug3('expiring UDP channel=%d\n' % channel)
|
debug3('expiring UDP channel=%d\n' % channel)
|
||||||
del udphandlers[channel]
|
remove.append(channel)
|
||||||
h.ok = False
|
h.ok = False
|
||||||
|
for channel in remove:
|
||||||
|
del udphandlers[channel]
|
||||||
|
@ -91,7 +91,8 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
|
|||||||
pyscript = r"""
|
pyscript = r"""
|
||||||
import sys;
|
import sys;
|
||||||
verbosity=%d;
|
verbosity=%d;
|
||||||
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))
|
""" % (helpers.verbose or 0, len(content))
|
||||||
pyscript = re.sub(r'\s+', ' ', pyscript.strip())
|
pyscript = re.sub(r'\s+', ' ', pyscript.strip())
|
||||||
|
|
||||||
@ -107,7 +108,7 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
|
|||||||
if python:
|
if python:
|
||||||
pycmd = "'%s' -c '%s'" % (python, pyscript)
|
pycmd = "'%s' -c '%s'" % (python, pyscript)
|
||||||
else:
|
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
|
"exec \"$P\" -c '%s'") % pyscript
|
||||||
argv = (sshl +
|
argv = (sshl +
|
||||||
portl +
|
portl +
|
||||||
|
@ -227,7 +227,7 @@ conflicts between client and server.
|
|||||||
|
|
||||||
Unlike most VPNs, sshuttle forwards sessions, not packets.
|
Unlike most VPNs, sshuttle forwards sessions, not packets.
|
||||||
That is, it uses kernel transparent proxying (`iptables
|
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
|
capture outgoing TCP sessions, then creates entirely
|
||||||
separate TCP sessions out to the original destination at
|
separate TCP sessions out to the original destination at
|
||||||
the other end of the tunnel.
|
the other end of the tunnel.
|
||||||
@ -256,24 +256,6 @@ between the two separate streams, so a tcp-based tunnel is
|
|||||||
fine.
|
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
|
# SEE ALSO
|
||||||
|
|
||||||
`ssh`(1), `python`(1)
|
`ssh`(1), `python`(1)
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
from mock import Mock, patch, call
|
from mock import Mock, patch, call
|
||||||
import io
|
import io
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
import shutil
|
|
||||||
import filecmp
|
|
||||||
|
|
||||||
import sshuttle.firewall
|
import sshuttle.firewall
|
||||||
|
|
||||||
@ -19,27 +15,27 @@ NSLIST
|
|||||||
10,2404:6800:4004:80c::33
|
10,2404:6800:4004:80c::33
|
||||||
PORTS 1024,1025,1026,1027
|
PORTS 1024,1025,1026,1027
|
||||||
GO 1
|
GO 1
|
||||||
|
HOST 1.2.3.3,existing
|
||||||
""")
|
""")
|
||||||
stdout = Mock()
|
stdout = Mock()
|
||||||
return stdin, stdout
|
return stdin, stdout
|
||||||
|
|
||||||
|
|
||||||
@patch('sshuttle.firewall.HOSTSFILE', new='tmp/hosts')
|
def test_rewrite_etc_hosts(tmpdir):
|
||||||
@patch('sshuttle.firewall.hostmap', new={
|
orig_hosts = tmpdir.join("hosts.orig")
|
||||||
'myhost': '1.2.3.4',
|
orig_hosts.write("1.2.3.3 existing\n")
|
||||||
'myotherhost': '1.2.3.5',
|
|
||||||
})
|
|
||||||
def test_rewrite_etc_hosts():
|
|
||||||
if not os.path.isdir("tmp"):
|
|
||||||
os.mkdir("tmp")
|
|
||||||
|
|
||||||
with open("tmp/hosts.orig", "w") as f:
|
new_hosts = tmpdir.join("hosts")
|
||||||
f.write("1.2.3.3 existing\n")
|
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 new_hosts.open() as f:
|
||||||
with open("tmp/hosts") as f:
|
|
||||||
line = f.readline()
|
line = f.readline()
|
||||||
s = line.split()
|
s = line.split()
|
||||||
assert s == ['1.2.3.3', 'existing']
|
assert s == ['1.2.3.3', 'existing']
|
||||||
@ -57,39 +53,37 @@ def test_rewrite_etc_hosts():
|
|||||||
line = f.readline()
|
line = f.readline()
|
||||||
assert line == ""
|
assert line == ""
|
||||||
|
|
||||||
sshuttle.firewall.restore_etc_hosts(10)
|
with patch('sshuttle.firewall.HOSTSFILE', new=str(new_hosts)):
|
||||||
assert filecmp.cmp("tmp/hosts.orig", "tmp/hosts", shallow=False) is True
|
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.setup_daemon')
|
||||||
@patch('sshuttle.firewall.get_method')
|
@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()
|
stdin, stdout = setup_daemon()
|
||||||
mock_setup_daemon.return_value = stdin, stdout
|
mock_setup_daemon.return_value = stdin, stdout
|
||||||
|
|
||||||
if not os.path.isdir("tmp"):
|
mock_get_method("not_auto").name = "test"
|
||||||
os.mkdir("tmp")
|
mock_get_method.reset_mock()
|
||||||
|
|
||||||
sshuttle.firewall.main("test", False)
|
sshuttle.firewall.main("not_auto", False)
|
||||||
|
|
||||||
with open("tmp/hosts") as f:
|
assert mock_rewrite_etc_hosts.mock_calls == [
|
||||||
line = f.readline()
|
call({'1.2.3.3': 'existing'}, 1024),
|
||||||
s = line.split()
|
call({}, 1024),
|
||||||
assert s == ['1.2.3.3', 'existing']
|
]
|
||||||
|
|
||||||
line = f.readline()
|
assert stdout.mock_calls == [
|
||||||
assert line == ""
|
|
||||||
|
|
||||||
stdout.mock_calls == [
|
|
||||||
call.write('READY test\n'),
|
call.write('READY test\n'),
|
||||||
call.flush(),
|
call.flush(),
|
||||||
call.write('STARTED\n'),
|
call.write('STARTED\n'),
|
||||||
call.flush()
|
call.flush()
|
||||||
]
|
]
|
||||||
mock_setup_daemon.mock_calls == [call()]
|
assert mock_setup_daemon.mock_calls == [call()]
|
||||||
mock_get_method.mock_calls == [
|
assert mock_get_method.mock_calls == [
|
||||||
call('test'),
|
call('not_auto'),
|
||||||
call().setup_firewall(
|
call().setup_firewall(
|
||||||
1024, 1026,
|
1024, 1026,
|
||||||
[(10, u'2404:6800:4004:80c::33')],
|
[(10, u'2404:6800:4004:80c::33')],
|
||||||
@ -103,7 +97,6 @@ def test_main(mock_get_method, mock_setup_daemon):
|
|||||||
2,
|
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'), (2, 32, True, u'1.2.3.66')],
|
||||||
True),
|
True),
|
||||||
call().setup_firewall()(),
|
call().restore_firewall(1024, 10, True),
|
||||||
call().setup_firewall(1024, 0, [], 10, [], True),
|
call().restore_firewall(1025, 2, True),
|
||||||
call().setup_firewall(1025, 0, [], 2, [], True),
|
|
||||||
]
|
]
|
||||||
|
@ -11,12 +11,32 @@ import sshuttle.helpers
|
|||||||
@patch('sshuttle.helpers.sys.stderr')
|
@patch('sshuttle.helpers.sys.stderr')
|
||||||
def test_log(mock_stderr, mock_stdout):
|
def test_log(mock_stderr, mock_stdout):
|
||||||
sshuttle.helpers.log("message")
|
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 == [
|
assert mock_stdout.mock_calls == [
|
||||||
call.flush(),
|
call.flush(),
|
||||||
|
call.flush(),
|
||||||
|
call.flush(),
|
||||||
|
call.flush(),
|
||||||
|
call.flush(),
|
||||||
]
|
]
|
||||||
assert mock_stderr.mock_calls == [
|
assert mock_stderr.mock_calls == [
|
||||||
call.write('prefix: message'),
|
call.write('prefix: message'),
|
||||||
call.flush(),
|
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 socket
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
|
from sshuttle.helpers import Fatal
|
||||||
from sshuttle.methods import get_method
|
from sshuttle.methods import get_method
|
||||||
|
|
||||||
|
|
||||||
@ -11,6 +12,7 @@ def test_get_supported_features():
|
|||||||
features = method.get_supported_features()
|
features = method.get_supported_features()
|
||||||
assert not features.ipv6
|
assert not features.ipv6
|
||||||
assert not features.udp
|
assert not features.udp
|
||||||
|
assert features.dns
|
||||||
|
|
||||||
|
|
||||||
def test_get_tcp_dstip():
|
def test_get_tcp_dstip():
|
||||||
@ -52,10 +54,18 @@ def test_setup_udp_listener():
|
|||||||
assert listener.mock_calls == []
|
assert listener.mock_calls == []
|
||||||
|
|
||||||
|
|
||||||
def test_check_settings():
|
def test_assert_features():
|
||||||
method = get_method('nat')
|
method = get_method('nat')
|
||||||
method.check_settings(True, True)
|
features = method.get_supported_features()
|
||||||
method.check_settings(False, True)
|
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():
|
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_ttl.reset_mock()
|
||||||
mock_ipt.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 == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(2, 'nat', 'sshuttle-1025')
|
call(2, 'nat', 'sshuttle-1025')
|
||||||
]
|
]
|
||||||
|
@ -3,6 +3,8 @@ from mock import Mock, patch, call, ANY
|
|||||||
import socket
|
import socket
|
||||||
|
|
||||||
from sshuttle.methods import get_method
|
from sshuttle.methods import get_method
|
||||||
|
from sshuttle.helpers import Fatal
|
||||||
|
from sshuttle.methods.pf import OsDefs
|
||||||
|
|
||||||
|
|
||||||
def test_get_supported_features():
|
def test_get_supported_features():
|
||||||
@ -10,8 +12,10 @@ def test_get_supported_features():
|
|||||||
features = method.get_supported_features()
|
features = method.get_supported_features()
|
||||||
assert not features.ipv6
|
assert not features.ipv6
|
||||||
assert not features.udp
|
assert not features.udp
|
||||||
|
assert features.dns
|
||||||
|
|
||||||
|
|
||||||
|
@patch('sshuttle.helpers.verbose', new=3)
|
||||||
def test_get_tcp_dstip():
|
def test_get_tcp_dstip():
|
||||||
sock = Mock()
|
sock = Mock()
|
||||||
sock.getpeername.return_value = ("127.0.0.1", 1024)
|
sock.getpeername.return_value = ("127.0.0.1", 1024)
|
||||||
@ -20,7 +24,7 @@ def test_get_tcp_dstip():
|
|||||||
|
|
||||||
firewall = Mock()
|
firewall = Mock()
|
||||||
firewall.pfile.readline.return_value = \
|
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 = get_method('pf')
|
||||||
method.set_firewall(firewall)
|
method.set_firewall(firewall)
|
||||||
@ -31,7 +35,7 @@ def test_get_tcp_dstip():
|
|||||||
call.getsockname(),
|
call.getsockname(),
|
||||||
]
|
]
|
||||||
assert firewall.mock_calls == [
|
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.flush(),
|
||||||
call.pfile.readline()
|
call.pfile.readline()
|
||||||
]
|
]
|
||||||
@ -67,16 +71,25 @@ def test_setup_udp_listener():
|
|||||||
assert listener.mock_calls == []
|
assert listener.mock_calls == []
|
||||||
|
|
||||||
|
|
||||||
def test_check_settings():
|
def test_assert_features():
|
||||||
method = get_method('pf')
|
method = get_method('pf')
|
||||||
method.check_settings(True, True)
|
features = method.get_supported_features()
|
||||||
method.check_settings(False, True)
|
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.sys.stdout')
|
||||||
@patch('sshuttle.methods.pf.ioctl')
|
@patch('sshuttle.methods.pf.ioctl')
|
||||||
@patch('sshuttle.methods.pf.pf_get_dev')
|
@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')
|
method = get_method('pf')
|
||||||
assert not method.firewall_command("somthing")
|
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_pf_get_dev.mock_calls == [call()]
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
call(mock_pf_get_dev(), 3226747927, ANY),
|
call(mock_pf_get_dev(), 0xc0544417, ANY),
|
||||||
]
|
]
|
||||||
assert mock_stdout.mock_calls == [
|
assert mock_stdout.mock_calls == [
|
||||||
call.write('QUERY_PF_NAT_SUCCESS 0.0.0.0,0\n'),
|
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
|
@patch('sshuttle.methods.pf.osdefs', OsDefs('notdarwin'))
|
||||||
# objects.
|
@patch('sshuttle.methods.pf.sys.stdout')
|
||||||
@patch('sshuttle.methods.pf.sys.platform', 'not_darwin')
|
@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.pfctl')
|
||||||
@patch('sshuttle.methods.pf.ioctl')
|
@patch('sshuttle.methods.pf.ioctl')
|
||||||
@patch('sshuttle.methods.pf.pf_get_dev')
|
@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')
|
method = get_method('pf')
|
||||||
assert method.name == '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')],
|
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
||||||
False)
|
False)
|
||||||
assert mock_ioctl.mock_calls == [
|
assert mock_ioctl.mock_calls == [
|
||||||
call(mock_pf_get_dev(), 3295691827, ANY),
|
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||||
call(mock_pf_get_dev(), 3424666650, ANY),
|
call(mock_pf_get_dev(), 0xCC20441A, ANY),
|
||||||
call(mock_pf_get_dev(), 3424666650, ANY),
|
call(mock_pf_get_dev(), 0xCC20441A, ANY),
|
||||||
call(mock_pf_get_dev(), 3295691827, ANY),
|
call(mock_pf_get_dev(), 0xC4704433, ANY),
|
||||||
call(mock_pf_get_dev(), 3424666650, ANY),
|
call(mock_pf_get_dev(), 0xCC20441A, ANY),
|
||||||
call(mock_pf_get_dev(), 3424666650, 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_pf_get_dev.reset_mock()
|
||||||
mock_ioctl.reset_mock()
|
mock_ioctl.reset_mock()
|
||||||
mock_pfctl.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_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_pf_get_dev.reset_mock()
|
||||||
mock_pfctl.reset_mock()
|
mock_pfctl.reset_mock()
|
||||||
mock_ioctl.reset_mock()
|
mock_ioctl.reset_mock()
|
||||||
|
@ -3,11 +3,22 @@ from mock import Mock, patch, call
|
|||||||
from sshuttle.methods import get_method
|
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')
|
method = get_method('tproxy')
|
||||||
features = method.get_supported_features()
|
features = method.get_supported_features()
|
||||||
assert features.ipv6
|
assert features.ipv6
|
||||||
assert features.udp
|
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():
|
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 = get_method('tproxy')
|
||||||
method.check_settings(True, True)
|
features = method.get_supported_features()
|
||||||
method.check_settings(False, True)
|
method.assert_features(features)
|
||||||
|
|
||||||
|
|
||||||
def test_firewall_command():
|
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_ttl.reset_mock()
|
||||||
mock_ipt.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 == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(10, 'mangle', 'sshuttle-m-1025'),
|
call(10, 'mangle', 'sshuttle-m-1025'),
|
||||||
call(10, 'mangle', 'sshuttle-t-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_ttl.reset_mock()
|
||||||
mock_ipt.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 == [
|
assert mock_ipt_chain_exists.mock_calls == [
|
||||||
call(2, 'mangle', 'sshuttle-m-1025'),
|
call(2, 'mangle', 'sshuttle-m-1025'),
|
||||||
call(2, 'mangle', 'sshuttle-t-1025'),
|
call(2, 'mangle', 'sshuttle-t-1025'),
|
||||||
|
Reference in New Issue
Block a user