summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAvery Pennarun <apenwarr@gmail.com>2012-01-08 19:16:05 -0500
committerAvery Pennarun <apenwarr@gmail.com>2012-01-08 19:16:05 -0500
commitd5c3aa61b8b77d3b12647bb15a83ba51a5f4d312 (patch)
treef2be45bae2de7bf76bd25e3120228747372452c9
parent5b57de24041a4c184197fa369859dcaf95c1bdf4 (diff)
MacOS precompiled app package for sshuttle-0.60sshuttle-0.60-macos-bin
-rwxr-xr-xSshuttle VPN.app/Contents/MacOS/Sshuttlebin25248 -> 37568 bytes
-rw-r--r--Sshuttle VPN.app/Contents/Resources/English.lproj/MainMenu.nibbin28750 -> 27144 bytes
-rw-r--r--Sshuttle VPN.app/Contents/Resources/askpass.pycbin1146 -> 0 bytes
-rw-r--r--Sshuttle VPN.app/Contents/Resources/main.py10
-rw-r--r--Sshuttle VPN.app/Contents/Resources/models.py7
-rw-r--r--Sshuttle VPN.app/Contents/Resources/models.pycbin9502 -> 0 bytes
-rw-r--r--Sshuttle VPN.app/Contents/Resources/my.pycbin2809 -> 0 bytes
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/client.py122
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/firewall.py130
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/helpers.py7
-rwxr-xr-x[-rw-r--r--]Sshuttle VPN.app/Contents/Resources/sshuttle/main.py8
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/ssnet.py11
12 files changed, 205 insertions, 90 deletions
diff --git a/Sshuttle VPN.app/Contents/MacOS/Sshuttle b/Sshuttle VPN.app/Contents/MacOS/Sshuttle
index f45f4e2..334693b 100755
--- a/Sshuttle VPN.app/Contents/MacOS/Sshuttle
+++ b/Sshuttle VPN.app/Contents/MacOS/Sshuttle
Binary files differ
diff --git a/Sshuttle VPN.app/Contents/Resources/English.lproj/MainMenu.nib b/Sshuttle VPN.app/Contents/Resources/English.lproj/MainMenu.nib
index af813ea..53b254c 100644
--- a/Sshuttle VPN.app/Contents/Resources/English.lproj/MainMenu.nib
+++ b/Sshuttle VPN.app/Contents/Resources/English.lproj/MainMenu.nib
Binary files differ
diff --git a/Sshuttle VPN.app/Contents/Resources/askpass.pyc b/Sshuttle VPN.app/Contents/Resources/askpass.pyc
deleted file mode 100644
index 3be75fc..0000000
--- a/Sshuttle VPN.app/Contents/Resources/askpass.pyc
+++ /dev/null
Binary files differ
diff --git a/Sshuttle VPN.app/Contents/Resources/main.py b/Sshuttle VPN.app/Contents/Resources/main.py
index 3e6c2a1..fc67a34 100644
--- a/Sshuttle VPN.app/Contents/Resources/main.py
+++ b/Sshuttle VPN.app/Contents/Resources/main.py
@@ -78,6 +78,11 @@ class Runner:
if pid == self.pid:
if os.WIFEXITED(code):
self.rv = os.WEXITSTATUS(code)
+ if self.rv == 111:
+ NSRunAlertPanel('Sshuttle',
+ 'Please restart your computer to finish '
+ 'installing Sshuttle.',
+ 'Restart Later', None, None)
else:
self.rv = -os.WSTOPSIG(code)
self.serverobj.setConnected_(False)
@@ -87,7 +92,10 @@ class Runner:
return self.rv
def wait(self):
- return self._try_wait(0)
+ rv = None
+ while rv is None:
+ self.gotdata(None)
+ rv = self._try_wait(os.WNOHANG)
def poll(self):
return self._try_wait(os.WNOHANG)
diff --git a/Sshuttle VPN.app/Contents/Resources/models.py b/Sshuttle VPN.app/Contents/Resources/models.py
index c4cbe8b..e71fce5 100644
--- a/Sshuttle VPN.app/Contents/Resources/models.py
+++ b/Sshuttle VPN.app/Contents/Resources/models.py
@@ -3,6 +3,7 @@ import my
configchange_callback = setconnect_callback = None
+objc_validator = objc.signature('@@:N^@o^@')
def config_changed():
@@ -39,7 +40,7 @@ class SshuttleNet(NSObject):
def setSubnet_(self, v):
self._k_subnet = v
config_changed()
- @objc.accessor
+ @objc_validator
def validateSubnet_error_(self, value, error):
#print 'validateSubnet!'
return True, _validate_ip(value), error
@@ -49,7 +50,7 @@ class SshuttleNet(NSObject):
def setWidth_(self, v):
self._k_width = v
config_changed()
- @objc.accessor
+ @objc_validator
def validateWidth_error_(self, value, error):
#print 'validateWidth!'
return True, _validate_width(value), error
@@ -118,7 +119,7 @@ class SshuttleServer(NSObject):
self._k_host = v
self.setTitle_(None)
config_changed()
- @objc.accessor
+ @objc_validator
def validateHost_error_(self, value, error):
#print 'validatehost! %r %r %r' % (self, value, error)
while value.startswith('-'):
diff --git a/Sshuttle VPN.app/Contents/Resources/models.pyc b/Sshuttle VPN.app/Contents/Resources/models.pyc
deleted file mode 100644
index 6163bd5..0000000
--- a/Sshuttle VPN.app/Contents/Resources/models.pyc
+++ /dev/null
Binary files differ
diff --git a/Sshuttle VPN.app/Contents/Resources/my.pyc b/Sshuttle VPN.app/Contents/Resources/my.pyc
deleted file mode 100644
index 188ff60..0000000
--- a/Sshuttle VPN.app/Contents/Resources/my.pyc
+++ /dev/null
Binary files differ
diff --git a/Sshuttle VPN.app/Contents/Resources/sshuttle/client.py b/Sshuttle VPN.app/Contents/Resources/sshuttle/client.py
index 449a75a..06fc573 100644
--- a/Sshuttle VPN.app/Contents/Resources/sshuttle/client.py
+++ b/Sshuttle VPN.app/Contents/Resources/sshuttle/client.py
@@ -171,10 +171,71 @@ class FirewallClient:
def done(self):
self.pfile.close()
rv = self.p.wait()
- if rv:
+ if rv == EXITCODE_NEEDS_REBOOT:
+ raise FatalNeedsReboot()
+ elif rv:
raise Fatal('cleanup: %r returned %d' % (self.argv, rv))
+def onaccept(listener, mux, handlers):
+ global _extra_fd
+ try:
+ sock,srcip = listener.accept()
+ except socket.error, e:
+ if e.args[0] in [errno.EMFILE, errno.ENFILE]:
+ debug1('Rejected incoming connection: too many open files!\n')
+ # free up an fd so we can eat the connection
+ os.close(_extra_fd)
+ try:
+ sock,srcip = listener.accept()
+ sock.close()
+ finally:
+ _extra_fd = os.open('/dev/null', os.O_RDONLY)
+ return
+ else:
+ raise
+ dstip = original_dst(sock)
+ debug1('Accept: %s:%r -> %s:%r.\n' % (srcip[0],srcip[1],
+ dstip[0],dstip[1]))
+ if dstip[1] == listener.getsockname()[1] and islocal(dstip[0]):
+ debug1("-- ignored: that's my address!\n")
+ sock.close()
+ return
+ chan = mux.next_channel()
+ if not chan:
+ log('warning: too many open channels. Discarded connection.\n')
+ sock.close()
+ return
+ mux.send(chan, ssnet.CMD_CONNECT, '%s,%s' % dstip)
+ outwrap = MuxWrapper(mux, chan)
+ handlers.append(Proxy(SockWrapper(sock, sock), outwrap))
+
+
+dnsreqs = {}
+def dns_done(chan, data):
+ peer,sock,timeout = dnsreqs.get(chan) or (None,None,None)
+ debug3('dns_done: channel=%r peer=%r\n' % (chan, peer))
+ if peer:
+ del dnsreqs[chan]
+ debug3('doing sendto %r\n' % (peer,))
+ sock.sendto(data, peer)
+
+
+def ondns(listener, mux, handlers):
+ pkt,peer = listener.recvfrom(4096)
+ now = time.time()
+ if pkt:
+ debug1('DNS request from %r: %d bytes\n' % (peer, len(pkt)))
+ chan = mux.next_channel()
+ dnsreqs[chan] = peer,listener,now+30
+ mux.send(chan, ssnet.CMD_DNS_REQ, pkt)
+ mux.channels[chan] = lambda cmd,data: dns_done(chan,data)
+ for chan,(peer,sock,timeout) in dnsreqs.items():
+ if timeout < now:
+ del dnsreqs[chan]
+ debug3('Remaining DNS requests: %d\n' % len(dnsreqs))
+
+
def _main(listener, fw, ssh_cmd, remotename, python, latency_control,
dnslistener, seed_hosts, auto_nets,
syslog, daemon):
@@ -255,63 +316,10 @@ def _main(listener, fw, ssh_cmd, remotename, python, latency_control,
fw.sethostip(name, ip)
mux.got_host_list = onhostlist
- def onaccept():
- global _extra_fd
- try:
- sock,srcip = listener.accept()
- except socket.error, e:
- if e.args[0] in [errno.EMFILE, errno.ENFILE]:
- debug1('Rejected incoming connection: too many open files!\n')
- # free up an fd so we can eat the connection
- os.close(_extra_fd)
- try:
- sock,srcip = listener.accept()
- sock.close()
- finally:
- _extra_fd = os.open('/dev/null', os.O_RDONLY)
- return
- else:
- raise
- dstip = original_dst(sock)
- debug1('Accept: %s:%r -> %s:%r.\n' % (srcip[0],srcip[1],
- dstip[0],dstip[1]))
- if dstip[1] == listener.getsockname()[1] and islocal(dstip[0]):
- debug1("-- ignored: that's my address!\n")
- sock.close()
- return
- chan = mux.next_channel()
- if not chan:
- log('warning: too many open channels. Discarded connection.\n')
- sock.close()
- return
- mux.send(chan, ssnet.CMD_CONNECT, '%s,%s' % dstip)
- outwrap = MuxWrapper(mux, chan)
- handlers.append(Proxy(SockWrapper(sock, sock), outwrap))
- handlers.append(Handler([listener], onaccept))
-
- dnsreqs = {}
- def dns_done(chan, data):
- peer,timeout = dnsreqs.get(chan) or (None,None)
- debug3('dns_done: channel=%r peer=%r\n' % (chan, peer))
- if peer:
- del dnsreqs[chan]
- debug3('doing sendto %r\n' % (peer,))
- dnslistener.sendto(data, peer)
- def ondns():
- pkt,peer = dnslistener.recvfrom(4096)
- now = time.time()
- if pkt:
- debug1('DNS request from %r: %d bytes\n' % (peer, len(pkt)))
- chan = mux.next_channel()
- dnsreqs[chan] = peer,now+30
- mux.send(chan, ssnet.CMD_DNS_REQ, pkt)
- mux.channels[chan] = lambda cmd,data: dns_done(chan,data)
- for chan,(peer,timeout) in dnsreqs.items():
- if timeout < now:
- del dnsreqs[chan]
- debug3('Remaining DNS requests: %d\n' % len(dnsreqs))
+ handlers.append(Handler([listener], lambda: onaccept(listener, mux, handlers)))
+
if dnslistener:
- handlers.append(Handler([dnslistener], ondns))
+ handlers.append(Handler([dnslistener], lambda: ondns(dnslistener, mux, handlers)))
if seed_hosts != None:
debug1('seed_hosts: %r\n' % seed_hosts)
diff --git a/Sshuttle VPN.app/Contents/Resources/sshuttle/firewall.py b/Sshuttle VPN.app/Contents/Resources/sshuttle/firewall.py
index 4fd8c79..452715e 100644
--- a/Sshuttle VPN.app/Contents/Resources/sshuttle/firewall.py
+++ b/Sshuttle VPN.app/Contents/Resources/sshuttle/firewall.py
@@ -1,4 +1,4 @@
-import re, errno, socket, select, struct
+import re, errno, socket, select, signal, struct
import compat.ssubprocess as ssubprocess
import helpers, ssyslog
from helpers import *
@@ -6,6 +6,12 @@ from helpers import *
# python doesn't have a definition for this
IPPROTO_DIVERT = 254
+# return values from sysctl_set
+SUCCESS = 0
+SAME = 1
+FAILED = -1
+NONEXIST = -2
+
def nonfatal(func, *args):
try:
@@ -14,6 +20,14 @@ def nonfatal(func, *args):
log('error: %s\n' % e)
+def _call(argv):
+ debug1('>> %s\n' % ' '.join(argv))
+ rv = ssubprocess.call(argv)
+ if rv:
+ raise Fatal('%r returned %d' % (argv, rv))
+ return rv
+
+
def ipt_chain_exists(name):
argv = ['iptables', '-t', 'nat', '-nL']
p = ssubprocess.Popen(argv, stdout = ssubprocess.PIPE)
@@ -27,10 +41,7 @@ def ipt_chain_exists(name):
def ipt(*args):
argv = ['iptables', '-t', 'nat'] + list(args)
- debug1('>> %s\n' % ' '.join(argv))
- rv = ssubprocess.call(argv)
- if rv:
- raise Fatal('%r returned %d' % (argv, rv))
+ _call(argv)
_no_ttl_module = False
@@ -135,6 +146,42 @@ def _fill_oldctls(prefix):
raise Fatal('%r returned no data' % (argv,))
+KERNEL_FLAGS_PATH = '/Library/Preferences/SystemConfiguration/com.apple.Boot'
+KERNEL_FLAGS_NAME = 'Kernel Flags'
+def _defaults_read_kernel_flags():
+ argv = ['defaults', 'read', KERNEL_FLAGS_PATH, KERNEL_FLAGS_NAME]
+ debug1('>> %s\n' % ' '.join(argv))
+ p = ssubprocess.Popen(argv, stdout = ssubprocess.PIPE)
+ flagstr = p.stdout.read().strip()
+ rv = p.wait()
+ if rv:
+ raise Fatal('%r returned %d' % (argv, rv))
+ flags = flagstr and flagstr.split(' ') or []
+ return flags
+
+
+def _defaults_write_kernel_flags(flags):
+ flagstr = ' '.join(flags)
+ argv = ['defaults', 'write', KERNEL_FLAGS_PATH, KERNEL_FLAGS_NAME,
+ flagstr]
+ _call(argv)
+ argv = ['plutil', '-convert', 'xml1', KERNEL_FLAGS_PATH + '.plist']
+ _call(argv)
+
+
+
+def defaults_write_kernel_flag(name, val):
+ flags = _defaults_read_kernel_flags()
+ found = 0
+ for i in range(len(flags)):
+ if flags[i].startswith('%s=' % name):
+ found += 1
+ flags[i] = '%s=%s' % (name, val)
+ if not found:
+ flags.insert(0, '%s=%s' % (name, val))
+ _defaults_write_kernel_flags(flags)
+
+
def _sysctl_set(name, val):
argv = ['sysctl', '-w', '%s=%s' % (name, val)]
debug1('>> %s\n' % ' '.join(argv))
@@ -150,20 +197,24 @@ def sysctl_set(name, val, permanent=False):
_fill_oldctls(PREFIX)
if not (name in _oldctls):
debug1('>> No such sysctl: %r\n' % name)
- return False
+ return NONEXIST
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
+ if val == oldval:
+ return SAME
+
+ rv = _sysctl_set(name, val)
+ if rv != 0:
+ return FAILED
+ if 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 SUCCESS
def _udp_unpack(p):
@@ -201,10 +252,7 @@ def _handle_diversion(divertsock, dnsport):
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))
+ _call(argv)
def do_ipfw(port, dnsport, subnets):
@@ -222,8 +270,14 @@ def do_ipfw(port, dnsport, subnets):
if subnets or dnsport:
sysctl_set('net.inet.ip.fw.enable', 1)
- changed = sysctl_set('net.inet.ip.scopedroute', 0, permanent=True)
- if changed:
+
+ # This seems to be needed on MacOS 10.6 and 10.7. For more
+ # information, see:
+ # http://groups.google.com/group/sshuttle/browse_thread/thread/bc32562e17987b25/6d3aa2bb30a1edab
+ # and
+ # http://serverfault.com/questions/138622/transparent-proxying-leaves-sockets-with-syn-rcvd-in-macos-x-10-6-snow-leopard
+ changeflag = sysctl_set('net.inet.ip.scopedroute', 0, permanent=True)
+ if changeflag == SUCCESS:
log("\n"
" WARNING: ONE-TIME NETWORK DISRUPTION:\n"
" =====================================\n"
@@ -234,6 +288,21 @@ def do_ipfw(port, dnsport, subnets):
"ethernet port) NOW, then restart sshuttle. The fix is\n"
"permanent; you only have to do this once.\n\n")
sys.exit(1)
+ elif changeflag == FAILED:
+ # On MacOS 10.7, the scopedroute sysctl became read-only, so
+ # we have to fix it using a kernel boot parameter instead,
+ # which requires rebooting. For more, see:
+ # http://groups.google.com/group/sshuttle/browse_thread/thread/a42505ca33e1de80/e5e8f3e5a92d25f7
+ log('Updating kernel boot flags.\n')
+ defaults_write_kernel_flag('net.inet.ip.scopedroute', 0)
+ log("\n"
+ " YOU MUST REBOOT TO USE SSHUTTLE\n"
+ " ===============================\n"
+ "sshuttle has changed a MacOS kernel boot-time setting\n"
+ "to work around a bug in MacOS 10.7 Lion. You will need\n"
+ "to reboot before it takes effect. You only have to\n"
+ "do this once.\n\n")
+ sys.exit(EXITCODE_NEEDS_REBOOT)
ipfw('add', sport, 'check-state', 'ip',
'from', 'any', 'to', 'any')
@@ -243,11 +312,11 @@ def do_ipfw(port, dnsport, subnets):
for swidth,sexclude,snet in sorted(subnets, reverse=True):
if sexclude:
ipfw('add', sport, 'skipto', xsport,
- 'log', 'tcp',
+ 'tcp',
'from', 'any', 'to', '%s/%s' % (snet,swidth))
else:
ipfw('add', sport, 'fwd', '127.0.0.1,%d' % port,
- 'log', 'tcp',
+ 'tcp',
'from', 'any', 'to', '%s/%s' % (snet,swidth),
'not', 'ipttl', '42', 'keep-state', 'setup')
@@ -289,12 +358,12 @@ def do_ipfw(port, dnsport, subnets):
for ip in nslist:
# relabel and then catch outgoing DNS requests
ipfw('add', sport, 'divert', sport,
- 'log', 'udp',
+ 'udp',
'from', 'any', 'to', '%s/32' % ip, '53',
'not', 'ipttl', '42')
# relabel DNS responses
ipfw('add', sport, 'divert', sport,
- 'log', 'udp',
+ 'udp',
'from', 'any', str(dnsport), 'to', 'any',
'not', 'ipttl', '42')
@@ -398,6 +467,11 @@ def main(port, dnsport, syslog):
sys.stdout.write('READY\n')
sys.stdout.flush()
+ # don't disappear if our controlling terminal or stdout/stderr
+ # disappears; we still have to clean up.
+ signal.signal(signal.SIGHUP, signal.SIG_IGN)
+ signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+
# ctrl-c shouldn't be passed along to me. When the main sshuttle dies,
# I'll die automatically.
os.setsid()
diff --git a/Sshuttle VPN.app/Contents/Resources/sshuttle/helpers.py b/Sshuttle VPN.app/Contents/Resources/sshuttle/helpers.py
index c169d0c..45a028b 100644
--- a/Sshuttle VPN.app/Contents/Resources/sshuttle/helpers.py
+++ b/Sshuttle VPN.app/Contents/Resources/sshuttle/helpers.py
@@ -1,4 +1,4 @@
-import sys, os, socket
+import sys, os, socket, errno
logprefix = ''
verbose = 0
@@ -30,6 +30,11 @@ class Fatal(Exception):
pass
+EXITCODE_NEEDS_REBOOT = 111
+class FatalNeedsReboot(Fatal):
+ pass
+
+
def list_contains_any(l, sub):
for i in sub:
if i in l:
diff --git a/Sshuttle VPN.app/Contents/Resources/sshuttle/main.py b/Sshuttle VPN.app/Contents/Resources/sshuttle/main.py
index 1cf00af..daf3b87 100644..100755
--- a/Sshuttle VPN.app/Contents/Resources/sshuttle/main.py
+++ b/Sshuttle VPN.app/Contents/Resources/sshuttle/main.py
@@ -63,6 +63,7 @@ seed-hosts= with -H, use these hostnames for initial scan (comma-separated)
no-latency-control sacrifice latency to improve bandwidth benchmarks
wrap= restart counting channel numbers after this number (for testing)
D,daemon run in the background as a daemon
+V,version print sshuttle's version number
syslog send log messages to syslog (default if you use --daemon)
pidfile= pidfile name (only if using --daemon) [./sshuttle.pid]
server (internal use only)
@@ -72,6 +73,10 @@ hostwatch (internal use only)
o = options.Options(optspec)
(opt, flags, extra) = o.parse(sys.argv[2:])
+if opt.version:
+ import version
+ print version.TAG
+ sys.exit(0)
if opt.daemon:
opt.syslog = 1
if opt.wrap:
@@ -121,6 +126,9 @@ try:
parse_subnets(includes),
parse_subnets(excludes),
opt.syslog, opt.daemon, opt.pidfile))
+except FatalNeedsReboot, e:
+ log('You must reboot before using sshuttle.\n')
+ sys.exit(EXITCODE_NEEDS_REBOOT)
except Fatal, e:
log('fatal: %s\n' % e)
sys.exit(99)
diff --git a/Sshuttle VPN.app/Contents/Resources/sshuttle/ssnet.py b/Sshuttle VPN.app/Contents/Resources/sshuttle/ssnet.py
index b6d73c2..a636ce1 100644
--- a/Sshuttle VPN.app/Contents/Resources/sshuttle/ssnet.py
+++ b/Sshuttle VPN.app/Contents/Resources/sshuttle/ssnet.py
@@ -152,6 +152,17 @@ class SockWrapper:
debug3('%r: fixed connect result: %s\n' % (self, e))
if e.args[0] in [errno.EINPROGRESS, errno.EALREADY]:
pass # not connected yet
+ elif e.args[0] == 0:
+ # connected successfully (weird Linux bug?)
+ # Sometimes Linux seems to return EINVAL when it isn't
+ # invalid. This *may* be caused by a race condition
+ # between connect() and getsockopt(SO_ERROR) (ie. it
+ # finishes connecting in between the two, so there is no
+ # longer an error). However, I'm not sure of that.
+ #
+ # I did get at least one report that the problem went away
+ # when we added this, however.
+ self.connect_to = None
elif e.args[0] == errno.EISCONN:
# connected successfully (BSD)
self.connect_to = None