Compare commits

..

47 Commits

Author SHA1 Message Date
e737f4b944 firewall.py: add comments about sysctl problems. 2012-01-08 19:13:39 -05:00
d9f761a8a3 ui-macos: tell the user that we need to reboot on MacOS Lion. 2012-01-08 19:01:18 -05:00
bd20841782 firewall.py: clean up repeated calls to ssubprocess.call().
And make sshuttle exit with a well-defined exit code (111) if it needs to
reboot.
2012-01-08 19:01:18 -05:00
4c1a505e37 firewall.py: workaround MacOS 10.7 Lion bug.
On top of the bug that already existed in 10.6, Lion also makes the sysctl
needed to fix the problem into a read-only variable, so we have to actually
change it at kernel boot time and force people to reboot.  Nice job, Apple.
2012-01-08 19:01:18 -05:00
41d1f73dc2 Add a --version (-V) option.
Now that we imported the feature from redo, might as well use it.
2012-01-06 13:45:43 -05:00
cbc32ff8d8 Import the non-pandoc manpage generator from redo.
This makes it easier (possible?) to generate sshuttle.8 from sshuttle.md on
MacOS.  We also import the git-enhanced version numbering magic so the
generated manpage can have a real version number.
2012-01-06 13:35:12 -05:00
6698992f4f Use the new arguments from redo v0.10.
(apenwarr: also updates to the matching, latest minimal/do)
2012-01-06 13:24:56 -05:00
e2c682084c firewall: catch SIGHUP and SIGPIPE.
Not sure if this will fix anything, but it might stop the problem reported
on some MacOS versions where the firewall doesn't get cleaned up correctly.
2012-01-06 13:14:55 -05:00
89e914e9d1 ui-macos/main.py: fix wait() to avoid deadlock.
If the subprocess was trying to write to its stdout/stderr, its process
would never actually finish because it was blocked waiting for us to read
it, but we were blocked on waitpid().  Instead, use waitpid(WNOHANG) and
continually read from the subprocess (which should be a blocking operation)
until it exits.
2012-01-02 18:46:30 -05:00
2268e76771 ipfw: don't use 'log' parameter.
I guess we were causing the kernel to syslog on every single packet on
MacOS.  Oops.
2012-01-02 18:19:19 -05:00
a8b71f6387 Move nested functions to top level. 2011-05-31 00:42:48 -04:00
4bfcd7091d Send DNS request back on same sock we received it on. 2011-05-31 00:39:17 -04:00
bd489b3319 Pass socket through to handlers. Required for IPv6 support. 2011-05-31 00:39:17 -04:00
8ab5ef283d ssnet.py: deal with a possible connect/getsockopt(SO_ERROR) race.
Seems to affect Linux servers.  Ed Maste says the patch fixes it for him.
2011-05-29 22:42:16 -04:00
e67208a294 helpers.py: errno is used by this module, but not imported. 2011-05-15 17:35:53 -04:00
7859be13c2 ui-macos/bits/runpython.do: skip ppc64 architecture.
I don't have a Mac that can build it.  Hopefully ppc will run fine on ppc64.
2011-05-07 23:19:52 -04:00
f313d50690 ui-macos/bits/runpython.do: report which platforms we're compiling for.
Just as a quick reminder, in case you're building a fat binary and you don't
have all the architectures actually installed.
2011-05-07 23:16:42 -04:00
15e26d2e0e README.md: fix little bug
The ssh hostname should immediately follow the -r parameter.
2011-05-07 23:16:42 -04:00
e2ec475de3 ui-macos/models.py: fix a compatibility problem on MacOS for PPC.
@objc.accessor isn't the right thing to use for a Core Data Validation
function.  Yowee, PyObjc sure is non-obvious.
2011-05-07 23:16:42 -04:00
57e744dadd ./do: use the latest minimal/do from the redo project. 2011-05-03 14:19:45 -07:00
c13be0b817 ui-macos/bits/runpython.do: auto-determine arches to build for.
Some people don't have all of them installed, so auto-detect them by
looking at the available arches in /usr/libexec.
2011-05-03 14:18:37 -07:00
da2c6273f6 Add some friendly info to the README 2011-05-03 14:03:19 -07:00
7712c60c36 Insert two binary NUL bytes (\0) before SSHUTTLE0001 sync string.
...and search for those null bytes before looking for the sync string.

This helps when people have misconfigured .bashrc to print messages even in
non-interactive mode.  (On my Debian Lenny system, .bashrc doesn't seem to
run when you do 'ssh localhost ls', but on MacOS servers, it does.  Hmm...)
2011-05-03 13:59:25 -07:00
65b0390fe9 ssh.py: use 'exec python -c' instead of just 'python -c'.
This gets rid of an extra intermediate sh process on the server that we were
keeping for no good reason, since it would exit as soon as python exited
anyway.
2011-05-03 13:51:09 -07:00
c5834a9773 Handle EHOSTDOWN, ENETDOWN.
Someone on the mailing list reported getting these.

Also centralize the list of these errors, so we don't have different parts
of the code supporting a different subset of them.  Now just use
ssnet.NET_ERRS.
2011-05-03 13:32:25 -07:00
e2474543fc runpython.do: also compile for ppc architecture. 2011-04-24 22:51:27 -04:00
8636378870 Dereference symlink for sshuttle launch script
(Modified slightly by apenwarr)
2011-04-24 22:42:50 -04:00
f5eed4c809 Don't try to connect to remote IPs that start with zero.
For some reason, on Linux servers this returns EINVAL.  I don't like just
treating EINVAL as non-fatal in general, so let's catch this specific case
and ignore it.

Reported by Reza Mohammadi on the mailing list.  Interestingly, it's kind of
hard to trigger this crash since the client would have to request the
connection, and that connection shouldn't exist because the original client
program would have already gotten EINVAL.  But my MacOS machine can generate
such a connection, so a MacOS->Linux sshuttle could trigger this.
2011-04-24 22:15:20 -04:00
783d33cada DNS: auto-retry if we get an error on send/recv to DNS server.
A few people have reported that they have one or more invalid DNS servers in
/etc/resolv.conf, which they don't notice because the normal resolver
library just skips the broken ones.  sshuttle would abort because it got an
unexpected socket error, which isn't so good.
2011-04-06 12:30:12 -04:00
94241b938b On FreeBSD, avoid a crash caused by buggy socket.connect() in python pre-2.5.
Bug reported by Ed Maste.  The fix in later versions of python is documented
here:
http://mail.python.org/pipermail/python-bugs-list/2006-August/034667.html

We're basically just doing the same thing when we see EINVAL.  Note that
this doesn't happen on Linux because connect() is more forgiving.
2011-03-21 03:15:11 -07:00
9031de1527 repr(socket.error) is useless in some versions of python.
So let's use %s instead of %r to print it, so that log messages can be more
useful.  This only affects one message at debug3 for now, so it's not too
exciting.
2011-03-21 03:15:11 -07:00
cfb2592346 server.py: handle (throw away) ECONNREFUSED from the DNS server.
This might happen occasionally on a flakey network.  Reported by Ed Maste.
2011-03-19 22:48:15 -07:00
2e8381ecda hostwatch.py: avoid using /dev/null on the server.
According to at least one report, there are some slightly insane servers out
there that have /dev/null set to non-user-writable.  This is totally broken,
but we want sshuttle to work with as many servers as possible, so let's fake
it up a bit instead.

We don't try to avoid /dev/null on the client; sshuttle needs root access
anyway, and if you're root, you can just fix your stupid /dev/null
permissions.
2011-03-14 18:57:06 -07:00
7d35690e41 ui-macos/clean: fix a GNUism in usage of the 'find' command. 2011-02-28 02:43:00 -08:00
141d9760b9 all.do: add some hints about how to run sshuttle.
This is mostly so that people know how to find the MacOS GUI app, which was
previously rather non-obvious.
2011-02-26 18:16:44 -08:00
0658c85ffe Replace make-based build with redo-based build.
Including a copy of minimal/do as 'do' in the top directory.  To build, just
run './do' or 'make'.

This also builds the ui-macos directory automatically if you're on MacOS.
2011-02-26 18:16:44 -08:00
90a55a33a2 firewall.py: make it super clear when we apply the MacOS fix.
Print a message to stderr, then abort.  But only the first time.
2011-02-26 17:45:27 -08:00
c3399595d2 README/sshuttle.1: add a note about the MacOS kernel bug.
And its side effects.

Reported by David Held / Antonio d'Souza.
2011-02-26 17:23:11 -08:00
6ef9ae1796 firewall.py: iptables: failure to delete a rule isn't always fatal.
If the previous run of sshuttle didn't manage to clean up after itself, it
might have left the sshuttle-12300 chain intact, but the OUTPUT chain might
not refer to it anymore.  That would cause the *next* run of sshuttle to
barf when trying to delete the OUTPUT entry, and then never get to the part
where it just tries to delete the old chain so it can continue.

Now only the last delete command (the one that actually deletes the chain)
is fatal if it fails; the others just print a scary message, but that should
only happen once in your life if you're unlucky.
2011-02-21 03:04:00 -08:00
1ca8aa5b89 server: workaround for idiotic ArchLinux renaming of python to python2.
First try running under python2, then python if that doesn't exist.
2011-02-07 17:18:30 -08:00
a62975e0ce client: workaround for idiotic ArchLinux renaming of python to python2.
First try running under python2, then python if that doesn't exist.
2011-02-07 00:18:58 -08:00
4fde980f46 firewall.py: MacOS: permanently set the net.inet.ip.scopedroute sysctl.
If this sysctl isn't set to 0 at the time your network interface is brought
up, and we later change it, then the MacOS (10.6.6 at least) ARP table gets
totally confused and networking stops working about 15 minutes later, until
you down and re-up the interface.  The symptom is that pings outside your
LAN would give results like this:

    ping: sendto: no route to host

and "arp -a -n" would show *two* entries for your default gateway instead of
just one.

sshuttle was helpfully putting the sysctl back the way it was when it shuts
down, so you would fix your network by downing the interface, so sshuttle
would abort and change the sysctl back, then you would re-up the interface,
then restart sshuttle, and sshuttle would change the sysctl back and restart
the cycle: it would break again a few minutes later.

That's annoying, and it gives sshuttle a bad reputation for being the thing
that breaks your network.  I can't find a *really* good workaround for the
bug, so barring that, let's just permanently set the sysctl to 0 and not
change it back on exit.  That should just leave your computer back how it
worked in MacOS 10.5, as far as I know, which seems harmless.  At least I've
been running my Mac that way for a few days and I haven't seen any
weirdness.

Now, doing *that* would still mean that the first sshuttle session after a
reboot would still break the network, since sysctl changes are lost on
reboot.  Thus, let's be extra hardcore and write it to /etc/sysctl.conf so
that it goes the way we want it after a reboot.  Thus, sshuttle should break
your network at most once.  Which still sucks, but hopefully nobody will
notice.
2011-02-04 21:55:40 -08:00
621997b279 ui-macos: move the noLatencyControl setting to a per-connection setting.
I think some connections you'll want to optimize for latency, and others for
bandwidth.  Probably.

Also, use a dropdown box instead of a checkbox; that way we can make it more
clear what each of the settings means.

While we're here, adjust all the anchor settings for the different display
items so that resizing the dialog box works sensibly.
2011-02-04 21:40:44 -08:00
ca7d38dc1a stresstest.py: a program to create lots and lots of TCP connections.
This version is a bit limited: it always only connects back to itself, which
is always on 127.0.0.1.  It also doesn't really find any problems, other
than odd behaviour when Linux runs out of available port numbers after a
while.
2011-02-04 21:37:22 -08:00
a81972b2b5 Add --wrap option to force channel number wrapping at a lower number.
This makes it easier to actually test what happens when channel numbers wrap
around.  The good news: it works.

However, I did find a bug where sshuttle would die if we completely ran out
of available channel numbers because so many of them were open.  This would
never realistically happen at the default of 65535 channels (we'd run out of
file descriptors first), but it's still a bug, so let's handle it by just
dropping the connection when it happens.
2011-02-02 02:32:46 -08:00
a238f7636c ui-macos: include routing type in each connection title.
This makes it extra clear when a connection is for "all routes" vs. custom
vs. auto.
2011-02-01 03:55:19 -08:00
62e1ac4b46 ui-macos: add checkboxes for --no-latency-control and --dns options. 2011-02-01 03:55:19 -08:00
45 changed files with 1503 additions and 246 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
*.pyc *.pyc
*~ *~
*.8 *.8
/.do_built
/.do_built.dir
/.redo

3
Documentation/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.8
/md-to-man
/*.md.tmp

5
Documentation/all.do Normal file
View File

@ -0,0 +1,5 @@
/bin/ls *.md |
sed 's/\.md/.8/' |
xargs redo-ifchange
redo-always

1
Documentation/clean.do Normal file
View File

@ -0,0 +1 @@
rm -f *~ .*~ *.8 t/*.8 md-to-man *.tmp t/*.tmp

View File

@ -0,0 +1,2 @@
redo-ifchange md-to-man $2.md.tmp
. ./md-to-man $1 $2 $3

View File

@ -0,0 +1,3 @@
redo-ifchange ../version/vars $2.md
. ../version/vars
sed -e "s/%VERSION%/$TAG/" -e "s/%DATE%/$DATE/" $2.md

View File

@ -0,0 +1,8 @@
redo-ifchange md2man.py
if ./md2man.py </dev/null >/dev/null; then
echo './md2man.py $2.md.tmp'
else
echo "Warning: md2man.py missing modules; can't generate manpages." >&2
echo "Warning: try this: sudo easy_install markdown BeautifulSoup" >&2
echo 'echo Skipping: $2.1 >&2'
fi

278
Documentation/md2man.py Executable file
View File

@ -0,0 +1,278 @@
#!/usr/bin/env python
import sys, os, markdown, re
from BeautifulSoup import BeautifulSoup
def _split_lines(s):
return re.findall(r'([^\n]*\n?)', s)
class Writer:
def __init__(self):
self.started = False
self.indent = 0
self.last_wrote = '\n'
def _write(self, s):
if s:
self.last_wrote = s
sys.stdout.write(s)
def writeln(self, s):
if s:
self.linebreak()
self._write('%s\n' % s)
def write(self, s):
if s:
self.para()
for line in _split_lines(s):
if line.startswith('.'):
self._write('\\&' + line)
else:
self._write(line)
def linebreak(self):
if not self.last_wrote.endswith('\n'):
self._write('\n')
def para(self, bullet=None):
if not self.started:
if not bullet:
bullet = ' '
if not self.indent:
self.writeln(_macro('.PP'))
else:
assert(self.indent >= 2)
prefix = ' '*(self.indent-2) + bullet + ' '
self.writeln('.IP "%s" %d' % (prefix, self.indent))
self.started = True
def end_para(self):
self.linebreak()
self.started = False
def start_bullet(self):
self.indent += 3
self.para(bullet='\\[bu]')
def end_bullet(self):
self.indent -= 3
self.end_para()
w = Writer()
def _macro(name, *args):
if not name.startswith('.'):
raise ValueError('macro names must start with "."')
fixargs = []
for i in args:
i = str(i)
i = i.replace('\\', '')
i = i.replace('"', "'")
if (' ' in i) or not i:
i = '"%s"' % i
fixargs.append(i)
return ' '.join([name] + list(fixargs))
def macro(name, *args):
w.writeln(_macro(name, *args))
def _force_string(owner, tag):
if tag.string:
return tag.string
else:
out = ''
for i in tag:
if not (i.string or i.name in ['a', 'br']):
raise ValueError('"%s" tags must contain only strings: '
'got %r: %r' % (owner.name, tag.name, tag))
out += _force_string(owner, i)
return out
def _clean(s):
s = s.replace('\\', '\\\\')
return s
def _bitlist(tag):
if getattr(tag, 'contents', None) == None:
for i in _split_lines(str(tag)):
yield None,_clean(i)
else:
for e in tag:
name = getattr(e, 'name', None)
if name in ['a', 'br']:
name = None # just treat as simple text
s = _force_string(tag, e)
if name:
yield name,_clean(s)
else:
for i in _split_lines(s):
yield None,_clean(i)
def _bitlist_simple(tag):
for typ,text in _bitlist(tag):
if typ and not typ in ['em', 'strong', 'code']:
raise ValueError('unexpected tag %r inside %r' % (typ, tag.name))
yield text
def _text(bitlist):
out = ''
for typ,text in bitlist:
if not typ:
out += text
elif typ == 'em':
out += '\\fI%s\\fR' % text
elif typ in ['strong', 'code']:
out += '\\fB%s\\fR' % text
else:
raise ValueError('unexpected tag %r inside %r' % (typ, tag.name))
out = out.strip()
out = re.sub(re.compile(r'^\s+', re.M), '', out)
return out
def text(tag):
w.write(_text(_bitlist(tag)))
# This is needed because .BI (and .BR, .RB, etc) are weird little state
# machines that alternate between two fonts. So if someone says something
# like foo<b>chicken</b><b>wicken</b>dicken we have to convert that to
# .BI foo chickenwicken dicken
def _boldline(l):
out = ['']
last_bold = False
for typ,text in l:
nonzero = not not typ
if nonzero != last_bold:
last_bold = not last_bold
out.append('')
out[-1] += re.sub(r'\s+', ' ', text)
macro('.BI', *out)
def do_definition(tag):
w.end_para()
macro('.TP')
w.started = True
split = 0
pre = []
post = []
for typ,text in _bitlist(tag):
if split:
post.append((typ,text))
elif text.lstrip().startswith(': '):
split = 1
post.append((typ,text.lstrip()[2:].lstrip()))
else:
pre.append((typ,text))
_boldline(pre)
w.write(_text(post))
def do_list(tag):
for i in tag:
name = getattr(i, 'name', '').lower()
if not name and not str(i).strip():
pass
elif name != 'li':
raise ValueError('only <li> is allowed inside <ul>: got %r' % i)
else:
w.start_bullet()
for xi in i:
do(xi)
w.end_para()
w.end_bullet()
def do(tag):
name = getattr(tag, 'name', '').lower()
if not name:
text(tag)
elif name == 'h1':
macro('.SH', _force_string(tag, tag).upper())
w.started = True
elif name == 'h2':
macro('.SS', _force_string(tag, tag))
w.started = True
elif name.startswith('h') and len(name)==2:
raise ValueError('%r invalid - man page headers must be h1 or h2'
% name)
elif name == 'pre':
t = _force_string(tag.code, tag.code)
if t.strip():
macro('.RS', '+4n')
macro('.nf')
w.write(_clean(t).rstrip())
macro('.fi')
macro('.RE')
w.end_para()
elif name == 'p' or name == 'br':
g = re.match(re.compile(r'([^\n]*)\n +: +(.*)', re.S), str(tag))
if g:
# it's a definition list (which some versions of python-markdown
# don't support, including the one in Debian-lenny, so we can't
# enable that markdown extension). Fake it up.
do_definition(tag)
else:
text(tag)
w.end_para()
elif name == 'ul':
do_list(tag)
else:
raise ValueError('non-man-compatible html tag %r' % name)
PROD='Untitled'
VENDOR='Vendor Name'
SECTION='9'
GROUPNAME='User Commands'
DATE=''
AUTHOR=''
lines = []
if len(sys.argv) > 1:
for n in sys.argv[1:]:
lines += open(n).read().decode('utf8').split('\n')
else:
lines += sys.stdin.read().decode('utf8').split('\n')
# parse pandoc-style document headers (not part of markdown)
g = re.match(r'^%\s+(.*?)\((.*?)\)\s+(.*)$', lines[0])
if g:
PROD = g.group(1)
SECTION = g.group(2)
VENDOR = g.group(3)
lines.pop(0)
g = re.match(r'^%\s+(.*?)$', lines[0])
if g:
AUTHOR = g.group(1)
lines.pop(0)
g = re.match(r'^%\s+(.*?)$', lines[0])
if g:
DATE = g.group(1)
lines.pop(0)
g = re.match(r'^%\s+(.*?)$', lines[0])
if g:
GROUPNAME = g.group(1)
lines.pop(0)
inp = '\n'.join(lines)
if AUTHOR:
inp += ('\n# AUTHOR\n\n%s\n' % AUTHOR).replace('<', '\\<')
html = markdown.markdown(inp)
soup = BeautifulSoup(html, convertEntities=BeautifulSoup.HTML_ENTITIES)
macro('.TH', PROD.upper(), SECTION, DATE, VENDOR, GROUPNAME)
macro('.ad', 'l') # left justified
macro('.nh') # disable hyphenation
for e in soup:
do(e)

View File

@ -1,6 +1,6 @@
% sshuttle(8) Sshuttle 0.46 % sshuttle(8) Sshuttle %VERSION%
% Avery Pennarun <apenwarr@gmail.com> % Avery Pennarun <apenwarr@gmail.com>
% 2011-01-25 % %DATE%
# NAME # NAME
@ -253,6 +253,17 @@ 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.
# SEE ALSO # SEE ALSO
`ssh`(1), `python`(1) `ssh`(1), `python`(1)

View File

@ -1,19 +1,10 @@
PANDOC:=$(shell \ all:
if pandoc </dev/null 2>/dev/null; then \
echo pandoc; \
else \
echo "Warning: pandoc not installed; can't generate manpages." >&2; \
echo '@echo Skipping: pandoc'; \
fi)
default: all Makefile:
@
all: sshuttle.8 %: FORCE
+./do $@
sshuttle.8: sshuttle.md .PHONY: FORCE
%.8: %.md
$(PANDOC) -s -r markdown -w man -o $@ $<
clean:
rm -f *~ */*~ .*~ */.*~ *.8 *.tmp */*.tmp *.pyc */*.pyc

View File

@ -1,3 +1,14 @@
WARNING:
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.
sshuttle: where transparent proxy meets VPN meets ssh sshuttle: where transparent proxy meets VPN meets ssh
===================================================== =====================================================
@ -52,7 +63,19 @@ This is how you use it:
on your client machine. You'll need root or sudo on your client machine. You'll need root or sudo
access, and python needs to be installed. access, and python needs to be installed.
- <tt>./sshuttle -r username@sshserver 0.0.0.0/0 -vv</tt> - The most basic use of sshuttle looks like:
<tt>./sshuttle -r username@sshserver 0.0.0.0/0 -vv</tt>
- There is a shortcut for 0.0.0.0/0 for those that value
their wrists
<tt>./sshuttle -r username@sshserver 0/0 -vv</tt>
- If you would also like your DNS queries to be proxied
through the DNS server of the server you are connect to:
<tt>./sshuttle --dns -vvr username@sshserver 0/0</tt>
The above is probably what you want to use to prevent
local network attacks such as Firesheep and friends.
(You may be prompted for one or more passwords; first, the (You may be prompted for one or more passwords; first, the
local password to become root using either sudo or su, and local password to become root using either sudo or su, and

11
all.do Normal file
View File

@ -0,0 +1,11 @@
exec >&2
UI=
[ "$(uname)" = "Darwin" ] && UI=ui-macos/all
redo-ifchange Documentation/all version/all $UI
echo
echo "What now?"
[ -z "$UI" ] || echo "- Try the MacOS GUI: open ui-macos/Sshuttle*.app"
echo "- Run sshuttle: ./sshuttle --dns -r HOSTNAME 0/0"
echo "- Read the README: less README.md"
echo "- Read the man page: less Documentation/sshuttle.md"

2
clean.do Normal file
View File

@ -0,0 +1,2 @@
redo ui-macos/clean Documentation/clean
rm -f *~ */*~ .*~ */.*~ *.8 *.tmp */*.tmp *.pyc */*.pyc

125
client.py
View File

@ -102,7 +102,7 @@ class FirewallClient:
self.subnets_include = subnets_include self.subnets_include = subnets_include
self.subnets_exclude = subnets_exclude self.subnets_exclude = subnets_exclude
self.dnsport = dnsport self.dnsport = dnsport
argvbase = ([sys.argv[0]] + argvbase = ([sys.argv[1], sys.argv[0], sys.argv[1]] +
['-v'] * (helpers.verbose or 0) + ['-v'] * (helpers.verbose or 0) +
['--firewall', str(port), str(dnsport)]) ['--firewall', str(port), str(dnsport)])
if ssyslog._p: if ssyslog._p:
@ -171,10 +171,71 @@ class FirewallClient:
def done(self): def done(self):
self.pfile.close() self.pfile.close()
rv = self.p.wait() rv = self.p.wait()
if rv: if rv == EXITCODE_NEEDS_REBOOT:
raise FatalNeedsReboot()
elif rv:
raise Fatal('cleanup: %r returned %d' % (self.argv, 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, def _main(listener, fw, ssh_cmd, remotename, python, latency_control,
dnslistener, seed_hosts, auto_nets, dnslistener, seed_hosts, auto_nets,
syslog, daemon): syslog, daemon):
@ -198,7 +259,14 @@ def _main(listener, fw, ssh_cmd, remotename, python, latency_control,
handlers.append(mux) handlers.append(mux)
expected = 'SSHUTTLE0001' expected = 'SSHUTTLE0001'
try: try:
v = 'x'
while v and v != '\0':
v = serversock.recv(1)
v = 'x'
while v and v != '\0':
v = serversock.recv(1)
initstring = serversock.recv(len(expected)) initstring = serversock.recv(len(expected))
except socket.error, e: except socket.error, e:
if e.args[0] == errno.ECONNRESET: if e.args[0] == errno.ECONNRESET:
@ -248,59 +316,10 @@ def _main(listener, fw, ssh_cmd, remotename, python, latency_control,
fw.sethostip(name, ip) fw.sethostip(name, ip)
mux.got_host_list = onhostlist mux.got_host_list = onhostlist
def onaccept(): handlers.append(Handler([listener], lambda: 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()
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))
if dnslistener: if dnslistener:
handlers.append(Handler([dnslistener], ondns)) handlers.append(Handler([dnslistener], lambda: ondns(dnslistener, mux, handlers)))
if seed_hosts != None: if seed_hosts != None:
debug1('seed_hosts: %r\n' % seed_hosts) debug1('seed_hosts: %r\n' % seed_hosts)

7
default.8.do Normal file
View File

@ -0,0 +1,7 @@
exec >&2
if pandoc </dev/null 2>/dev/null; then
pandoc -s -r markdown -w man -o $3 $2.md
else
echo "Warning: pandoc not installed; can't generate manpages."
redo-always
fi

175
do Executable file
View File

@ -0,0 +1,175 @@
#!/bin/sh
#
# A minimal alternative to djb redo that doesn't support incremental builds.
# For the full version, visit http://github.com/apenwarr/redo
#
# The author disclaims copyright to this source file and hereby places it in
# the public domain. (2010 12 14)
#
# By default, no output coloring.
green=""
bold=""
plain=""
if [ -n "$TERM" -a "$TERM" != "dumb" ] && tty <&2 >/dev/null 2>&1; then
green="$(printf '\033[32m')"
bold="$(printf '\033[1m')"
plain="$(printf '\033[m')"
fi
_dirsplit()
{
base=${1##*/}
dir=${1%$base}
}
dirname()
(
_dirsplit "$1"
dir=${dir%/}
echo "${dir:-.}"
)
_dirsplit "$0"
export REDO=$(cd "${dir:-.}" && echo "$PWD/$base")
DO_TOP=
if [ -z "$DO_BUILT" ]; then
DO_TOP=1
[ -n "$*" ] || set all # only toplevel redo has a default target
export DO_BUILT=$PWD/.do_built
: >>"$DO_BUILT"
echo "Removing previously built files..." >&2
sort -u "$DO_BUILT" | tee "$DO_BUILT.new" |
while read f; do printf "%s\0%s.did\0" "$f" "$f"; done |
xargs -0 rm -f 2>/dev/null
mv "$DO_BUILT.new" "$DO_BUILT"
DO_PATH=$DO_BUILT.dir
export PATH=$DO_PATH:$PATH
rm -rf "$DO_PATH"
mkdir "$DO_PATH"
for d in redo redo-ifchange; do
ln -s "$REDO" "$DO_PATH/$d";
done
[ -e /bin/true ] && TRUE=/bin/true || TRUE=/usr/bin/true
for d in redo-ifcreate redo-stamp redo-always; do
ln -s $TRUE "$DO_PATH/$d";
done
fi
_find_dofile_pwd()
{
dofile=default.$1.do
while :; do
dofile=default.${dofile#default.*.}
[ -e "$dofile" -o "$dofile" = default.do ] && break
done
ext=${dofile#default}
ext=${ext%.do}
base=${1%$ext}
}
_find_dofile()
{
local prefix=
while :; do
_find_dofile_pwd "$1"
[ -e "$dofile" ] && break
[ "$PWD" = "/" ] && break
target=${PWD##*/}/$target
tmp=${PWD##*/}/$tmp
prefix=${PWD##*/}/$prefix
cd ..
done
base=$prefix$base
}
_run_dofile()
{
export DO_DEPTH="$DO_DEPTH "
export REDO_TARGET=$PWD/$target
local line1
set -e
read line1 <"$PWD/$dofile"
cmd=${line1#"#!/"}
if [ "$cmd" != "$line1" ]; then
/$cmd "$PWD/$dofile" "$@" >"$tmp.tmp2"
else
:; . "$PWD/$dofile" >"$tmp.tmp2"
fi
}
_do()
{
local dir=$1 target=$2 tmp=$3
if [ ! -e "$target" ] || [ -d "$target" -a ! -e "$target.did" ]; then
printf '%sdo %s%s%s%s\n' \
"$green" "$DO_DEPTH" "$bold" "$dir$target" "$plain" >&2
echo "$PWD/$target" >>"$DO_BUILT"
dofile=$target.do
base=$target
ext=
[ -e "$target.do" ] || _find_dofile "$target"
if [ ! -e "$dofile" ]; then
echo "do: $target: no .do file" >&2
return 1
fi
[ ! -e "$DO_BUILT" ] || [ ! -d "$(dirname "$target")" ] ||
: >>"$target.did"
( _run_dofile "$target" "$base" "$tmp.tmp" )
rv=$?
if [ $rv != 0 ]; then
printf "do: %s%s\n" "$DO_DEPTH" \
"$dir$target: got exit code $rv" >&2
rm -f "$tmp.tmp" "$tmp.tmp2"
return $rv
fi
mv "$tmp.tmp" "$target" 2>/dev/null ||
! test -s "$tmp.tmp2" ||
mv "$tmp.tmp2" "$target" 2>/dev/null
rm -f "$tmp.tmp2"
else
echo "do $DO_DEPTH$target exists." >&2
fi
}
# Make corrections for directories that don't actually exist yet.
_dir_shovel()
{
local dir base
xdir=$1 xbase=$2 xbasetmp=$2
while [ ! -d "$xdir" -a -n "$xdir" ]; do
_dirsplit "${xdir%/}"
xbasetmp=${base}__$xbase
xdir=$dir xbase=$base/$xbase
echo "xbasetmp='$xbasetmp'" >&2
done
}
redo()
{
for i in "$@"; do
_dirsplit "$i"
_dir_shovel "$dir" "$base"
dir=$xdir base=$xbase basetmp=$xbasetmp
( cd "$dir" && _do "$dir" "$base" "$basetmp" ) || return 1
done
}
set -e
redo "$@"
if [ -n "$DO_TOP" ]; then
echo "Removing stamp files..." >&2
[ ! -e "$DO_BUILT" ] ||
while read f; do printf "%s.did\0" "$f"; done <"$DO_BUILT" |
xargs -0 rm -f 2>/dev/null
fi

View File

@ -1,4 +1,4 @@
import re, errno, socket, select, struct import re, errno, socket, select, signal, struct
import compat.ssubprocess as ssubprocess import compat.ssubprocess as ssubprocess
import helpers, ssyslog import helpers, ssyslog
from helpers import * from helpers import *
@ -6,6 +6,27 @@ from helpers import *
# python doesn't have a definition for this # python doesn't have a definition for this
IPPROTO_DIVERT = 254 IPPROTO_DIVERT = 254
# return values from sysctl_set
SUCCESS = 0
SAME = 1
FAILED = -1
NONEXIST = -2
def nonfatal(func, *args):
try:
func(*args)
except Fatal, e:
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): def ipt_chain_exists(name):
argv = ['iptables', '-t', 'nat', '-nL'] argv = ['iptables', '-t', 'nat', '-nL']
@ -20,10 +41,7 @@ def ipt_chain_exists(name):
def ipt(*args): def ipt(*args):
argv = ['iptables', '-t', 'nat'] + list(args) argv = ['iptables', '-t', 'nat'] + list(args)
debug1('>> %s\n' % ' '.join(argv)) _call(argv)
rv = ssubprocess.call(argv)
if rv:
raise Fatal('%r returned %d' % (argv, rv))
_no_ttl_module = False _no_ttl_module = False
@ -57,9 +75,9 @@ def do_iptables(port, dnsport, subnets):
# basic cleanup/setup of chains # basic cleanup/setup of chains
if ipt_chain_exists(chain): if ipt_chain_exists(chain):
ipt('-D', 'OUTPUT', '-j', chain) nonfatal(ipt, '-D', 'OUTPUT', '-j', chain)
ipt('-D', 'PREROUTING', '-j', chain) nonfatal(ipt, '-D', 'PREROUTING', '-j', chain)
ipt('-F', chain) nonfatal(ipt, '-F', chain)
ipt('-X', chain) ipt('-X', chain)
if subnets or dnsport: if subnets or dnsport:
@ -128,14 +146,50 @@ def _fill_oldctls(prefix):
raise Fatal('%r returned no data' % (argv,)) 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): def _sysctl_set(name, val):
argv = ['sysctl', '-w', '%s=%s' % (name, val)] argv = ['sysctl', '-w', '%s=%s' % (name, val)]
debug1('>> %s\n' % ' '.join(argv)) debug1('>> %s\n' % ' '.join(argv))
rv = ssubprocess.call(argv, stdout = open('/dev/null', 'w')) return ssubprocess.call(argv, stdout = open('/dev/null', 'w'))
_changedctls = [] _changedctls = []
def sysctl_set(name, val): def sysctl_set(name, val, permanent=False):
PREFIX = 'net.inet.ip' PREFIX = 'net.inet.ip'
assert(name.startswith(PREFIX + '.')) assert(name.startswith(PREFIX + '.'))
val = str(val) val = str(val)
@ -143,11 +197,24 @@ def sysctl_set(name, val):
_fill_oldctls(PREFIX) _fill_oldctls(PREFIX)
if not (name in _oldctls): if not (name in _oldctls):
debug1('>> No such sysctl: %r\n' % name) debug1('>> No such sysctl: %r\n' % name)
return return NONEXIST
oldval = _oldctls[name] oldval = _oldctls[name]
if val != oldval: 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) _changedctls.append(name)
return _sysctl_set(name, val) return SUCCESS
def _udp_unpack(p): def _udp_unpack(p):
@ -185,10 +252,7 @@ def _handle_diversion(divertsock, dnsport):
def ipfw(*args): def ipfw(*args):
argv = ['ipfw', '-q'] + list(args) argv = ['ipfw', '-q'] + list(args)
debug1('>> %s\n' % ' '.join(argv)) _call(argv)
rv = ssubprocess.call(argv)
if rv:
raise Fatal('%r returned %d' % (argv, rv))
def do_ipfw(port, dnsport, subnets): def do_ipfw(port, dnsport, subnets):
@ -206,7 +270,39 @@ def do_ipfw(port, dnsport, subnets):
if subnets or dnsport: if subnets or dnsport:
sysctl_set('net.inet.ip.fw.enable', 1) sysctl_set('net.inet.ip.fw.enable', 1)
sysctl_set('net.inet.ip.scopedroute', 0)
# 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"
"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)
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', ipfw('add', sport, 'check-state', 'ip',
'from', 'any', 'to', 'any') 'from', 'any', 'to', 'any')
@ -216,11 +312,11 @@ def do_ipfw(port, dnsport, subnets):
for swidth,sexclude,snet in sorted(subnets, reverse=True): for swidth,sexclude,snet in sorted(subnets, reverse=True):
if sexclude: if sexclude:
ipfw('add', sport, 'skipto', xsport, ipfw('add', sport, 'skipto', xsport,
'log', 'tcp', 'tcp',
'from', 'any', 'to', '%s/%s' % (snet,swidth)) 'from', 'any', 'to', '%s/%s' % (snet,swidth))
else: else:
ipfw('add', sport, 'fwd', '127.0.0.1,%d' % port, ipfw('add', sport, 'fwd', '127.0.0.1,%d' % port,
'log', 'tcp', 'tcp',
'from', 'any', 'to', '%s/%s' % (snet,swidth), 'from', 'any', 'to', '%s/%s' % (snet,swidth),
'not', 'ipttl', '42', 'keep-state', 'setup') 'not', 'ipttl', '42', 'keep-state', 'setup')
@ -262,12 +358,12 @@ def do_ipfw(port, dnsport, subnets):
for ip in nslist: for ip in nslist:
# relabel and then catch outgoing DNS requests # relabel and then catch outgoing DNS requests
ipfw('add', sport, 'divert', sport, ipfw('add', sport, 'divert', sport,
'log', 'udp', 'udp',
'from', 'any', 'to', '%s/32' % ip, '53', 'from', 'any', 'to', '%s/32' % ip, '53',
'not', 'ipttl', '42') 'not', 'ipttl', '42')
# relabel DNS responses # relabel DNS responses
ipfw('add', sport, 'divert', sport, ipfw('add', sport, 'divert', sport,
'log', 'udp', 'udp',
'from', 'any', str(dnsport), 'to', 'any', 'from', 'any', str(dnsport), 'to', 'any',
'not', 'ipttl', '42') 'not', 'ipttl', '42')
@ -371,6 +467,11 @@ def main(port, dnsport, syslog):
sys.stdout.write('READY\n') sys.stdout.write('READY\n')
sys.stdout.flush() 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, # ctrl-c shouldn't be passed along to me. When the main sshuttle dies,
# I'll die automatically. # I'll die automatically.
os.setsid() os.setsid()

View File

@ -1,4 +1,4 @@
import sys, os, socket import sys, os, socket, errno
logprefix = '' logprefix = ''
verbose = 0 verbose = 0
@ -30,6 +30,11 @@ class Fatal(Exception):
pass pass
EXITCODE_NEEDS_REBOOT = 111
class FatalNeedsReboot(Fatal):
pass
def list_contains_any(l, sub): def list_contains_any(l, sub):
for i in sub: for i in sub:
if i in l: if i in l:

View File

@ -13,7 +13,11 @@ _nmb_ok = True
_smb_ok = True _smb_ok = True
hostnames = {} hostnames = {}
queue = {} queue = {}
null = open('/dev/null', 'rb+') try:
null = open('/dev/null', 'wb')
except IOError, e:
log('warning: %s\n' % e)
null = os.popen("sh -c 'while read x; do :; done'", 'wb', 4096)
def _is_ip(s): def _is_ip(s):

17
main.py
View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
import sys, os, re import sys, os, re
import helpers, options, client, server, firewall, hostwatch import helpers, options, client, server, firewall, hostwatch
import compat.ssubprocess as ssubprocess import compat.ssubprocess as ssubprocess
@ -55,14 +54,16 @@ l,listen= transproxy to this ip address and port number [127.0.0.1:0]
H,auto-hosts scan for remote hostnames and update local /etc/hosts 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
python= path to python interpreter on the remote server [python] 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)
v,verbose increase debug message verbosity v,verbose increase debug message verbosity
e,ssh-cmd= the command to use to connect to the remote [ssh] e,ssh-cmd= the command to use to connect to the remote [ssh]
seed-hosts= with -H, use these hostnames for initial scan (comma-separated) seed-hosts= with -H, use these hostnames for initial scan (comma-separated)
no-latency-control sacrifice latency to improve bandwidth benchmarks 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 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) syslog send log messages to syslog (default if you use --daemon)
pidfile= pidfile name (only if using --daemon) [./sshuttle.pid] pidfile= pidfile name (only if using --daemon) [./sshuttle.pid]
server (internal use only) server (internal use only)
@ -70,10 +71,17 @@ firewall (internal use only)
hostwatch (internal use only) hostwatch (internal use only)
""" """
o = options.Options(optspec) o = options.Options(optspec)
(opt, flags, extra) = o.parse(sys.argv[1:]) (opt, flags, extra) = o.parse(sys.argv[2:])
if opt.version:
import version
print version.TAG
sys.exit(0)
if opt.daemon: if opt.daemon:
opt.syslog = 1 opt.syslog = 1
if opt.wrap:
import ssnet
ssnet.MAX_CHANNEL = int(opt.wrap)
helpers.verbose = opt.verbose helpers.verbose = opt.verbose
try: try:
@ -118,6 +126,9 @@ try:
parse_subnets(includes), parse_subnets(includes),
parse_subnets(excludes), parse_subnets(excludes),
opt.syslog, opt.daemon, opt.pidfile)) 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: except Fatal, e:
log('fatal: %s\n' % e) log('fatal: %s\n' % e)
sys.exit(99) sys.exit(99)

View File

@ -110,16 +110,51 @@ class DnsProxy(Handler):
def __init__(self, mux, chan, request): def __init__(self, mux, chan, request):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
Handler.__init__(self, [sock]) Handler.__init__(self, [sock])
self.sock = sock
self.timeout = time.time()+30 self.timeout = time.time()+30
self.mux = mux self.mux = mux
self.chan = chan self.chan = chan
self.tries = 0
self.peer = None
self.request = request
self.sock = sock
self.sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42) self.sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
self.sock.connect((resolvconf_random_nameserver(), 53)) self.try_send()
self.sock.send(request)
def try_send(self):
if self.tries >= 3:
return
self.tries += 1
self.peer = resolvconf_random_nameserver()
self.sock.connect((self.peer, 53))
debug2('DNS: sending to %r\n' % self.peer)
try:
self.sock.send(self.request)
except socket.error, e:
if e.args[0] in ssnet.NET_ERRS:
# might have been spurious; try again.
# Note: these errors sometimes are reported by recv(),
# and sometimes by send(). We have to catch both.
debug2('DNS send to %r: %s\n' % (self.peer, e))
self.try_send()
return
else:
log('DNS send to %r: %s\n' % (self.peer, e))
return
def callback(self): def callback(self):
data = self.sock.recv(4096) try:
data = self.sock.recv(4096)
except socket.error, e:
if e.args[0] in ssnet.NET_ERRS:
# might have been spurious; try again.
# Note: these errors sometimes are reported by recv(),
# and sometimes by send(). We have to catch both.
debug2('DNS recv from %r: %s\n' % (self.peer, e))
self.try_send()
return
else:
log('DNS recv from %r: %s\n' % (self.peer, e))
return
debug2('DNS response: %d bytes\n' % len(data)) debug2('DNS response: %d bytes\n' % len(data))
self.mux.send(self.chan, ssnet.CMD_DNS_RESPONSE, data) self.mux.send(self.chan, ssnet.CMD_DNS_RESPONSE, data)
self.ok = False self.ok = False
@ -138,7 +173,7 @@ def main():
debug1(' %s/%d\n' % r) debug1(' %s/%d\n' % r)
# synchronization header # synchronization header
sys.stdout.write('SSHUTTLE0001') sys.stdout.write('\0\0SSHUTTLE0001')
sys.stdout.flush() sys.stdout.flush()
handlers = [] handlers = []

11
ssh.py
View File

@ -73,16 +73,23 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
if not rhost: if not rhost:
argv = [python, '-c', pyscript] # ignore the --python argument when running locally; we already know
# which python version works.
argv = [sys.argv[1], '-c', pyscript]
else: else:
if ssh_cmd: if ssh_cmd:
sshl = ssh_cmd.split(' ') sshl = ssh_cmd.split(' ')
else: else:
sshl = ['ssh'] sshl = ['ssh']
if python:
pycmd = "'%s' -c '%s'" % (python, pyscript)
else:
pycmd = ("P=python2; $P -V 2>/dev/null || P=python; "
"exec \"$P\" -c '%s'") % pyscript
argv = (sshl + argv = (sshl +
portl + portl +
ipv6flag + ipv6flag +
[rhost, '--', "'%s' -c '%s'" % (python, pyscript)]) [rhost, '--', pycmd])
(s1,s2) = socket.socketpair() (s1,s2) = socket.socketpair()
def setup(): def setup():
# runs in the child process # runs in the child process

View File

@ -1 +0,0 @@
main.py

12
sshuttle Executable file
View File

@ -0,0 +1,12 @@
#!/bin/sh
EXE=$0
for i in 1 2 3 4 5 6 7 8 9 10; do
[ -L "$EXE" ] || break
EXE=$(readlink "$EXE")
done
DIR=$(dirname "$EXE")
if python2 -V 2>/dev/null; then
exec python2 "$DIR/main.py" python2 "$@"
else
exec python "$DIR/main.py" python "$@"
fi

View File

@ -1,6 +1,8 @@
import struct, socket, errno, select import struct, socket, errno, select
if not globals().get('skip_imports'): if not globals().get('skip_imports'):
from helpers import * from helpers import *
MAX_CHANNEL = 65535
# these don't exist in the socket module in python 2.3! # these don't exist in the socket module in python 2.3!
SHUT_RD = 0 SHUT_RD = 0
@ -38,7 +40,11 @@ cmd_to_name = {
CMD_DNS_REQ: 'DNS_REQ', CMD_DNS_REQ: 'DNS_REQ',
CMD_DNS_RESPONSE: 'DNS_RESPONSE', CMD_DNS_RESPONSE: 'DNS_RESPONSE',
} }
NET_ERRS = [errno.ECONNREFUSED, errno.ETIMEDOUT,
errno.EHOSTUNREACH, errno.ENETUNREACH,
errno.EHOSTDOWN, errno.ENETDOWN]
def _add(l, elem): def _add(l, elem):
@ -84,7 +90,7 @@ class SockWrapper:
def __init__(self, rsock, wsock, connect_to=None, peername=None): def __init__(self, rsock, wsock, connect_to=None, peername=None):
global _swcount global _swcount
_swcount += 1 _swcount += 1
debug3('creating new SockWrapper (%d now exist\n)' % _swcount) debug3('creating new SockWrapper (%d now exist)\n' % _swcount)
self.exc = None self.exc = None
self.rsock = rsock self.rsock = rsock
self.wsock = wsock self.wsock = wsock
@ -99,7 +105,7 @@ class SockWrapper:
_swcount -= 1 _swcount -= 1
debug1('%r: deleting (%d remain)\n' % (self, _swcount)) debug1('%r: deleting (%d remain)\n' % (self, _swcount))
if self.exc: if self.exc:
debug1('%r: error was: %r\n' % (self, self.exc)) debug1('%r: error was: %s\n' % (self, self.exc))
def __repr__(self): def __repr__(self):
if self.rsock == self.wsock: if self.rsock == self.wsock:
@ -122,20 +128,45 @@ class SockWrapper:
return # already connected return # already connected
self.rsock.setblocking(False) self.rsock.setblocking(False)
debug3('%r: trying connect to %r\n' % (self, self.connect_to)) debug3('%r: trying connect to %r\n' % (self, self.connect_to))
if socket.inet_aton(self.connect_to[0])[0] == '\0':
self.seterr(Exception("Can't connect to %r: "
"IP address starts with zero\n"
% (self.connect_to,)))
self.connect_to = None
return
try: try:
self.rsock.connect(self.connect_to) self.rsock.connect(self.connect_to)
# connected successfully (Linux) # connected successfully (Linux)
self.connect_to = None self.connect_to = None
except socket.error, e: except socket.error, e:
debug3('%r: connect result: %r\n' % (self, e)) debug3('%r: connect result: %s\n' % (self, e))
if e.args[0] == errno.EINVAL:
# this is what happens when you call connect() on a socket
# that is now connected but returned EINPROGRESS last time,
# on BSD, on python pre-2.5.1. We need to use getsockopt()
# to get the "real" error. Later pythons do this
# automatically, so this code won't run.
realerr = self.rsock.getsockopt(socket.SOL_SOCKET,
socket.SO_ERROR)
e = socket.error(realerr, os.strerror(realerr))
debug3('%r: fixed connect result: %s\n' % (self, e))
if e.args[0] in [errno.EINPROGRESS, errno.EALREADY]: if e.args[0] in [errno.EINPROGRESS, errno.EALREADY]:
pass # not connected yet 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: elif e.args[0] == errno.EISCONN:
# connected successfully (BSD) # connected successfully (BSD)
self.connect_to = None self.connect_to = None
elif e.args[0] in [errno.ECONNREFUSED, errno.ETIMEDOUT, elif e.args[0] in NET_ERRS + [errno.EACCES, errno.EPERM]:
errno.EHOSTUNREACH, errno.ENETUNREACH,
errno.EACCES, errno.EPERM]:
# a "normal" kind of error # a "normal" kind of error
self.connect_to = None self.connect_to = None
self.seterr(e) self.seterr(e)
@ -300,7 +331,7 @@ class Mux(Handler):
# channel 0 is special, so we never allocate it # channel 0 is special, so we never allocate it
for timeout in xrange(1024): for timeout in xrange(1024):
self.chani += 1 self.chani += 1
if self.chani > 65535: if self.chani > MAX_CHANNEL:
self.chani = 1 self.chani = 1
if not self.channels.get(self.chani): if not self.channels.get(self.chani):
return self.chani return self.chani

86
stresstest.py Executable file
View File

@ -0,0 +1,86 @@
#!/usr/bin/env python
import sys, os, socket, select, struct, time
listener = socket.socket()
listener.bind(('127.0.0.1', 0))
listener.listen(500)
servers = []
clients = []
remain = {}
NUMCLIENTS = 50
count = 0
while 1:
if len(clients) < NUMCLIENTS:
c = socket.socket()
c.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
c.bind(('0.0.0.0', 0))
c.connect(listener.getsockname())
count += 1
if count >= 16384:
count = 1
print 'cli CREATING %d' % count
b = struct.pack('I', count) + 'x'*count
remain[c] = count
print 'cli >> %r' % len(b)
c.send(b)
c.shutdown(socket.SHUT_WR)
clients.append(c)
r = [listener]
time.sleep(0.1)
else:
r = [listener]+servers+clients
print 'select(%d)' % len(r)
r,w,x = select.select(r, [], [], 5)
assert(r)
for i in r:
if i == listener:
s,addr = listener.accept()
servers.append(s)
elif i in servers:
b = i.recv(4096)
print 'srv << %r' % len(b)
if not i in remain:
assert(len(b) >= 4)
want = struct.unpack('I', b[:4])[0]
b = b[4:]
#i.send('y'*want)
else:
want = remain[i]
if want < len(b):
print 'weird wanted %d bytes, got %d: %r' % (want, len(b), b)
assert(want >= len(b))
want -= len(b)
remain[i] = want
if not b: # EOF
if want:
print 'weird: eof but wanted %d more' % want
assert(want == 0)
i.close()
servers.remove(i)
del remain[i]
else:
print 'srv >> %r' % len(b)
i.send('y'*len(b))
if not want:
i.shutdown(socket.SHUT_WR)
elif i in clients:
b = i.recv(4096)
print 'cli << %r' % len(b)
want = remain[i]
if want < len(b):
print 'weird wanted %d bytes, got %d: %r' % (want, len(b), b)
assert(want >= len(b))
want -= len(b)
remain[i] = want
if not b: # EOF
if want:
print 'weird: eof but wanted %d more' % want
assert(want == 0)
i.close()
clients.remove(i)
del remain[i]
listener.accept()

View File

@ -2,17 +2,17 @@
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10"> <archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
<data> <data>
<int key="IBDocument.SystemTarget">1060</int> <int key="IBDocument.SystemTarget">1060</int>
<string key="IBDocument.SystemVersion">10H574</string> <string key="IBDocument.SystemVersion">10J567</string>
<string key="IBDocument.InterfaceBuilderVersion">762</string> <string key="IBDocument.InterfaceBuilderVersion">762</string>
<string key="IBDocument.AppKitVersion">1038.35</string> <string key="IBDocument.AppKitVersion">1038.35</string>
<string key="IBDocument.HIToolboxVersion">461.00</string> <string key="IBDocument.HIToolboxVersion">462.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions"> <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="NS.object.0">762</string> <string key="NS.object.0">762</string>
</object> </object>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs"> <object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<integer value="233"/> <integer value="237"/>
</object> </object>
<object class="NSArray" key="IBDocument.PluginDependencies"> <object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
@ -41,9 +41,9 @@
<string key="NSClassName">NSApplication</string> <string key="NSClassName">NSApplication</string>
</object> </object>
<object class="NSWindowTemplate" id="411825121"> <object class="NSWindowTemplate" id="411825121">
<int key="NSWindowStyleMask">3</int> <int key="NSWindowStyleMask">11</int>
<int key="NSWindowBacking">2</int> <int key="NSWindowBacking">2</int>
<string key="NSWindowRect">{{157, 116}, {611, 369}}</string> <string key="NSWindowRect">{{324, 171}, {611, 469}}</string>
<int key="NSWTFlags">1886913536</int> <int key="NSWTFlags">1886913536</int>
<string key="NSWindowTitle">Sshuttle VPN Preferences</string> <string key="NSWindowTitle">Sshuttle VPN Preferences</string>
<string key="NSWindowClass">NSWindow</string> <string key="NSWindowClass">NSWindow</string>
@ -51,16 +51,16 @@
<characters key="NS.bytes">View</characters> <characters key="NS.bytes">View</characters>
</object> </object>
<string key="NSWindowContentMaxSize">{1.79769e+308, 1.79769e+308}</string> <string key="NSWindowContentMaxSize">{1.79769e+308, 1.79769e+308}</string>
<string key="NSWindowContentMinSize">{213, 107}</string> <string key="NSWindowContentMinSize">{611, 469}</string>
<object class="NSView" key="NSWindowView" id="174067038"> <object class="NSView" key="NSWindowView" id="174067038">
<reference key="NSNextResponder"/> <reference key="NSNextResponder"/>
<int key="NSvFlags">256</int> <int key="NSvFlags">274</int>
<object class="NSMutableArray" key="NSSubviews"> <object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSTabView" id="389252804"> <object class="NSTabView" id="389252804">
<reference key="NSNextResponder" ref="174067038"/> <reference key="NSNextResponder" ref="174067038"/>
<int key="NSvFlags">12</int> <int key="NSvFlags">18</int>
<string key="NSFrame">{{-8, -10}, {627, 373}}</string> <string key="NSFrame">{{-8, -10}, {627, 473}}</string>
<reference key="NSSuperview" ref="174067038"/> <reference key="NSSuperview" ref="174067038"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
<object class="NSMutableArray" key="NSTabViewItems"> <object class="NSMutableArray" key="NSTabViewItems">
@ -68,13 +68,13 @@
<object class="NSTabViewItem" id="762265164"> <object class="NSTabViewItem" id="762265164">
<string key="NSIdentifier">1</string> <string key="NSIdentifier">1</string>
<object class="NSView" key="NSView" id="60314308"> <object class="NSView" key="NSView" id="60314308">
<nil key="NSNextResponder"/> <reference key="NSNextResponder" ref="389252804"/>
<int key="NSvFlags">256</int> <int key="NSvFlags">274</int>
<object class="NSMutableArray" key="NSSubviews"> <object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSButton" id="26015719"> <object class="NSButton" id="26015719">
<reference key="NSNextResponder" ref="60314308"/> <reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int> <int key="NSvFlags">292</int>
<string key="NSFrame">{{17, 17}, {25, 23}}</string> <string key="NSFrame">{{17, 17}, {25, 23}}</string>
<reference key="NSSuperview" ref="60314308"/> <reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -103,7 +103,7 @@
</object> </object>
<object class="NSButton" id="244571541"> <object class="NSButton" id="244571541">
<reference key="NSNextResponder" ref="60314308"/> <reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int> <int key="NSvFlags">292</int>
<string key="NSFrame">{{41, 17}, {25, 23}}</string> <string key="NSFrame">{{41, 17}, {25, 23}}</string>
<reference key="NSSuperview" ref="60314308"/> <reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -128,7 +128,7 @@
</object> </object>
<object class="NSScrollView" id="776974664"> <object class="NSScrollView" id="776974664">
<reference key="NSNextResponder" ref="60314308"/> <reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int> <int key="NSvFlags">274</int>
<object class="NSMutableArray" key="NSSubviews"> <object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSClipView" id="8658574"> <object class="NSClipView" id="8658574">
@ -139,7 +139,7 @@
<object class="NSTableView" id="849333466"> <object class="NSTableView" id="849333466">
<reference key="NSNextResponder" ref="8658574"/> <reference key="NSNextResponder" ref="8658574"/>
<int key="NSvFlags">256</int> <int key="NSvFlags">256</int>
<string key="NSFrameSize">{224, 282}</string> <string key="NSFrameSize">{224, 372}</string>
<reference key="NSSuperview" ref="8658574"/> <reference key="NSSuperview" ref="8658574"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool> <bool key="NSEnabled">YES</bool>
@ -275,7 +275,7 @@
<int key="NSTableViewDraggingDestinationStyle">0</int> <int key="NSTableViewDraggingDestinationStyle">0</int>
</object> </object>
</object> </object>
<string key="NSFrame">{{1, 1}, {223, 282}}</string> <string key="NSFrame">{{1, 1}, {223, 372}}</string>
<reference key="NSSuperview" ref="776974664"/> <reference key="NSSuperview" ref="776974664"/>
<reference key="NSNextKeyView" ref="849333466"/> <reference key="NSNextKeyView" ref="849333466"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -306,7 +306,7 @@
<double key="NSPercent">0.91812865497076024</double> <double key="NSPercent">0.91812865497076024</double>
</object> </object>
</object> </object>
<string key="NSFrame">{{17, 40}, {225, 284}}</string> <string key="NSFrame">{{17, 40}, {225, 374}}</string>
<reference key="NSSuperview" ref="60314308"/> <reference key="NSSuperview" ref="60314308"/>
<reference key="NSNextKeyView" ref="8658574"/> <reference key="NSNextKeyView" ref="8658574"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -318,8 +318,8 @@
</object> </object>
<object class="NSButton" id="538356055"> <object class="NSButton" id="538356055">
<reference key="NSNextResponder" ref="60314308"/> <reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int> <int key="NSvFlags">265</int>
<string key="NSFrame">{{296, 254}, {273, 18}}</string> <string key="NSFrame">{{291, 356}, {273, 18}}</string>
<reference key="NSSuperview" ref="60314308"/> <reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool> <bool key="NSEnabled">YES</bool>
@ -341,8 +341,8 @@
</object> </object>
<object class="NSTextField" id="889877302"> <object class="NSTextField" id="889877302">
<reference key="NSNextResponder" ref="60314308"/> <reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int> <int key="NSvFlags">265</int>
<string key="NSFrame">{{341, 292}, {249, 22}}</string> <string key="NSFrame">{{341, 392}, {249, 22}}</string>
<reference key="NSSuperview" ref="60314308"/> <reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool> <bool key="NSEnabled">YES</bool>
@ -369,8 +369,8 @@
</object> </object>
<object class="NSPopUpButton" id="801412726"> <object class="NSPopUpButton" id="801412726">
<reference key="NSNextResponder" ref="60314308"/> <reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int> <int key="NSvFlags">265</int>
<string key="NSFrame">{{273, 185}, {320, 26}}</string> <string key="NSFrame">{{273, 193}, {320, 26}}</string>
<reference key="NSSuperview" ref="60314308"/> <reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool> <bool key="NSEnabled">YES</bool>
@ -440,10 +440,77 @@
<int key="NSArrowPosition">2</int> <int key="NSArrowPosition">2</int>
</object> </object>
</object> </object>
<object class="NSPopUpButton" id="451647466">
<reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">265</int>
<string key="NSFrame">{{273, 257}, {320, 26}}</string>
<reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
<object class="NSPopUpButtonCell" key="NSCell" id="970826243">
<int key="NSCellFlags">-2076049856</int>
<int key="NSCellFlags2">2048</int>
<reference key="NSSupport" ref="696441443"/>
<reference key="NSControlView" ref="451647466"/>
<int key="NSButtonFlags">112869631</int>
<int key="NSButtonFlags2">129</int>
<string key="NSAlternateContents"/>
<string key="NSKeyEquivalent"/>
<int key="NSPeriodicDelay">400</int>
<int key="NSPeriodicInterval">75</int>
<object class="NSMenuItem" key="NSMenuItem" id="903120255">
<reference key="NSMenu" ref="790880658"/>
<string key="NSTitle">Choose one</string>
<string key="NSKeyEquiv"/>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
<int key="NSState">1</int>
<reference key="NSOnImage" ref="615977438"/>
<reference key="NSMixedImage" ref="445532764"/>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="970826243"/>
</object>
<bool key="NSMenuItemRespectAlignment">YES</bool>
<object class="NSMenu" key="NSMenu" id="790880658">
<string key="NSTitle">OtherViews</string>
<object class="NSMutableArray" key="NSMenuItems">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="903120255"/>
<object class="NSMenuItem" id="778262848">
<reference key="NSMenu" ref="790880658"/>
<string key="NSTitle">Item 2</string>
<string key="NSKeyEquiv"/>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="615977438"/>
<reference key="NSMixedImage" ref="445532764"/>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="970826243"/>
</object>
<object class="NSMenuItem" id="195135854">
<reference key="NSMenu" ref="790880658"/>
<string key="NSTitle">Item 3</string>
<string key="NSKeyEquiv"/>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="615977438"/>
<reference key="NSMixedImage" ref="445532764"/>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="970826243"/>
</object>
</object>
<reference key="NSMenuFont" ref="696441443"/>
</object>
<int key="NSPreferredEdge">1</int>
<bool key="NSUsesItemFromMenu">YES</bool>
<bool key="NSAltersState">YES</bool>
<int key="NSArrowPosition">2</int>
</object>
</object>
<object class="NSTextField" id="753545988"> <object class="NSTextField" id="753545988">
<reference key="NSNextResponder" ref="60314308"/> <reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int> <int key="NSvFlags">265</int>
<string key="NSFrame">{{261, 294}, {75, 17}}</string> <string key="NSFrame">{{261, 394}, {75, 17}}</string>
<reference key="NSSuperview" ref="60314308"/> <reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool> <bool key="NSEnabled">YES</bool>
@ -459,8 +526,8 @@
</object> </object>
<object class="NSTextField" id="840157770"> <object class="NSTextField" id="840157770">
<reference key="NSNextResponder" ref="60314308"/> <reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int> <int key="NSvFlags">265</int>
<string key="NSFrame">{{261, 217}, {105, 17}}</string> <string key="NSFrame">{{261, 226}, {105, 17}}</string>
<reference key="NSSuperview" ref="60314308"/> <reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool> <bool key="NSEnabled">YES</bool>
@ -474,9 +541,26 @@
<reference key="NSTextColor" ref="399750419"/> <reference key="NSTextColor" ref="399750419"/>
</object> </object>
</object> </object>
<object class="NSTextField" id="459566505">
<reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">265</int>
<string key="NSFrame">{{261, 290}, {105, 17}}</string>
<reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="534245320">
<int key="NSCellFlags">68288064</int>
<int key="NSCellFlags2">272630784</int>
<string key="NSContents">Optimize for:</string>
<reference key="NSSupport" ref="696441443"/>
<reference key="NSControlView" ref="459566505"/>
<reference key="NSBackgroundColor" ref="965844506"/>
<reference key="NSTextColor" ref="399750419"/>
</object>
</object>
<object class="NSButton" id="962924480"> <object class="NSButton" id="962924480">
<reference key="NSNextResponder" ref="60314308"/> <reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int> <int key="NSvFlags">289</int>
<string key="NSFrame">{{276, 40}, {25, 23}}</string> <string key="NSFrame">{{276, 40}, {25, 23}}</string>
<reference key="NSSuperview" ref="60314308"/> <reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -498,7 +582,7 @@
</object> </object>
<object class="NSButton" id="705215911"> <object class="NSButton" id="705215911">
<reference key="NSNextResponder" ref="60314308"/> <reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int> <int key="NSvFlags">289</int>
<string key="NSFrame">{{300, 40}, {25, 23}}</string> <string key="NSFrame">{{300, 40}, {25, 23}}</string>
<reference key="NSSuperview" ref="60314308"/> <reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -520,7 +604,7 @@
</object> </object>
<object class="NSScrollView" id="610703353"> <object class="NSScrollView" id="610703353">
<reference key="NSNextResponder" ref="60314308"/> <reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int> <int key="NSvFlags">273</int>
<object class="NSMutableArray" key="NSSubviews"> <object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSClipView" id="844110079"> <object class="NSClipView" id="844110079">
@ -531,7 +615,7 @@
<object class="NSTableView" id="714838401"> <object class="NSTableView" id="714838401">
<reference key="NSNextResponder" ref="844110079"/> <reference key="NSNextResponder" ref="844110079"/>
<int key="NSvFlags">256</int> <int key="NSvFlags">256</int>
<string key="NSFrameSize">{312, 95}</string> <string key="NSFrameSize">{312, 102}</string>
<reference key="NSSuperview" ref="844110079"/> <reference key="NSSuperview" ref="844110079"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool> <bool key="NSEnabled">YES</bool>
@ -623,7 +707,7 @@
<int key="NSTableViewDraggingDestinationStyle">0</int> <int key="NSTableViewDraggingDestinationStyle">0</int>
</object> </object>
</object> </object>
<string key="NSFrame">{{1, 17}, {312, 95}}</string> <string key="NSFrame">{{1, 17}, {312, 102}}</string>
<reference key="NSSuperview" ref="610703353"/> <reference key="NSSuperview" ref="610703353"/>
<reference key="NSNextKeyView" ref="714838401"/> <reference key="NSNextKeyView" ref="714838401"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -639,7 +723,7 @@
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
<reference key="NSTarget" ref="610703353"/> <reference key="NSTarget" ref="610703353"/>
<string key="NSAction">_doScroller:</string> <string key="NSAction">_doScroller:</string>
<double key="NSPercent">0.8529411764705882</double> <double key="NSPercent">0.96938775510204078</double>
</object> </object>
<object class="NSScroller" id="522064761"> <object class="NSScroller" id="522064761">
<reference key="NSNextResponder" ref="610703353"/> <reference key="NSNextResponder" ref="610703353"/>
@ -669,7 +753,7 @@
</object> </object>
<reference ref="188199142"/> <reference ref="188199142"/>
</object> </object>
<string key="NSFrame">{{276, 63}, {314, 113}}</string> <string key="NSFrame">{{276, 63}, {314, 120}}</string>
<reference key="NSSuperview" ref="60314308"/> <reference key="NSSuperview" ref="60314308"/>
<reference key="NSNextKeyView" ref="844110079"/> <reference key="NSNextKeyView" ref="844110079"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -681,8 +765,32 @@
<reference key="NSCornerView" ref="188199142"/> <reference key="NSCornerView" ref="188199142"/>
<bytes key="NSScrollAmts">QSAAAEEgAABBmAAAQZgAAA</bytes> <bytes key="NSScrollAmts">QSAAAEEgAABBmAAAQZgAAA</bytes>
</object> </object>
<object class="NSButton" id="160987209">
<reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">265</int>
<string key="NSFrame">{{291, 323}, {285, 18}}</string>
<reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="565787077">
<int key="NSCellFlags">-2080244224</int>
<int key="NSCellFlags2">0</int>
<string key="NSContents">Send DNS requests through this server</string>
<reference key="NSSupport" ref="696441443"/>
<reference key="NSControlView" ref="160987209"/>
<int key="NSButtonFlags">1211912703</int>
<int key="NSButtonFlags2">2</int>
<reference key="NSNormalImage" ref="581816235"/>
<reference key="NSAlternateImage" ref="753862261"/>
<string key="NSAlternateContents"/>
<string key="NSKeyEquivalent"/>
<int key="NSPeriodicDelay">200</int>
<int key="NSPeriodicInterval">25</int>
</object>
</object>
</object> </object>
<string key="NSFrame">{{10, 33}, {607, 327}}</string> <string key="NSFrame">{{10, 33}, {607, 427}}</string>
<reference key="NSSuperview" ref="389252804"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
</object> </object>
<string key="NSLabel">SSH Servers</string> <string key="NSLabel">SSH Servers</string>
@ -692,14 +800,14 @@
<object class="NSTabViewItem" id="740075218"> <object class="NSTabViewItem" id="740075218">
<string key="NSIdentifier">2</string> <string key="NSIdentifier">2</string>
<object class="NSView" key="NSView" id="187214803"> <object class="NSView" key="NSView" id="187214803">
<reference key="NSNextResponder" ref="389252804"/> <nil key="NSNextResponder"/>
<int key="NSvFlags">256</int> <int key="NSvFlags">274</int>
<object class="NSMutableArray" key="NSSubviews"> <object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSButton" id="625816566"> <object class="NSButton" id="625816566">
<reference key="NSNextResponder" ref="187214803"/> <reference key="NSNextResponder" ref="187214803"/>
<int key="NSvFlags">268</int> <int key="NSvFlags">269</int>
<string key="NSFrame">{{189, 287}, {177, 29}}</string> <string key="NSFrame">{{195, 374}, {236, 29}}</string>
<reference key="NSSuperview" ref="187214803"/> <reference key="NSSuperview" ref="187214803"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool> <bool key="NSEnabled">YES</bool>
@ -721,8 +829,8 @@
</object> </object>
<object class="NSButton" id="633518934"> <object class="NSButton" id="633518934">
<reference key="NSNextResponder" ref="187214803"/> <reference key="NSNextResponder" ref="187214803"/>
<int key="NSvFlags">268</int> <int key="NSvFlags">269</int>
<string key="NSFrame">{{189, 256}, {270, 29}}</string> <string key="NSFrame">{{195, 343}, {236, 29}}</string>
<reference key="NSSuperview" ref="187214803"/> <reference key="NSSuperview" ref="187214803"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool> <bool key="NSEnabled">YES</bool>
@ -744,15 +852,15 @@
</object> </object>
<object class="NSButton" id="625121428"> <object class="NSButton" id="625121428">
<reference key="NSNextResponder" ref="187214803"/> <reference key="NSNextResponder" ref="187214803"/>
<int key="NSvFlags">268</int> <int key="NSvFlags">269</int>
<string key="NSFrame">{{189, 225}, {270, 29}}</string> <string key="NSFrame">{{195, 312}, {236, 29}}</string>
<reference key="NSSuperview" ref="187214803"/> <reference key="NSSuperview" ref="187214803"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool> <bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="132399775"> <object class="NSButtonCell" key="NSCell" id="132399775">
<int key="NSCellFlags">67239424</int> <int key="NSCellFlags">67239424</int>
<int key="NSCellFlags2">0</int> <int key="NSCellFlags2">0</int>
<string key="NSContents">Send debug information to log</string> <string key="NSContents">Enable debug messages</string>
<reference key="NSSupport" ref="696441443"/> <reference key="NSSupport" ref="696441443"/>
<reference key="NSControlView" ref="625121428"/> <reference key="NSControlView" ref="625121428"/>
<int key="NSButtonFlags">1211912703</int> <int key="NSButtonFlags">1211912703</int>
@ -766,8 +874,7 @@
</object> </object>
</object> </object>
</object> </object>
<string key="NSFrame">{{10, 33}, {607, 327}}</string> <string key="NSFrame">{{10, 33}, {607, 427}}</string>
<reference key="NSSuperview" ref="389252804"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
</object> </object>
<string key="NSLabel">Options</string> <string key="NSLabel">Options</string>
@ -783,7 +890,7 @@
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSScrollView" id="486362220"> <object class="NSScrollView" id="486362220">
<reference key="NSNextResponder" ref="311013698"/> <reference key="NSNextResponder" ref="311013698"/>
<int key="NSvFlags">256</int> <int key="NSvFlags">274</int>
<object class="NSMutableArray" key="NSSubviews"> <object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSClipView" id="708990027"> <object class="NSClipView" id="708990027">
@ -794,7 +901,7 @@
<object class="NSTextView" id="758761310"> <object class="NSTextView" id="758761310">
<reference key="NSNextResponder" ref="708990027"/> <reference key="NSNextResponder" ref="708990027"/>
<int key="NSvFlags">2322</int> <int key="NSvFlags">2322</int>
<string key="NSFrameSize">{596, 14}</string> <string key="NSFrameSize">{596, 114}</string>
<reference key="NSSuperview" ref="708990027"/> <reference key="NSSuperview" ref="708990027"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
<object class="NSTextContainer" key="NSTextContainer" id="548968659"> <object class="NSTextContainer" key="NSTextContainer" id="548968659">
@ -874,7 +981,7 @@
<nil key="NSDelegate"/> <nil key="NSDelegate"/>
</object> </object>
</object> </object>
<string key="NSFrame">{{1, 1}, {596, 310}}</string> <string key="NSFrame">{{1, 1}, {596, 410}}</string>
<reference key="NSSuperview" ref="486362220"/> <reference key="NSSuperview" ref="486362220"/>
<reference key="NSNextKeyView" ref="758761310"/> <reference key="NSNextKeyView" ref="758761310"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -889,7 +996,7 @@
<object class="NSScroller" id="794963499"> <object class="NSScroller" id="794963499">
<reference key="NSNextResponder" ref="486362220"/> <reference key="NSNextResponder" ref="486362220"/>
<int key="NSvFlags">256</int> <int key="NSvFlags">256</int>
<string key="NSFrame">{{597, 1}, {15, 310}}</string> <string key="NSFrame">{{597, 1}, {15, 410}}</string>
<reference key="NSSuperview" ref="486362220"/> <reference key="NSSuperview" ref="486362220"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
<reference key="NSTarget" ref="486362220"/> <reference key="NSTarget" ref="486362220"/>
@ -900,7 +1007,7 @@
<object class="NSScroller" id="324242772"> <object class="NSScroller" id="324242772">
<reference key="NSNextResponder" ref="486362220"/> <reference key="NSNextResponder" ref="486362220"/>
<int key="NSvFlags">256</int> <int key="NSvFlags">256</int>
<string key="NSFrame">{{1, 311}, {596, 15}}</string> <string key="NSFrame">{{1, 411}, {596, 15}}</string>
<reference key="NSSuperview" ref="486362220"/> <reference key="NSSuperview" ref="486362220"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
<int key="NSsFlags">1</int> <int key="NSsFlags">1</int>
@ -910,7 +1017,7 @@
<double key="NSPercent">0.94565218687057495</double> <double key="NSPercent">0.94565218687057495</double>
</object> </object>
</object> </object>
<string key="NSFrame">{{-3, -3}, {613, 327}}</string> <string key="NSFrame">{{-3, -3}, {613, 427}}</string>
<reference key="NSSuperview" ref="311013698"/> <reference key="NSSuperview" ref="311013698"/>
<reference key="NSNextKeyView" ref="708990027"/> <reference key="NSNextKeyView" ref="708990027"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -920,7 +1027,7 @@
<reference key="NSContentView" ref="708990027"/> <reference key="NSContentView" ref="708990027"/>
</object> </object>
</object> </object>
<string key="NSFrame">{{10, 33}, {607, 327}}</string> <string key="NSFrame">{{10, 33}, {607, 427}}</string>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
</object> </object>
<string key="NSLabel">Log Messages</string> <string key="NSLabel">Log Messages</string>
@ -928,17 +1035,17 @@
<reference key="NSTabView" ref="389252804"/> <reference key="NSTabView" ref="389252804"/>
</object> </object>
</object> </object>
<reference key="NSSelectedTabViewItem" ref="740075218"/> <reference key="NSSelectedTabViewItem" ref="762265164"/>
<reference key="NSFont" ref="696441443"/> <reference key="NSFont" ref="696441443"/>
<int key="NSTvFlags">0</int> <int key="NSTvFlags">0</int>
<bool key="NSDrawsBackground">YES</bool> <bool key="NSDrawsBackground">YES</bool>
<object class="NSMutableArray" key="NSSubviews"> <object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="187214803"/> <reference ref="60314308"/>
</object> </object>
</object> </object>
</object> </object>
<string key="NSFrameSize">{611, 369}</string> <string key="NSFrameSize">{611, 469}</string>
<reference key="NSSuperview"/> <reference key="NSSuperview"/>
<object class="NSDictionary" key="NSViewAnimations"> <object class="NSDictionary" key="NSViewAnimations">
<string key="NS.key.0">subviews</string> <string key="NS.key.0">subviews</string>
@ -962,8 +1069,8 @@
</object> </object>
<int key="NSViewLayerContentsRedrawPolicy">2</int> <int key="NSViewLayerContentsRedrawPolicy">2</int>
</object> </object>
<string key="NSScreenRect">{{0, 0}, {800, 578}}</string> <string key="NSScreenRect">{{0, 0}, {1280, 778}}</string>
<string key="NSMinSize">{213, 129}</string> <string key="NSMinSize">{611, 491}</string>
<string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string> <string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string>
</object> </object>
<object class="NSArrayController" id="678105904"> <object class="NSArrayController" id="678105904">
@ -979,10 +1086,6 @@
<string key="NSClassName">SshuttleController</string> <string key="NSClassName">SshuttleController</string>
</object> </object>
<object class="NSUserDefaultsController" id="582889489"> <object class="NSUserDefaultsController" id="582889489">
<object class="NSMutableArray" key="NSDeclaredKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>debug</string>
</object>
<bool key="NSSharedInstance">YES</bool> <bool key="NSSharedInstance">YES</bool>
</object> </object>
<object class="NSCustomObject" id="735871403"> <object class="NSCustomObject" id="735871403">
@ -1000,6 +1103,9 @@
<string>statusMsg</string> <string>statusMsg</string>
<string>status</string> <string>status</string>
<string>isValid</string> <string>isValid</string>
<string>useDns</string>
<string>title</string>
<string>latencyControl</string>
</object> </object>
<string key="NSObjectClassName">SshuttleServer</string> <string key="NSObjectClassName">SshuttleServer</string>
<bool key="NSEditable">YES</bool> <bool key="NSEditable">YES</bool>
@ -1109,35 +1215,6 @@
</object> </object>
<int key="connectionID">480</int> <int key="connectionID">480</int>
</object> </object>
<object class="IBConnectionRecord">
<object class="IBBindingConnection" key="connection">
<string key="label">value: arrangedObjects.host</string>
<reference key="source" ref="807059746"/>
<reference key="destination" ref="59237012"/>
<object class="NSNibBindingConnector" key="connector">
<reference key="NSSource" ref="807059746"/>
<reference key="NSDestination" ref="59237012"/>
<string key="NSLabel">value: arrangedObjects.host</string>
<string key="NSBinding">value</string>
<string key="NSKeyPath">arrangedObjects.host</string>
<object class="NSDictionary" key="NSOptions">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>NSConditionallySetsEditable</string>
<string>NSNullPlaceholder</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<boolean value="YES"/>
<string>Untitled</string>
</object>
</object>
<int key="NSNibBindingConnectorVersion">2</int>
</object>
</object>
<int key="connectionID">495</int>
</object>
<object class="IBConnectionRecord"> <object class="IBConnectionRecord">
<object class="IBBindingConnection" key="connection"> <object class="IBBindingConnection" key="connection">
<string key="label">selectedIndex: selection.autoNets</string> <string key="label">selectedIndex: selection.autoNets</string>
@ -1454,6 +1531,83 @@
</object> </object>
<int key="connectionID">557</int> <int key="connectionID">557</int>
</object> </object>
<object class="IBConnectionRecord">
<object class="IBBindingConnection" key="connection">
<string key="label">value: selection.useDns</string>
<reference key="source" ref="160987209"/>
<reference key="destination" ref="59237012"/>
<object class="NSNibBindingConnector" key="connector">
<reference key="NSSource" ref="160987209"/>
<reference key="NSDestination" ref="59237012"/>
<string key="NSLabel">value: selection.useDns</string>
<string key="NSBinding">value</string>
<string key="NSKeyPath">selection.useDns</string>
<object class="NSDictionary" key="NSOptions">
<string key="NS.key.0">NSNoSelectionPlaceholder</string>
<integer value="0" key="NS.object.0"/>
</object>
<int key="NSNibBindingConnectorVersion">2</int>
</object>
</object>
<int key="connectionID">572</int>
</object>
<object class="IBConnectionRecord">
<object class="IBBindingConnection" key="connection">
<string key="label">value: arrangedObjects.title</string>
<reference key="source" ref="807059746"/>
<reference key="destination" ref="59237012"/>
<object class="NSNibBindingConnector" key="connector">
<reference key="NSSource" ref="807059746"/>
<reference key="NSDestination" ref="59237012"/>
<string key="NSLabel">value: arrangedObjects.title</string>
<string key="NSBinding">value</string>
<string key="NSKeyPath">arrangedObjects.title</string>
<object class="NSDictionary" key="NSOptions">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>NSConditionallySetsEditable</string>
<string>NSNullPlaceholder</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<boolean value="YES"/>
<string>Untitled</string>
</object>
</object>
<int key="NSNibBindingConnectorVersion">2</int>
</object>
</object>
<int key="connectionID">573</int>
</object>
<object class="IBConnectionRecord">
<object class="IBBindingConnection" key="connection">
<string key="label">selectedIndex: selection.latencyControl</string>
<reference key="source" ref="451647466"/>
<reference key="destination" ref="59237012"/>
<object class="NSNibBindingConnector" key="connector">
<reference key="NSSource" ref="451647466"/>
<reference key="NSDestination" ref="59237012"/>
<string key="NSLabel">selectedIndex: selection.latencyControl</string>
<string key="NSBinding">selectedIndex</string>
<string key="NSKeyPath">selection.latencyControl</string>
<object class="NSDictionary" key="NSOptions">
<string key="NS.key.0">NSNoSelectionPlaceholder</string>
<real value="1" key="NS.object.0"/>
</object>
<int key="NSNibBindingConnectorVersion">2</int>
</object>
</object>
<int key="connectionID">581</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">latencyControlField</string>
<reference key="source" ref="307402018"/>
<reference key="destination" ref="451647466"/>
</object>
<int key="connectionID">582</int>
</object>
</object> </object>
<object class="IBMutableOrderedSet" key="objectRecords"> <object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects"> <object class="NSArray" key="orderedObjects">
@ -1554,15 +1708,18 @@
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="889877302"/> <reference ref="889877302"/>
<reference ref="753545988"/> <reference ref="753545988"/>
<reference ref="26015719"/>
<reference ref="244571541"/>
<reference ref="776974664"/>
<reference ref="962924480"/>
<reference ref="705215911"/>
<reference ref="610703353"/>
<reference ref="160987209"/>
<reference ref="801412726"/> <reference ref="801412726"/>
<reference ref="840157770"/> <reference ref="840157770"/>
<reference ref="538356055"/> <reference ref="538356055"/>
<reference ref="776974664"/> <reference ref="451647466"/>
<reference ref="610703353"/> <reference ref="459566505"/>
<reference ref="26015719"/>
<reference ref="244571541"/>
<reference ref="962924480"/>
<reference ref="705215911"/>
</object> </object>
<reference key="parent" ref="762265164"/> <reference key="parent" ref="762265164"/>
</object> </object>
@ -1962,6 +2119,78 @@
<reference key="object" ref="132399775"/> <reference key="object" ref="132399775"/>
<reference key="parent" ref="625121428"/> <reference key="parent" ref="625121428"/>
</object> </object>
<object class="IBObjectRecord">
<int key="objectID">561</int>
<reference key="object" ref="160987209"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="565787077"/>
</object>
<reference key="parent" ref="60314308"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">562</int>
<reference key="object" ref="565787077"/>
<reference key="parent" ref="160987209"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">574</int>
<reference key="object" ref="451647466"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="970826243"/>
</object>
<reference key="parent" ref="60314308"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">575</int>
<reference key="object" ref="970826243"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="790880658"/>
</object>
<reference key="parent" ref="451647466"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">576</int>
<reference key="object" ref="790880658"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="195135854"/>
<reference ref="778262848"/>
<reference ref="903120255"/>
</object>
<reference key="parent" ref="970826243"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">577</int>
<reference key="object" ref="195135854"/>
<reference key="parent" ref="790880658"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">578</int>
<reference key="object" ref="778262848"/>
<reference key="parent" ref="790880658"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">579</int>
<reference key="object" ref="903120255"/>
<reference key="parent" ref="790880658"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">583</int>
<reference key="object" ref="459566505"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="534245320"/>
</object>
<reference key="parent" ref="60314308"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">584</int>
<reference key="object" ref="534245320"/>
<reference key="parent" ref="459566505"/>
</object>
</object> </object>
</object> </object>
<object class="NSMutableDictionary" key="flattenedProperties"> <object class="NSMutableDictionary" key="flattenedProperties">
@ -2055,6 +2284,18 @@
<string>552.IBAttributePlaceholdersKey</string> <string>552.IBAttributePlaceholdersKey</string>
<string>552.IBPluginDependency</string> <string>552.IBPluginDependency</string>
<string>553.IBPluginDependency</string> <string>553.IBPluginDependency</string>
<string>561.IBAttributePlaceholdersKey</string>
<string>561.IBPluginDependency</string>
<string>562.IBPluginDependency</string>
<string>574.IBAttributePlaceholdersKey</string>
<string>574.IBPluginDependency</string>
<string>575.IBPluginDependency</string>
<string>576.IBPluginDependency</string>
<string>577.IBPluginDependency</string>
<string>578.IBPluginDependency</string>
<string>579.IBPluginDependency</string>
<string>583.IBPluginDependency</string>
<string>584.IBPluginDependency</string>
</object> </object>
<object class="NSMutableArray" key="dict.values"> <object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
@ -2062,13 +2303,13 @@
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/> <boolean value="YES"/>
<boolean value="YES"/> <boolean value="YES"/>
<string>{{317, 387}, {611, 369}}</string> <string>{{324, 171}, {611, 469}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>{{317, 387}, {611, 369}}</string> <string>{{324, 171}, {611, 469}}</string>
<boolean value="YES"/> <boolean value="YES"/>
<boolean value="NO"/> <boolean value="NO"/>
<boolean value="YES"/> <boolean value="YES"/>
<string>{213, 107}</string> <string>{611, 469}</string>
<object class="NSMutableDictionary"> <object class="NSMutableDictionary">
<string key="NS.key.0">ToolTip</string> <string key="NS.key.0">ToolTip</string>
<object class="IBToolTipAttribute" key="NS.object.0"> <object class="IBToolTipAttribute" key="NS.object.0">
@ -2232,6 +2473,32 @@
</object> </object>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSMutableDictionary">
<string key="NS.key.0">ToolTip</string>
<object class="IBToolTipAttribute" key="NS.object.0">
<string key="name">ToolTip</string>
<reference key="object" ref="160987209"/>
<string key="toolTip">Search for server names on the remote end and add them to your computer's /etc/hosts file.</string>
</object>
</object>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSMutableDictionary">
<string key="NS.key.0">ToolTip</string>
<object class="IBToolTipAttribute" key="NS.object.0">
<string key="name">ToolTip</string>
<reference key="object" ref="451647466"/>
<string key="toolTip">Choose which network traffic should be routed over the VPN.</string>
</object>
</object>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
</object> </object>
</object> </object>
<object class="NSMutableDictionary" key="unlocalizedProperties"> <object class="NSMutableDictionary" key="unlocalizedProperties">
@ -2250,7 +2517,7 @@
</object> </object>
</object> </object>
<nil key="sourceID"/> <nil key="sourceID"/>
<int key="maxID">557</int> <int key="maxID">584</int>
</object> </object>
<object class="IBClassDescriber" key="IBDocument.Classes"> <object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions"> <object class="NSMutableArray" key="referencedPartialClassDescriptions">
@ -2280,6 +2547,7 @@
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<string>autoReconnectField</string> <string>autoReconnectField</string>
<string>debugField</string> <string>debugField</string>
<string>latencyControlField</string>
<string>logField</string> <string>logField</string>
<string>prefsWindow</string> <string>prefsWindow</string>
<string>routingField</string> <string>routingField</string>
@ -2295,6 +2563,7 @@
<string>id</string> <string>id</string>
<string>id</string> <string>id</string>
<string>id</string> <string>id</string>
<string>id</string>
</object> </object>
</object> </object>
<object class="IBClassDescriptionSource" key="sourceIdentifier"> <object class="IBClassDescriptionSource" key="sourceIdentifier">

View File

@ -1,5 +1,15 @@
exec >&2 exec >&2
redo-ifchange runpython.c redo-ifchange runpython.c
gcc -Wall -o $3 runpython.c \ ARCHES=""
printf "Platforms: "
for d in /usr/libexec/gcc/darwin/*; do
PLAT=$(basename "$d")
[ "$PLAT" != "ppc64" ] || continue # fails for some reason on my Mac
ARCHES="$ARCHES -arch $PLAT"
printf "$PLAT "
done
printf "\n"
gcc $ARCHES \
-Wall -o $3 runpython.c \
-I/usr/include/python2.5 \ -I/usr/include/python2.5 \
-lpython2.5 -lpython2.5

View File

@ -1,4 +1,4 @@
exec >&2 exec >&2
find -name '*~' | xargs rm -f find . -name '*~' | xargs rm -f
rm -rf *.app *.zip *.tar.gz rm -rf *.app *.zip *.tar.gz
rm -f bits/runpython *.nib sources.list rm -f bits/runpython *.nib sources.list

View File

@ -3,9 +3,9 @@ redo-ifchange sources.list
redo-ifchange Info.plist bits/runpython \ redo-ifchange Info.plist bits/runpython \
$(while read name newname; do echo "$name"; done <sources.list) $(while read name newname; do echo "$name"; done <sources.list)
rm -rf "$1.app" rm -rf "$2.app"
mkdir "$1.app" "$1.app/Contents" mkdir "$2.app" "$2.app/Contents"
cd "$1.app/Contents" cd "$2.app/Contents"
cp "$TOP/Info.plist" . cp "$TOP/Info.plist" .
@ -18,11 +18,11 @@ cd "$TOP"
while read name newname; do while read name newname; do
[ -z "$name" ] && continue [ -z "$name" ] && continue
: "${newname:=$name}" : "${newname:=$name}"
outname=$1.app/Contents/Resources/$newname outname=$2.app/Contents/Resources/$newname
outdir=$(dirname "$outname") outdir=$(dirname "$outname")
[ -d "$outdir" ] || mkdir "$outdir" [ -d "$outdir" ] || mkdir "$outdir"
cp "${name-$newname}" "$outname" cp "${name-$newname}" "$outname"
done <sources.list done <sources.list
cd "$1.app" cd "$2.app"
redo-ifchange $(find . -type f) redo-ifchange $(find . -type f)

View File

@ -1,5 +1,5 @@
exec >&2 exec >&2
IFS=" IFS="
" "
redo-ifchange $1.app redo-ifchange $2.app
tar -czf $3 $1.app/ tar -czf $3 $2.app/

View File

@ -1,5 +1,5 @@
exec >&2 exec >&2
IFS=" IFS="
" "
redo-ifchange $1.app redo-ifchange $2.app
zip -q -r $3 $1.app/ zip -q -r $3 $2.app/

View File

@ -1,2 +1,2 @@
redo-ifchange $1.xib redo-ifchange $2.xib
ibtool --compile $3 $1.xib ibtool --compile $3 $2.xib

View File

@ -2,7 +2,8 @@ import sys, os, pty
from AppKit import * from AppKit import *
import my, models, askpass import my, models, askpass
def sshuttle_args(host, auto_nets, auto_hosts, nets, debug): def sshuttle_args(host, auto_nets, auto_hosts, dns, nets, debug,
no_latency_control):
argv = [my.bundle_path('sshuttle/sshuttle', ''), '-r', host] argv = [my.bundle_path('sshuttle/sshuttle', ''), '-r', host]
assert(argv[0]) assert(argv[0])
if debug: if debug:
@ -11,6 +12,10 @@ def sshuttle_args(host, auto_nets, auto_hosts, nets, debug):
argv.append('--auto-nets') argv.append('--auto-nets')
if auto_hosts: if auto_hosts:
argv.append('--auto-hosts') argv.append('--auto-hosts')
if dns:
argv.append('--dns')
if no_latency_control:
argv.append('--no-latency-control')
argv += nets argv += nets
return argv return argv
@ -73,6 +78,11 @@ class Runner:
if pid == self.pid: if pid == self.pid:
if os.WIFEXITED(code): if os.WIFEXITED(code):
self.rv = os.WEXITSTATUS(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: else:
self.rv = -os.WSTOPSIG(code) self.rv = -os.WSTOPSIG(code)
self.serverobj.setConnected_(False) self.serverobj.setConnected_(False)
@ -82,7 +92,10 @@ class Runner:
return self.rv return self.rv
def wait(self): 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): def poll(self):
return self._try_wait(os.WNOHANG) return self._try_wait(os.WNOHANG)
@ -131,6 +144,7 @@ class SshuttleController(NSObject):
prefsWindow = objc.IBOutlet() prefsWindow = objc.IBOutlet()
serversController = objc.IBOutlet() serversController = objc.IBOutlet()
logField = objc.IBOutlet() logField = objc.IBOutlet()
latencyControlField = objc.IBOutlet()
servers = [] servers = []
conns = {} conns = {}
@ -156,11 +170,14 @@ class SshuttleController(NSObject):
manual_nets = ['0/0'] manual_nets = ['0/0']
else: else:
manual_nets = [] manual_nets = []
noLatencyControl = (server.latencyControl() != models.LAT_INTERACTIVE)
conn = Runner(sshuttle_args(host, conn = Runner(sshuttle_args(host,
auto_nets = nets_mode == models.NET_AUTO, auto_nets = nets_mode == models.NET_AUTO,
auto_hosts = server.autoHosts(), auto_hosts = server.autoHosts(),
dns = server.useDns(),
nets = manual_nets, nets = manual_nets,
debug = self.debugField.state()), debug = self.debugField.state(),
no_latency_control = noLatencyControl),
logfunc=logfunc, promptfunc=promptfunc, logfunc=logfunc, promptfunc=promptfunc,
serverobj=server) serverobj=server)
self.conns[host] = conn self.conns[host] = conn
@ -213,6 +230,7 @@ class SshuttleController(NSObject):
if len(self.servers): if len(self.servers):
for i in self.servers: for i in self.servers:
host = i.host() host = i.host()
title = i.title()
want = i.wantConnect() want = i.wantConnect()
connected = i.connected() connected = i.connected()
numnets = len(list(i.nets())) numnets = len(list(i.nets()))
@ -222,9 +240,9 @@ class SshuttleController(NSObject):
additem('Connect %s (no routes)' % host, None, i) additem('Connect %s (no routes)' % host, None, i)
elif want: elif want:
any_conn = i any_conn = i
additem('Disconnect %s' % host, self.cmd_disconnect, i) additem('Disconnect %s' % title, self.cmd_disconnect, i)
else: else:
additem('Connect %s' % host, self.cmd_connect, i) additem('Connect %s' % title, self.cmd_connect, i)
if not want: if not want:
msg = 'Off' msg = 'Off'
elif i.error(): elif i.error():
@ -236,12 +254,6 @@ class SshuttleController(NSObject):
msg = 'Connecting...' msg = 'Connecting...'
any_inprogress = i any_inprogress = i
addnote(' State: %s' % msg) addnote(' State: %s' % msg)
if i.autoNets() == 0:
addnote(' Routes: All')
elif i.autoNets() == 2:
addnote(' Routes: Auto')
else:
addnote(' Routes: Custom')
else: else:
addnote('No servers defined yet') addnote('No servers defined yet')
@ -279,13 +291,17 @@ class SshuttleController(NSObject):
net.setWidth_(width) net.setWidth_(width)
nl.append(net) nl.append(net)
autoNets = s.get('autoNets', 1) autoNets = s.get('autoNets', models.NET_AUTO)
autoHosts = s.get('autoHosts', 1) autoHosts = s.get('autoHosts', True)
useDns = s.get('useDns', autoNets == models.NET_ALL)
latencyControl = s.get('latencyControl', models.LAT_INTERACTIVE)
srv = models.SshuttleServer.alloc().init() srv = models.SshuttleServer.alloc().init()
srv.setHost_(host) srv.setHost_(host)
srv.setAutoNets_(autoNets) srv.setAutoNets_(autoNets)
srv.setAutoHosts_(autoHosts) srv.setAutoHosts_(autoHosts)
srv.setNets_(nl) srv.setNets_(nl)
srv.setUseDns_(useDns)
srv.setLatencyControl_(latencyControl)
sl.append(srv) sl.append(srv)
self.serversController.addObjects_(sl) self.serversController.addObjects_(sl)
self.serversController.setSelectionIndex_(0) self.serversController.setSelectionIndex_(0)
@ -303,7 +319,9 @@ class SshuttleController(NSObject):
d = dict(host=s.host(), d = dict(host=s.host(),
nets=nets, nets=nets,
autoNets=s.autoNets(), autoNets=s.autoNets(),
autoHosts=s.autoHosts()) autoHosts=s.autoHosts(),
useDns=s.useDns(),
latencyControl=s.latencyControl())
l.append(d) l.append(d)
my.Defaults().setObject_forKey_(l, 'servers') my.Defaults().setObject_forKey_(l, 'servers')
self.fill_menu() self.fill_menu()
@ -315,6 +333,11 @@ class SshuttleController(NSObject):
tf('Determine automatically') tf('Determine automatically')
tf('Custom...') tf('Custom...')
self.latencyControlField.removeAllItems()
tf = self.latencyControlField.addItemWithTitle_
tf('Fast transfer')
tf('Low latency')
# Hmm, even when I mark this as !enabled in the .nib, it still comes # Hmm, even when I mark this as !enabled in the .nib, it still comes
# through as enabled. So let's just disable it here (since we don't # through as enabled. So let's just disable it here (since we don't
# support this feature yet). # support this feature yet).

View File

@ -3,6 +3,7 @@ import my
configchange_callback = setconnect_callback = None configchange_callback = setconnect_callback = None
objc_validator = objc.signature('@@:N^@o^@')
def config_changed(): def config_changed():
@ -39,7 +40,7 @@ class SshuttleNet(NSObject):
def setSubnet_(self, v): def setSubnet_(self, v):
self._k_subnet = v self._k_subnet = v
config_changed() config_changed()
@objc.accessor @objc_validator
def validateSubnet_error_(self, value, error): def validateSubnet_error_(self, value, error):
#print 'validateSubnet!' #print 'validateSubnet!'
return True, _validate_ip(value), error return True, _validate_ip(value), error
@ -49,7 +50,7 @@ class SshuttleNet(NSObject):
def setWidth_(self, v): def setWidth_(self, v):
self._k_width = v self._k_width = v
config_changed() config_changed()
@objc.accessor @objc_validator
def validateWidth_error_(self, value, error): def validateWidth_error_(self, value, error):
#print 'validateWidth!' #print 'validateWidth!'
return True, _validate_width(value), error return True, _validate_width(value), error
@ -58,6 +59,9 @@ NET_ALL = 0
NET_AUTO = 1 NET_AUTO = 1
NET_MANUAL = 2 NET_MANUAL = 2
LAT_BANDWIDTH = 0
LAT_INTERACTIVE = 1
class SshuttleServer(NSObject): class SshuttleServer(NSObject):
def init(self): def init(self):
self = super(SshuttleServer, self).init() self = super(SshuttleServer, self).init()
@ -92,13 +96,30 @@ class SshuttleServer(NSObject):
if self.autoNets() == NET_MANUAL and not len(list(self.nets())): if self.autoNets() == NET_MANUAL and not len(list(self.nets())):
return False return False
return True return True
def title(self):
host = self.host()
if not host:
return host
an = self.autoNets()
suffix = ""
if an == NET_ALL:
suffix = " (all traffic)"
elif an == NET_MANUAL:
n = self.nets()
suffix = ' (%d subnet%s)' % (len(n), len(n)!=1 and 's' or '')
return self.host() + suffix
def setTitle_(self, v):
# title is always auto-generated
config_changed()
def host(self): def host(self):
return getattr(self, '_k_host', None) return getattr(self, '_k_host', None)
def setHost_(self, v): def setHost_(self, v):
self._k_host = v self._k_host = v
self.setTitle_(None)
config_changed() config_changed()
@objc.accessor @objc_validator
def validateHost_error_(self, value, error): def validateHost_error_(self, value, error):
#print 'validatehost! %r %r %r' % (self, value, error) #print 'validatehost! %r %r %r' % (self, value, error)
while value.startswith('-'): while value.startswith('-'):
@ -109,6 +130,7 @@ class SshuttleServer(NSObject):
return getattr(self, '_k_nets', []) return getattr(self, '_k_nets', [])
def setNets_(self, v): def setNets_(self, v):
self._k_nets = v self._k_nets = v
self.setTitle_(None)
config_changed() config_changed()
def netsHidden(self): def netsHidden(self):
#print 'checking netsHidden' #print 'checking netsHidden'
@ -122,6 +144,8 @@ class SshuttleServer(NSObject):
def setAutoNets_(self, v): def setAutoNets_(self, v):
self._k_autoNets = v self._k_autoNets = v
self.setNetsHidden_(-1) self.setNetsHidden_(-1)
self.setUseDns_(v == NET_ALL)
self.setTitle_(None)
config_changed() config_changed()
def autoHosts(self): def autoHosts(self):
@ -129,3 +153,15 @@ class SshuttleServer(NSObject):
def setAutoHosts_(self, v): def setAutoHosts_(self, v):
self._k_autoHosts = v self._k_autoHosts = v
config_changed() config_changed()
def useDns(self):
return getattr(self, '_k_useDns', False)
def setUseDns_(self, v):
self._k_useDns = v
config_changed()
def latencyControl(self):
return getattr(self, '_k_latencyControl', LAT_INTERACTIVE)
def setLatencyControl_(self, v):
self._k_latencyControl = v
config_changed()

View File

@ -1,3 +1,4 @@
redo-ifchange debug.app redo-ifchange debug.app
exec >&2 exec >&2
./debug.app/Contents/MacOS/run ./debug.app/Contents/MacOS/Sshuttle

1
version/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
gitvars.pre export-subst

3
version/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/vars
/gitvars
/_version.py

1
version/__init__.py Normal file
View File

@ -0,0 +1 @@
from _version import COMMIT, TAG, DATE

3
version/_version.py.do Normal file
View File

@ -0,0 +1,3 @@
redo-ifchange vars
cat vars

2
version/all.do Normal file
View File

@ -0,0 +1,2 @@
redo-ifchange vars _version.py

3
version/clean.do Normal file
View File

@ -0,0 +1,3 @@
rm -f *~ .*~ *.pyc _version.py vars gitvars

28
version/gitvars.do Normal file
View File

@ -0,0 +1,28 @@
redo-ifchange gitvars.pre prodname
read PROD <prodname
exec >$3
# Fix each line from gitvars.pre where git may or may not have already
# substituted the variables. If someone generated a tarball with 'git archive',
# then the data will have been substituted already. If we're in a checkout of
# the git repo, then it won't, but we can just ask git to do the substitutions
# right now.
while read line; do
# Lines *may* be of the form: $Format: ... $
x=${line#\$Format:} # remove prefix
if [ "$x" != "$line" ]; then
# git didn't substitute it
redo-always # git this from the git repo
x=${x%\$} # remove trailing $
if [ "$x" = "%d" ]; then
tag=$(git describe --match="$PROD-*")
x="(tag: $tag)"
else
x=$(git log -1 --pretty=format:"$x")
fi
fi
echo "$x"
done <gitvars.pre
redo-stamp <$3

3
version/gitvars.pre Normal file
View File

@ -0,0 +1,3 @@
$Format:%H$
$Format:%d$
$Format:%ci$

1
version/prodname Normal file
View File

@ -0,0 +1 @@
sshuttle

40
version/vars.do Normal file
View File

@ -0,0 +1,40 @@
redo-ifchange gitvars prodname
read PROD <prodname
exec <gitvars
read COMMIT
read NAMES
read DATE
# the list of names is of the form:
# (x,y,tag: $PROD-####,tag: $PROD-####,a,b)
# The entries we want are the ones starting with "tag: $PROD-" since those
# refer to the right actual git tags.
names_to_tag()
{
x=${1#\(}
x=${x%\)}
cur=
while [ "$cur" != "$x" ]; do
x=${x# }
x=${x#tag: }
cur=${x%%,*}
tagpost=${cur#$PROD-}
if [ "$cur" != "$tagpost" ]; then
echo "$tagpost"
return 0
fi
x=${x#*,}
done
commitpost=${COMMIT#???????}
commitpre=${COMMIT%$commitpost}
echo "unknown-$commitpre"
}
sTAG=$(names_to_tag "$NAMES")
echo "COMMIT='$COMMIT'"
echo "TAG='$sTAG'"
echo "DATE='${DATE%% *}'"