Compare commits

...

72 Commits

Author SHA1 Message Date
29d2e06bf5 Added --exclude-from feature.
(Slightly modified by apenwarr)
2012-07-06 15:13:30 -04:00
bff1610050 Document missing --dns option in sshuttle manpage 2012-07-06 15:06:07 -04:00
cce6a9d96d firewall.py: catch SIGINT and SIGTERM too.
There were still a few conditions under some OSes that would cause
firewall.py to terminate without cleaning up the firewall settings.  'pkill
sshuttle' was one of them.  Ignore a couple more signals to further ensure a
correct cleanup.

(This only affects sshuttle --firewall, which is a subprocess of the main
sshuttle process.  The firewall is supposed to exit automatically whenever
the client exits, and so far that part seems to work reliably.)
2012-07-06 15:00:28 -04:00
5743f29ed6 server.py: slightly rearrange previous commit.
Add some documentation about the int() vs long() and the reason behind
_shl().  Instead of "from __future__ import generators", just don't use
generators.
2012-07-06 15:00:28 -04:00
42bc6d62db Two small changes to server.py that allow it to run on python2.2 2012-04-19 23:00:29 -07:00
274ee854d4 clean.do: don't forget to do version/clean. 2012-02-07 12:17:56 -05:00
12f6a52ec6 Fix runpython.do for systems with unxpected configurations.
If the expected arch directory doesn't exist, give up and don't specify arch at
all. Currently it expands to '*' which fails.

[slightly modified by apenwarr]
2012-02-07 12:16:31 -05:00
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
f2297066e7 Oops, left in a junk option that causes a crash without --dns. 2011-01-26 11:26:35 -08:00
0bf0351d9b Merge branch 'dns'
* dns:
  dns on MacOS: use divert sockets instead of 'fwd' rules.
  client.py: do DNS listener on the same port as the TCP listener.
  Move client._islocal() to helpers.islocal() in preparation for sharing.
  dns: add support for MacOS (but it doesn't work...)
  Oops, dns_done() crashed if the request had already been timed out.
  dns: trim DNS channel handlers after a response, or after a timeout.
  dns: extract 'nameserver' lines from /etc/resolv.conf
  Extremely basic, but functional, DNS proxying support (--dns option)
2011-01-26 05:29:51 -08:00
9731680d2e dns on MacOS: use divert sockets instead of 'fwd' rules.
It turns out diverting UDP sockets is pretty easy compared to TCP (which
makes it all the more embarrassing that they screwed up 'fwd' support for
UDP and not TCP, but oh well).  So let's use divert sockets instead of
transproxy for our DNS packets.

This is a little tricky because we have to do it all in firewall.py, since
divert sockets require root access, and only firewall.py has root access.
2011-01-26 05:25:27 -08:00
88937e148e client.py: do DNS listener on the same port as the TCP listener.
UDP and TCP have separate port namespaces, so to make it easier to keep
track of what's going on, just use the same transproxy port number for both.
We still need two sockets, but now tcpdumps are easier to understand.
2011-01-26 05:25:26 -08:00
7f3c522c56 Move client._islocal() to helpers.islocal() in preparation for sharing. 2011-01-26 05:25:26 -08:00
ebfc3703ec dns: add support for MacOS (but it doesn't work...)
...because stupid MacOS ipfw 'fwd' rules don't work quite right with udp.
It can intercept packets bound for remote hosts, but it doesn't correctly
rewrite the port number from its original to the new socket, so it gets
dropped by the local kernel anyway.

That is, a packet to 1.2.3.4:53 should be redirected to, say,
127.0.0.1:9999, the local DNS listener socket.  But instead, it gets sent to
127.0.0.1:53, which nobody is listening on, so it gets eaten.

Sigh.
2011-01-26 05:25:26 -08:00
760740e9aa Oops, dns_done() crashed if the request had already been timed out. 2011-01-26 05:25:26 -08:00
b570778894 dns: trim DNS channel handlers after a response, or after a timeout.
This avoids memory/socket leaks.
2011-01-26 02:34:46 -08:00
4c5185dc55 dns: extract 'nameserver' lines from /etc/resolv.conf 2011-01-26 02:34:46 -08:00
a2fcb08a2d Extremely basic, but functional, DNS proxying support (--dns option)
Limitations:
- uses a hardcoded DNS server IP on both client and server
- never expires request/response objects, so leaks memory and sockets
- works only with iptables, not with ipfw
2011-01-26 02:34:46 -08:00
e7a19890aa Merge branch 'fullness'
Tests with speedtest.net to a linode.com server:

                       Downstream     Upstream

No sshuttle            1.25 Mbit/s    0.55 Mbit/s
Default                0.75 Mbit/s    0.51 Mbit/s
--no-latency-control   1.25 Mbit/s    0.55 Mbit/s

* fullness:
  man page for the --no-latency-control option.
  options: remove unused 'exe' parameter
  options.py: generate usage string correctly for no-* options.
  Implement the optional fullness checking a bit more like I like it.
  new option to disable fullness checking
2011-01-25 22:11:28 -08:00
d9b1bb52e5 man page for the --no-latency-control option. 2011-01-25 21:30:29 -08:00
a30c4d7ccb options: remove unused 'exe' parameter
The 'exe' parameter was added in the hope of using it for additional
contextual information in the help text that Options generates. It was
till then abandoned and was judged as superflous information.

Remove the 'exe' parameter from Options' constructor.

(copied from the 'bup' project)

Signed-off-by: Gabriel Filion <lelutin@gmail.com>
2011-01-25 21:19:28 -08:00
9877a8d6fb options.py: generate usage string correctly for no-* options.
Signed-off-by: Avery Pennarun <apenwarr@gmail.com>
2011-01-25 21:14:51 -08:00
8fde1155da Implement the optional fullness checking a bit more like I like it.
Looks like it worked before, but personal preference is a killer.

The new name is "--no-latency-control".
2011-01-25 21:07:39 -08:00
fdb7c9b995 new option to disable fullness checking
On high latency links, the PING/PONG round trip triggered by fullness
checking could kill the bandwidth. Disabling it could result in >10x
bandwidth increase in some setups where the existing latency is already high
and the available bandwidth is also high.
2011-01-25 21:05:13 -08:00
675f19f57e Don't die if iptables doesn't have 'ttl match' support.
ttl matching is only needed if your server is the same machine as the
client, which is kind of useless anyway (other than for testing), so there's
no reason for it to be fatal if that doesn't work.

Reported by "Alphazo" on the mailing list, who managed to get sshuttle
working on his Nokia N900 by removing the ttl stuff.
2011-01-25 20:42:10 -08:00
049a0c40ac ui-macos: guess we don't need stupid.py anymore.
It was just a test.
2011-01-22 16:55:18 -08:00
47 changed files with 1819 additions and 292 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
*.pyc
*~
*.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.44
% sshuttle(8) Sshuttle %VERSION%
% Avery Pennarun <apenwarr@gmail.com>
% 2010-12-31
% %DATE%
# NAME
@ -71,6 +71,10 @@ entire subnet to the VPN.
are taken automatically from the server's routing
table.
--dns
: capture local DNS requests and forward to the remote DNS
server.
--python
: specify the name/path of the remote python interpreter.
The default is just `python`, which means to use the
@ -90,6 +94,10 @@ entire subnet to the VPN.
`0/0 -x 1.2.3.0/24` to forward everything except the
local subnet over the VPN, for example.
--exclude-from=*file*
: exclude the subnets specified in a file, one subnet per
line. Useful when you have lots of subnets to exclude.
-v, --verbose
: print more information about the session. This option
can be used more than once for increased verbosity. By
@ -109,6 +117,22 @@ entire subnet to the VPN.
if you use this option to give it a few names to start
from.
--no-latency-control
: sacrifice latency to improve bandwidth benchmarks. ssh
uses really big socket buffers, which can overload the
connection if you start doing large file transfers,
thus making all your other sessions inside the same
tunnel go slowly. Normally, sshuttle tries to avoid
this problem using a "fullness check" that allows only
a certain amount of outstanding data to be buffered at
a time. But on high-bandwidth links, this can leave a
lot of your bandwidth underutilized. It also makes
sshuttle seem slow in bandwidth benchmarks (benchmarks
rarely test ping latency, which is what sshuttle is
trying to control). This option disables the latency
control feature, maximizing bandwidth usage. Use at
your own risk.
-D, --daemon
: automatically fork into the background after connecting
to the remote server. Implies `--syslog`.
@ -237,6 +261,17 @@ between the two separate streams, so a tcp-based tunnel is
fine.
# BUGS
On MacOS 10.6 (at least up to 10.6.6), your network will
stop responding about 10 minutes after the first time you
start sshuttle, because of a MacOS kernel bug relating to
arp and the net.inet.ip.scopedroute sysctl. To fix it,
just switch your wireless off and on. Sshuttle makes the
kernel setting it changes permanent, so this won't happen
again, even after a reboot.
# SEE ALSO
`ssh`(1), `python`(1)

View File

@ -1,19 +1,10 @@
PANDOC:=$(shell \
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)
all:
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
=====================================================
@ -52,7 +63,19 @@ This is how you use it:
on your client machine. You'll need root or sudo
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
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 version/clean
rm -f *~ */*~ .*~ */.*~ *.8 *.tmp */*.tmp *.pyc */*.pyc

160
client.py
View File

@ -1,4 +1,4 @@
import struct, socket, select, errno, re, signal
import struct, socket, select, errno, re, signal, time
import compat.ssubprocess as ssubprocess
import helpers, ssnet, ssh, ssyslog
from ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
@ -6,21 +6,6 @@ from helpers import *
_extra_fd = os.open('/dev/null', os.O_RDONLY)
def _islocal(ip):
sock = socket.socket()
try:
try:
sock.bind((ip, 0))
except socket.error, e:
if e.args[0] == errno.EADDRNOTAVAIL:
return False # not a local IP
else:
raise
finally:
sock.close()
return True # it's a local IP, or there would have been an error
def got_signal(signum, frame):
log('exiting on signal %d\n' % signum)
sys.exit(1)
@ -111,14 +96,15 @@ def original_dst(sock):
class FirewallClient:
def __init__(self, port, subnets_include, subnets_exclude):
def __init__(self, port, subnets_include, subnets_exclude, dnsport):
self.port = port
self.auto_nets = []
self.subnets_include = subnets_include
self.subnets_exclude = subnets_exclude
argvbase = ([sys.argv[0]] +
self.dnsport = dnsport
argvbase = ([sys.argv[1], sys.argv[0], sys.argv[1]] +
['-v'] * (helpers.verbose or 0) +
['--firewall', str(port)])
['--firewall', str(port), str(dnsport)])
if ssyslog._p:
argvbase += ['--syslog']
argv_tries = [
@ -185,11 +171,73 @@ class FirewallClient:
def done(self):
self.pfile.close()
rv = self.p.wait()
if rv:
if rv == EXITCODE_NEEDS_REBOOT:
raise FatalNeedsReboot()
elif rv:
raise Fatal('cleanup: %r returned %d' % (self.argv, rv))
def _main(listener, fw, ssh_cmd, remotename, python, seed_hosts, auto_nets,
def onaccept(listener, mux, handlers):
global _extra_fd
try:
sock,srcip = listener.accept()
except socket.error, e:
if e.args[0] in [errno.EMFILE, errno.ENFILE]:
debug1('Rejected incoming connection: too many open files!\n')
# free up an fd so we can eat the connection
os.close(_extra_fd)
try:
sock,srcip = listener.accept()
sock.close()
finally:
_extra_fd = os.open('/dev/null', os.O_RDONLY)
return
else:
raise
dstip = original_dst(sock)
debug1('Accept: %s:%r -> %s:%r.\n' % (srcip[0],srcip[1],
dstip[0],dstip[1]))
if dstip[1] == listener.getsockname()[1] and islocal(dstip[0]):
debug1("-- ignored: that's my address!\n")
sock.close()
return
chan = mux.next_channel()
if not chan:
log('warning: too many open channels. Discarded connection.\n')
sock.close()
return
mux.send(chan, ssnet.CMD_CONNECT, '%s,%s' % dstip)
outwrap = MuxWrapper(mux, chan)
handlers.append(Proxy(SockWrapper(sock, sock), outwrap))
dnsreqs = {}
def dns_done(chan, data):
peer,sock,timeout = dnsreqs.get(chan) or (None,None,None)
debug3('dns_done: channel=%r peer=%r\n' % (chan, peer))
if peer:
del dnsreqs[chan]
debug3('doing sendto %r\n' % (peer,))
sock.sendto(data, peer)
def ondns(listener, mux, handlers):
pkt,peer = listener.recvfrom(4096)
now = time.time()
if pkt:
debug1('DNS request from %r: %d bytes\n' % (peer, len(pkt)))
chan = mux.next_channel()
dnsreqs[chan] = peer,listener,now+30
mux.send(chan, ssnet.CMD_DNS_REQ, pkt)
mux.channels[chan] = lambda cmd,data: dns_done(chan,data)
for chan,(peer,sock,timeout) in dnsreqs.items():
if timeout < now:
del dnsreqs[chan]
debug3('Remaining DNS requests: %d\n' % len(dnsreqs))
def _main(listener, fw, ssh_cmd, remotename, python, latency_control,
dnslistener, seed_hosts, auto_nets,
syslog, daemon):
handlers = []
if helpers.verbose >= 1:
@ -200,7 +248,8 @@ def _main(listener, fw, ssh_cmd, remotename, python, seed_hosts, auto_nets,
try:
(serverproc, serversock) = ssh.connect(ssh_cmd, remotename, python,
stderr=ssyslog._p and ssyslog._p.stdin)
stderr=ssyslog._p and ssyslog._p.stdin,
options=dict(latency_control=latency_control))
except socket.error, e:
if e.args[0] == errno.EPIPE:
raise Fatal("failed to establish ssh session (1)")
@ -210,7 +259,14 @@ def _main(listener, fw, ssh_cmd, remotename, python, seed_hosts, auto_nets,
handlers.append(mux)
expected = 'SSHUTTLE0001'
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))
except socket.error, e:
if e.args[0] == errno.ECONNRESET:
@ -260,35 +316,10 @@ def _main(listener, fw, ssh_cmd, remotename, python, seed_hosts, auto_nets,
fw.sethostip(name, ip)
mux.got_host_list = onhostlist
def onaccept():
global _extra_fd
try:
sock,srcip = listener.accept()
except socket.error, e:
if e.args[0] in [errno.EMFILE, errno.ENFILE]:
debug1('Rejected incoming connection: too many open files!\n')
# free up an fd so we can eat the connection
os.close(_extra_fd)
try:
sock,srcip = listener.accept()
sock.close()
finally:
_extra_fd = os.open('/dev/null', os.O_RDONLY)
return
else:
raise
dstip = original_dst(sock)
debug1('Accept: %s:%r -> %s:%r.\n' % (srcip[0],srcip[1],
dstip[0],dstip[1]))
if dstip[1] == listener.getsockname()[1] and _islocal(dstip[0]):
debug1("-- ignored: that's my address!\n")
sock.close()
return
chan = mux.next_channel()
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))
handlers.append(Handler([listener], lambda: onaccept(listener, mux, handlers)))
if dnslistener:
handlers.append(Handler([dnslistener], lambda: ondns(dnslistener, mux, handlers)))
if seed_hosts != None:
debug1('seed_hosts: %r\n' % seed_hosts)
@ -300,11 +331,13 @@ def _main(listener, fw, ssh_cmd, remotename, python, seed_hosts, auto_nets,
raise Fatal('server died with error code %d' % rv)
ssnet.runonce(handlers, mux)
mux.callback()
if latency_control:
mux.check_fullness()
mux.callback()
def main(listenip, ssh_cmd, remotename, python, seed_hosts, auto_nets,
def main(listenip, ssh_cmd, remotename, python, latency_control, dns,
seed_hosts, auto_nets,
subnets_include, subnets_exclude, syslog, daemon, pidfile):
if syslog:
ssyslog.start_syslog()
@ -315,8 +348,7 @@ def main(listenip, ssh_cmd, remotename, python, seed_hosts, auto_nets,
log("%s\n" % e)
return 5
debug1('Starting sshuttle proxy.\n')
listener = socket.socket()
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if listenip[1]:
ports = [listenip[1]]
else:
@ -326,8 +358,13 @@ def main(listenip, ssh_cmd, remotename, python, seed_hosts, auto_nets,
debug2('Binding:')
for port in ports:
debug2(' %d' % port)
listener = socket.socket()
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
dnslistener = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
dnslistener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
listener.bind((listenip[0], port))
dnslistener.bind((listenip[0], port))
bound = True
break
except socket.error, e:
@ -340,11 +377,20 @@ def main(listenip, ssh_cmd, remotename, python, seed_hosts, auto_nets,
listenip = listener.getsockname()
debug1('Listening on %r.\n' % (listenip,))
fw = FirewallClient(listenip[1], subnets_include, subnets_exclude)
if dns:
dnsip = dnslistener.getsockname()
debug1('DNS listening on %r.\n' % (dnsip,))
dnsport = dnsip[1]
else:
dnsport = 0
dnslistener = None
fw = FirewallClient(listenip[1], subnets_include, subnets_exclude, dnsport)
try:
return _main(listener, fw, ssh_cmd, remotename,
python, seed_hosts, auto_nets, syslog, daemon)
python, latency_control, dnslistener,
seed_hosts, auto_nets, syslog, daemon)
finally:
try:
if daemon:

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,8 +1,32 @@
import re, errno
import re, errno, socket, select, signal, struct
import compat.ssubprocess as ssubprocess
import helpers, ssyslog
from helpers import *
# python doesn't have a definition for this
IPPROTO_DIVERT = 254
# return values from sysctl_set
SUCCESS = 0
SAME = 1
FAILED = -1
NONEXIST = -2
def nonfatal(func, *args):
try:
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):
argv = ['iptables', '-t', 'nat', '-nL']
@ -17,10 +41,28 @@ def ipt_chain_exists(name):
def ipt(*args):
argv = ['iptables', '-t', 'nat'] + list(args)
debug1('>> %s\n' % ' '.join(argv))
rv = ssubprocess.call(argv)
if rv:
raise Fatal('%r returned %d' % (argv, rv))
_call(argv)
_no_ttl_module = False
def ipt_ttl(*args):
global _no_ttl_module
if not _no_ttl_module:
# we avoid infinite loops by generating server-side connections
# with ttl 42. This makes the client side not recapture those
# connections, in case client == server.
try:
argsplus = list(args) + ['-m', 'ttl', '!', '--ttl', '42']
ipt(*argsplus)
except Fatal:
ipt(*args)
# we only get here if the non-ttl attempt succeeds
log('sshuttle: warning: your iptables is missing '
'the ttl module.\n')
_no_ttl_module = True
else:
ipt(*args)
# We name the chain based on the transproxy port number so that it's possible
@ -28,22 +70,23 @@ def ipt(*args):
# multiple copies shouldn't have overlapping subnets, or only the most-
# recently-started one will win (because we use "-I OUTPUT 1" instead of
# "-A OUTPUT").
def do_iptables(port, subnets):
def do_iptables(port, dnsport, subnets):
chain = 'sshuttle-%s' % port
# basic cleanup/setup of chains
if ipt_chain_exists(chain):
ipt('-D', 'OUTPUT', '-j', chain)
ipt('-D', 'PREROUTING', '-j', chain)
ipt('-F', chain)
nonfatal(ipt, '-D', 'OUTPUT', '-j', chain)
nonfatal(ipt, '-D', 'PREROUTING', '-j', chain)
nonfatal(ipt, '-F', chain)
ipt('-X', chain)
if subnets:
if subnets or dnsport:
ipt('-N', chain)
ipt('-F', chain)
ipt('-I', 'OUTPUT', '1', '-j', chain)
ipt('-I', 'PREROUTING', '1', '-j', chain)
if subnets:
# create new subnet entries. Note that we're sorting in a very
# particular order: we need to go from most-specific (largest swidth)
# to least-specific, and at any given level of specificity, we want
@ -55,12 +98,19 @@ def do_iptables(port, subnets):
'--dest', '%s/%s' % (snet,swidth),
'-p', 'tcp')
else:
ipt('-A', chain, '-j', 'REDIRECT',
ipt_ttl('-A', chain, '-j', 'REDIRECT',
'--dest', '%s/%s' % (snet,swidth),
'-p', 'tcp',
'--to-ports', str(port),
'-m', 'ttl', '!', '--ttl', '42' # to prevent infinite loops
)
'--to-ports', str(port))
if dnsport:
nslist = resolvconf_nameservers()
for ip in nslist:
ipt_ttl('-A', chain, '-j', 'REDIRECT',
'--dest', '%s/32' % ip,
'-p', 'udp',
'--dport', '53',
'--to-ports', str(dnsport))
def ipfw_rule_exists(n):
@ -69,7 +119,7 @@ def ipfw_rule_exists(n):
found = False
for line in p.stdout:
if line.startswith('%05d ' % n):
if not ('ipttl 42 setup keep-state' in line
if not ('ipttl 42' in line
or ('skipto %d' % (n+1)) in line
or 'check-state' in line):
log('non-sshuttle ipfw rule: %r\n' % line.strip())
@ -96,14 +146,50 @@ def _fill_oldctls(prefix):
raise Fatal('%r returned no data' % (argv,))
KERNEL_FLAGS_PATH = '/Library/Preferences/SystemConfiguration/com.apple.Boot'
KERNEL_FLAGS_NAME = 'Kernel Flags'
def _defaults_read_kernel_flags():
argv = ['defaults', 'read', KERNEL_FLAGS_PATH, KERNEL_FLAGS_NAME]
debug1('>> %s\n' % ' '.join(argv))
p = ssubprocess.Popen(argv, stdout = ssubprocess.PIPE)
flagstr = p.stdout.read().strip()
rv = p.wait()
if rv:
raise Fatal('%r returned %d' % (argv, rv))
flags = flagstr and flagstr.split(' ') or []
return flags
def _defaults_write_kernel_flags(flags):
flagstr = ' '.join(flags)
argv = ['defaults', 'write', KERNEL_FLAGS_PATH, KERNEL_FLAGS_NAME,
flagstr]
_call(argv)
argv = ['plutil', '-convert', 'xml1', KERNEL_FLAGS_PATH + '.plist']
_call(argv)
def defaults_write_kernel_flag(name, val):
flags = _defaults_read_kernel_flags()
found = 0
for i in range(len(flags)):
if flags[i].startswith('%s=' % name):
found += 1
flags[i] = '%s=%s' % (name, val)
if not found:
flags.insert(0, '%s=%s' % (name, val))
_defaults_write_kernel_flags(flags)
def _sysctl_set(name, val):
argv = ['sysctl', '-w', '%s=%s' % (name, val)]
debug1('>> %s\n' % ' '.join(argv))
rv = ssubprocess.call(argv, stdout = open('/dev/null', 'w'))
return ssubprocess.call(argv, stdout = open('/dev/null', 'w'))
_changedctls = []
def sysctl_set(name, val):
def sysctl_set(name, val, permanent=False):
PREFIX = 'net.inet.ip'
assert(name.startswith(PREFIX + '.'))
val = str(val)
@ -111,22 +197,65 @@ def sysctl_set(name, val):
_fill_oldctls(PREFIX)
if not (name in _oldctls):
debug1('>> No such sysctl: %r\n' % name)
return
return NONEXIST
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)
return _sysctl_set(name, val)
return SUCCESS
def _udp_unpack(p):
src = (socket.inet_ntoa(p[12:16]), struct.unpack('!H', p[20:22])[0])
dst = (socket.inet_ntoa(p[16:20]), struct.unpack('!H', p[22:24])[0])
return src, dst
def _udp_repack(p, src, dst):
addrs = socket.inet_aton(src[0]) + socket.inet_aton(dst[0])
ports = struct.pack('!HH', src[1], dst[1])
return p[:12] + addrs + ports + p[24:]
_real_dns_server = [None]
def _handle_diversion(divertsock, dnsport):
p,tag = divertsock.recvfrom(4096)
src,dst = _udp_unpack(p)
debug3('got diverted packet from %r to %r\n' % (src, dst))
if dst[1] == 53:
# outgoing DNS
debug3('...packet is a DNS request.\n')
_real_dns_server[0] = dst
dst = ('127.0.0.1', dnsport)
elif src[1] == dnsport:
if islocal(src[0]):
debug3('...packet is a DNS response.\n')
src = _real_dns_server[0]
else:
log('weird?! unexpected divert from %r to %r\n' % (src, dst))
assert(0)
newp = _udp_repack(p, src, dst)
divertsock.sendto(newp, tag)
def ipfw(*args):
argv = ['ipfw', '-q'] + list(args)
debug1('>> %s\n' % ' '.join(argv))
rv = ssubprocess.call(argv)
if rv:
raise Fatal('%r returned %d' % (argv, rv))
_call(argv)
def do_ipfw(port, subnets):
def do_ipfw(port, dnsport, subnets):
sport = str(port)
xsport = str(port+1)
@ -139,25 +268,117 @@ def do_ipfw(port, subnets):
oldval = _oldctls[name]
_sysctl_set(name, oldval)
if subnets:
if subnets or dnsport:
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',
'from', 'any', 'to', 'any')
if subnets:
# create new subnet entries
for swidth,sexclude,snet in sorted(subnets, reverse=True):
if sexclude:
ipfw('add', sport, 'skipto', xsport,
'log', 'tcp',
'tcp',
'from', 'any', 'to', '%s/%s' % (snet,swidth))
else:
ipfw('add', sport, 'fwd', '127.0.0.1,%d' % port,
'log', 'tcp',
'tcp',
'from', 'any', 'to', '%s/%s' % (snet,swidth),
'not', 'ipttl', '42', 'keep-state', 'setup')
# This part is much crazier than it is on Linux, because MacOS (at least
# 10.6, and probably other versions, and maybe FreeBSD too) doesn't
# correctly fixup the dstip/dstport for UDP packets when it puts them
# through a 'fwd' rule. It also doesn't fixup the srcip/srcport in the
# response packet. In Linux iptables, all that happens magically for us,
# so we just redirect the packets and relax.
#
# On MacOS, we have to fix the ports ourselves. For that, we use a
# 'divert' socket, which receives raw packets and lets us mangle them.
#
# Here's how it works. Let's say the local DNS server is 1.1.1.1:53,
# and the remote DNS server is 2.2.2.2:53, and the local transproxy port
# is 10.0.0.1:12300, and a client machine is making a request from
# 10.0.0.5:9999. We see a packet like this:
# 10.0.0.5:9999 -> 1.1.1.1:53
# Since the destip:port matches one of our local nameservers, it will
# match a 'fwd' rule, thus grabbing it on the local machine. However,
# the local kernel will then see a packet addressed to *:53 and
# not know what to do with it; there's nobody listening on port 53. Thus,
# we divert it, rewriting it into this:
# 10.0.0.5:9999 -> 10.0.0.1:12300
# This gets proxied out to the server, which sends it to 2.2.2.2:53,
# and the answer comes back, and the proxy sends it back out like this:
# 10.0.0.1:12300 -> 10.0.0.5:9999
# But that's wrong! The original machine expected an answer from
# 1.1.1.1:53, so we have to divert the *answer* and rewrite it:
# 1.1.1.1:53 -> 10.0.0.5:9999
#
# See? Easy stuff.
if dnsport:
divertsock = socket.socket(socket.AF_INET, socket.SOCK_RAW,
IPPROTO_DIVERT)
divertsock.bind(('0.0.0.0', port)) # IP field is ignored
nslist = resolvconf_nameservers()
for ip in nslist:
# relabel and then catch outgoing DNS requests
ipfw('add', sport, 'divert', sport,
'udp',
'from', 'any', 'to', '%s/32' % ip, '53',
'not', 'ipttl', '42')
# relabel DNS responses
ipfw('add', sport, 'divert', sport,
'udp',
'from', 'any', str(dnsport), 'to', 'any',
'not', 'ipttl', '42')
def do_wait():
while 1:
r,w,x = select.select([sys.stdin, divertsock], [], [])
if divertsock in r:
_handle_diversion(divertsock, dnsport)
if sys.stdin in r:
return
else:
do_wait = None
return do_wait
def program_exists(name):
paths = (os.getenv('PATH') or os.defpath).split(os.pathsep)
@ -166,6 +387,7 @@ def program_exists(name):
if os.path.exists(fn):
return not os.path.isdir(fn) and os.access(fn, os.X_OK)
hostmap = {}
def rewrite_etc_hosts(port):
HOSTSFILE='/etc/hosts'
@ -216,9 +438,11 @@ def restore_etc_hosts(port):
# exit. In case that fails, it's not the end of the world; future runs will
# supercede it in the transproxy list, at least, so the leftover rules
# are hopefully harmless.
def main(port, syslog):
def main(port, dnsport, syslog):
assert(port > 0)
assert(port <= 65535)
assert(dnsport >= 0)
assert(dnsport <= 65535)
if os.getuid() != 0:
raise Fatal('you must be root (or enable su/sudo) to set the firewall')
@ -243,6 +467,13 @@ def main(port, syslog):
sys.stdout.write('READY\n')
sys.stdout.flush()
# don't disappear if our controlling terminal or stdout/stderr
# disappears; we still have to clean up.
signal.signal(signal.SIGHUP, signal.SIG_IGN)
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
signal.signal(signal.SIGTERM, signal.SIG_IGN)
signal.signal(signal.SIGINT, signal.SIG_IGN)
# ctrl-c shouldn't be passed along to me. When the main sshuttle dies,
# I'll die automatically.
os.setsid()
@ -272,7 +503,7 @@ def main(port, syslog):
try:
if line:
debug1('firewall manager: starting transproxy.\n')
do_it(port, subnets)
do_wait = do_it(port, dnsport, subnets)
sys.stdout.write('STARTED\n')
try:
@ -286,6 +517,7 @@ def main(port, syslog):
# to stay running so that we don't need a *second* password
# authentication at shutdown time - that cleanup is important!
while 1:
if do_wait: do_wait()
line = sys.stdin.readline(128)
if line.startswith('HOST '):
(name,ip) = line[5:].strip().split(',', 1)
@ -300,5 +532,5 @@ def main(port, syslog):
debug1('firewall manager: undoing changes.\n')
except:
pass
do_it(port, [])
do_it(port, 0, [])
restore_etc_hosts(port)

View File

@ -1,4 +1,4 @@
import sys, os
import sys, os, socket, errno
logprefix = ''
verbose = 0
@ -30,8 +30,51 @@ class Fatal(Exception):
pass
EXITCODE_NEEDS_REBOOT = 111
class FatalNeedsReboot(Fatal):
pass
def list_contains_any(l, sub):
for i in sub:
if i in l:
return True
return False
def resolvconf_nameservers():
l = []
for line in open('/etc/resolv.conf'):
words = line.lower().split()
if len(words) >= 2 and words[0] == 'nameserver':
l.append(words[1])
return l
def resolvconf_random_nameserver():
l = resolvconf_nameservers()
if l:
if len(l) > 1:
# don't import this unless we really need it
import random
random.shuffle(l)
return l[0]
else:
return '127.0.0.1'
def islocal(ip):
sock = socket.socket()
try:
try:
sock.bind((ip, 0))
except socket.error, e:
if e.args[0] == errno.EADDRNOTAVAIL:
return False # not a local IP
else:
raise
finally:
sock.close()
return True # it's a local IP, or there would have been an error

View File

@ -13,7 +13,11 @@ _nmb_ok = True
_smb_ok = True
hostnames = {}
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):

33
main.py
View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
import sys, os, re
import helpers, options, client, server, firewall, hostwatch
import compat.ssubprocess as ssubprocess
@ -54,35 +53,48 @@ sshuttle --hostwatch
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
N,auto-nets automatically determine subnets to route
python= path to python interpreter on the remote server [python]
dns capture local DNS requests and forward to the remote DNS server
python= path to python interpreter on the remote server
r,remote= ssh hostname (and optional username) of remote sshuttle server
x,exclude= exclude this subnet (can be used more than once)
exclude-from= exclude the subnets in a file (whitespace separated)
v,verbose increase debug message verbosity
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)
no-latency-control sacrifice latency to improve bandwidth benchmarks
wrap= restart counting channel numbers after this number (for testing)
D,daemon run in the background as a daemon
V,version print sshuttle's version number
syslog send log messages to syslog (default if you use --daemon)
pidfile= pidfile name (only if using --daemon) [./sshuttle.pid]
server (internal use only)
firewall (internal use only)
hostwatch (internal use only)
"""
o = options.Options('sshuttle', optspec)
(opt, flags, extra) = o.parse(sys.argv[1:])
o = options.Options(optspec)
(opt, flags, extra) = o.parse(sys.argv[2:])
if opt.version:
import version
print version.TAG
sys.exit(0)
if opt.daemon:
opt.syslog = 1
if opt.wrap:
import ssnet
ssnet.MAX_CHANNEL = int(opt.wrap)
helpers.verbose = opt.verbose
try:
if opt.server:
if len(extra) != 0:
o.fatal('no arguments expected')
server.latency_control = opt.latency_control
sys.exit(server.main())
elif opt.firewall:
if len(extra) != 1:
o.fatal('exactly one argument expected')
sys.exit(firewall.main(int(extra[0]), opt.syslog))
if len(extra) != 2:
o.fatal('exactly two arguments expected')
sys.exit(firewall.main(int(extra[0]), int(extra[1]), opt.syslog))
elif opt.hostwatch:
sys.exit(hostwatch.hw_main(extra))
else:
@ -93,6 +105,8 @@ try:
for k,v in flags:
if k in ('-x','--exclude'):
excludes.append(v)
if k in ('-X', '--exclude-from'):
excludes += open(v).read().split()
remotename = opt.remote
if remotename == '' or remotename == '-':
remotename = None
@ -108,11 +122,16 @@ try:
opt.ssh_cmd,
remotename,
opt.python,
opt.latency_control,
opt.dns,
sh,
opt.auto_nets,
parse_subnets(includes),
parse_subnets(excludes),
opt.syslog, opt.daemon, opt.pidfile))
except FatalNeedsReboot, e:
log('You must reboot before using sshuttle.\n')
sys.exit(EXITCODE_NEEDS_REBOOT)
except Fatal, e:
log('fatal: %s\n' % e)
sys.exit(99)

View File

@ -76,9 +76,8 @@ class Options:
By default, the parser function is getopt.gnu_getopt, and the abort
behaviour is to exit the program.
"""
def __init__(self, exe, optspec, optfunc=getopt.gnu_getopt,
def __init__(self, optspec, optfunc=getopt.gnu_getopt,
onabort=_default_onabort):
self.exe = exe
self.optspec = optspec
self._onabort = onabort
self.optfunc = optfunc
@ -122,8 +121,8 @@ class Options:
defval = None
flagl = flags.split(',')
flagl_nice = []
for f in flagl:
f,dvi = _remove_negative_kv(f, _intify(defval))
for _f in flagl:
f,dvi = _remove_negative_kv(_f, _intify(defval))
self._aliases[f] = _remove_negative_k(flagl[0])
self._hasparms[f] = has_parm
self._defaults[f] = dvi
@ -135,7 +134,7 @@ class Options:
self._aliases[f_nice] = _remove_negative_k(flagl[0])
self._longopts.append(f + (has_parm and '=' or ''))
self._longopts.append('no-' + f)
flagl_nice.append('--' + f)
flagl_nice.append('--' + _f)
flags_nice = ', '.join(flagl_nice)
if has_parm:
flags_nice += ' ...'

View File

@ -1,4 +1,4 @@
import re, struct, socket, select, traceback
import re, struct, socket, select, traceback, time
if not globals().get('skip_imports'):
import ssnet, helpers, hostwatch
import compat.ssubprocess as ssubprocess
@ -43,7 +43,12 @@ def _maskbits(netmask):
def _shl(n, bits):
return n * int(2**bits)
# we use our own implementation of left-shift because
# results may be different between older and newer versions
# of python for numbers like 1<<32. We use long() because
# int(2**32) doesn't work in older python, which has limited
# int sizes.
return n * long(2**bits)
def _list_routes():
@ -68,9 +73,11 @@ def _list_routes():
def list_routes():
l = []
for (ip,width) in _list_routes():
if not ip.startswith('0.') and not ip.startswith('127.'):
yield (ip,width)
l.append((ip,width))
return l
def _exc_dump():
@ -106,11 +113,66 @@ class Hostwatch:
self.sock = None
class DnsProxy(Handler):
def __init__(self, mux, chan, request):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
Handler.__init__(self, [sock])
self.timeout = time.time()+30
self.mux = mux
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.try_send()
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):
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))
self.mux.send(self.chan, ssnet.CMD_DNS_RESPONSE, data)
self.ok = False
def main():
if helpers.verbose >= 1:
helpers.logprefix = ' s: '
else:
helpers.logprefix = 'server: '
debug1('latency control setting = %r\n' % latency_control)
routes = list(list_routes())
debug1('available routes:\n')
@ -118,7 +180,7 @@ def main():
debug1(' %s/%d\n' % r)
# synchronization header
sys.stdout.write('SSHUTTLE0001')
sys.stdout.write('\0\0SSHUTTLE0001')
sys.stdout.flush()
handlers = []
@ -164,6 +226,14 @@ def main():
handlers.append(Proxy(MuxWrapper(mux, channel), outwrap))
mux.new_channel = new_channel
dnshandlers = {}
def dns_req(channel, data):
debug2('Incoming DNS request.\n')
h = DnsProxy(mux, channel, data)
handlers.append(h)
dnshandlers[channel] = h
mux.got_dns_req = dns_req
while mux.ok:
if hw.pid:
assert(hw.pid > 0)
@ -172,5 +242,13 @@ def main():
raise Fatal('hostwatch exited unexpectedly: code 0x%04x\n' % rv)
ssnet.runonce(handlers, mux)
if latency_control:
mux.check_fullness()
mux.callback()
if dnshandlers:
now = time.time()
for channel,h in dnshandlers.items():
if h.timeout < now or not h.ok:
del dnshandlers[channel]
h.ok = False

23
ssh.py
View File

@ -14,14 +14,16 @@ def readfile(name):
raise Exception("can't find file %r in any of %r" % (name, path))
def empackage(z, filename):
def empackage(z, filename, data=None):
(path,basename) = os.path.split(filename)
content = z.compress(readfile(filename))
if not data:
data = readfile(filename)
content = z.compress(data)
content += z.flush(zlib.Z_SYNC_FLUSH)
return '%s\n%d\n%s' % (basename, len(content), content)
def connect(ssh_cmd, rhostport, python, stderr):
def connect(ssh_cmd, rhostport, python, stderr, options):
main_exe = sys.argv[0]
portl = []
@ -52,7 +54,9 @@ def connect(ssh_cmd, rhostport, python, stderr):
z = zlib.compressobj(1)
content = readfile('assembler.py')
content2 = (empackage(z, 'helpers.py') +
optdata = ''.join("%s=%r\n" % (k,v) for (k,v) in options.items())
content2 = (empackage(z, 'cmdline_options.py', optdata) +
empackage(z, 'helpers.py') +
empackage(z, 'compat/ssubprocess.py') +
empackage(z, 'ssnet.py') +
empackage(z, 'hostwatch.py') +
@ -69,16 +73,23 @@ def connect(ssh_cmd, rhostport, python, stderr):
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:
if ssh_cmd:
sshl = ssh_cmd.split(' ')
else:
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 +
portl +
ipv6flag +
[rhost, '--', "'%s' -c '%s'" % (python, pyscript)])
[rhost, '--', pycmd])
(s1,s2) = socket.socketpair()
def setup():
# 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

@ -2,6 +2,8 @@ import struct, socket, errno, select
if not globals().get('skip_imports'):
from helpers import *
MAX_CHANNEL = 65535
# these don't exist in the socket module in python 2.3!
SHUT_RD = 0
SHUT_WR = 1
@ -21,6 +23,8 @@ CMD_DATA = 0x4206
CMD_ROUTES = 0x4207
CMD_HOST_REQ = 0x4208
CMD_HOST_LIST = 0x4209
CMD_DNS_REQ = 0x420a
CMD_DNS_RESPONSE = 0x420b
cmd_to_name = {
CMD_EXIT: 'EXIT',
@ -33,9 +37,15 @@ cmd_to_name = {
CMD_ROUTES: 'ROUTES',
CMD_HOST_REQ: 'HOST_REQ',
CMD_HOST_LIST: 'HOST_LIST',
CMD_DNS_REQ: 'DNS_REQ',
CMD_DNS_RESPONSE: 'DNS_RESPONSE',
}
NET_ERRS = [errno.ECONNREFUSED, errno.ETIMEDOUT,
errno.EHOSTUNREACH, errno.ENETUNREACH,
errno.EHOSTDOWN, errno.ENETDOWN]
def _add(l, elem):
if not elem in l:
@ -80,7 +90,7 @@ class SockWrapper:
def __init__(self, rsock, wsock, connect_to=None, peername=None):
global _swcount
_swcount += 1
debug3('creating new SockWrapper (%d now exist\n)' % _swcount)
debug3('creating new SockWrapper (%d now exist)\n' % _swcount)
self.exc = None
self.rsock = rsock
self.wsock = wsock
@ -95,7 +105,7 @@ class SockWrapper:
_swcount -= 1
debug1('%r: deleting (%d remain)\n' % (self, _swcount))
if self.exc:
debug1('%r: error was: %r\n' % (self, self.exc))
debug1('%r: error was: %s\n' % (self, self.exc))
def __repr__(self):
if self.rsock == self.wsock:
@ -118,20 +128,45 @@ class SockWrapper:
return # already connected
self.rsock.setblocking(False)
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:
self.rsock.connect(self.connect_to)
# connected successfully (Linux)
self.connect_to = None
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]:
pass # not connected yet
elif e.args[0] == 0:
# connected successfully (weird Linux bug?)
# Sometimes Linux seems to return EINVAL when it isn't
# invalid. This *may* be caused by a race condition
# between connect() and getsockopt(SO_ERROR) (ie. it
# finishes connecting in between the two, so there is no
# longer an error). However, I'm not sure of that.
#
# I did get at least one report that the problem went away
# when we added this, however.
self.connect_to = None
elif e.args[0] == errno.EISCONN:
# connected successfully (BSD)
self.connect_to = None
elif e.args[0] in [errno.ECONNREFUSED, errno.ETIMEDOUT,
errno.EHOSTUNREACH, errno.ENETUNREACH,
errno.EACCES, errno.EPERM]:
elif e.args[0] in NET_ERRS + [errno.EACCES, errno.EPERM]:
# a "normal" kind of error
self.connect_to = None
self.seterr(e)
@ -281,7 +316,7 @@ class Mux(Handler):
Handler.__init__(self, [rsock, wsock])
self.rsock = rsock
self.wsock = wsock
self.new_channel = self.got_routes = None
self.new_channel = self.got_dns_req = self.got_routes = None
self.got_host_req = self.got_host_list = None
self.channels = {}
self.chani = 0
@ -296,7 +331,7 @@ class Mux(Handler):
# channel 0 is special, so we never allocate it
for timeout in xrange(1024):
self.chani += 1
if self.chani > 65535:
if self.chani > MAX_CHANNEL:
self.chani = 1
if not self.channels.get(self.chani):
return self.chani
@ -343,6 +378,10 @@ class Mux(Handler):
assert(not self.channels.get(channel))
if self.new_channel:
self.new_channel(channel, data)
elif cmd == CMD_DNS_REQ:
assert(not self.channels.get(channel))
if self.got_dns_req:
self.got_dns_req(channel, data)
elif cmd == CMD_ROUTES:
if self.got_routes:
self.got_routes(data)

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">
<data>
<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.AppKitVersion">1038.35</string>
<string key="IBDocument.HIToolboxVersion">461.00</string>
<string key="IBDocument.HIToolboxVersion">462.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="NS.object.0">762</string>
</object>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<bool key="EncodedWithXMLCoder">YES</bool>
<integer value="233"/>
<integer value="237"/>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
@ -41,9 +41,9 @@
<string key="NSClassName">NSApplication</string>
</object>
<object class="NSWindowTemplate" id="411825121">
<int key="NSWindowStyleMask">3</int>
<int key="NSWindowStyleMask">11</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>
<string key="NSWindowTitle">Sshuttle VPN Preferences</string>
<string key="NSWindowClass">NSWindow</string>
@ -51,16 +51,16 @@
<characters key="NS.bytes">View</characters>
</object>
<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">
<reference key="NSNextResponder"/>
<int key="NSvFlags">256</int>
<int key="NSvFlags">274</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSTabView" id="389252804">
<reference key="NSNextResponder" ref="174067038"/>
<int key="NSvFlags">12</int>
<string key="NSFrame">{{-8, -10}, {627, 373}}</string>
<int key="NSvFlags">18</int>
<string key="NSFrame">{{-8, -10}, {627, 473}}</string>
<reference key="NSSuperview" ref="174067038"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<object class="NSMutableArray" key="NSTabViewItems">
@ -68,13 +68,13 @@
<object class="NSTabViewItem" id="762265164">
<string key="NSIdentifier">1</string>
<object class="NSView" key="NSView" id="60314308">
<nil key="NSNextResponder"/>
<int key="NSvFlags">256</int>
<reference key="NSNextResponder" ref="389252804"/>
<int key="NSvFlags">274</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSButton" id="26015719">
<reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{17, 17}, {25, 23}}</string>
<reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -103,7 +103,7 @@
</object>
<object class="NSButton" id="244571541">
<reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{41, 17}, {25, 23}}</string>
<reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -128,7 +128,7 @@
</object>
<object class="NSScrollView" id="776974664">
<reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int>
<int key="NSvFlags">274</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSClipView" id="8658574">
@ -139,7 +139,7 @@
<object class="NSTableView" id="849333466">
<reference key="NSNextResponder" ref="8658574"/>
<int key="NSvFlags">256</int>
<string key="NSFrameSize">{224, 282}</string>
<string key="NSFrameSize">{224, 372}</string>
<reference key="NSSuperview" ref="8658574"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
@ -275,7 +275,7 @@
<int key="NSTableViewDraggingDestinationStyle">0</int>
</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="NSNextKeyView" ref="849333466"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -306,7 +306,7 @@
<double key="NSPercent">0.91812865497076024</double>
</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="NSNextKeyView" ref="8658574"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -318,8 +318,8 @@
</object>
<object class="NSButton" id="538356055">
<reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{296, 254}, {273, 18}}</string>
<int key="NSvFlags">265</int>
<string key="NSFrame">{{291, 356}, {273, 18}}</string>
<reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
@ -341,8 +341,8 @@
</object>
<object class="NSTextField" id="889877302">
<reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{341, 292}, {249, 22}}</string>
<int key="NSvFlags">265</int>
<string key="NSFrame">{{341, 392}, {249, 22}}</string>
<reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
@ -369,8 +369,8 @@
</object>
<object class="NSPopUpButton" id="801412726">
<reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{273, 185}, {320, 26}}</string>
<int key="NSvFlags">265</int>
<string key="NSFrame">{{273, 193}, {320, 26}}</string>
<reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
@ -440,10 +440,77 @@
<int key="NSArrowPosition">2</int>
</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">
<reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{261, 294}, {75, 17}}</string>
<int key="NSvFlags">265</int>
<string key="NSFrame">{{261, 394}, {75, 17}}</string>
<reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
@ -459,8 +526,8 @@
</object>
<object class="NSTextField" id="840157770">
<reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{261, 217}, {105, 17}}</string>
<int key="NSvFlags">265</int>
<string key="NSFrame">{{261, 226}, {105, 17}}</string>
<reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
@ -474,9 +541,26 @@
<reference key="NSTextColor" ref="399750419"/>
</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">
<reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int>
<int key="NSvFlags">289</int>
<string key="NSFrame">{{276, 40}, {25, 23}}</string>
<reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -498,7 +582,7 @@
</object>
<object class="NSButton" id="705215911">
<reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int>
<int key="NSvFlags">289</int>
<string key="NSFrame">{{300, 40}, {25, 23}}</string>
<reference key="NSSuperview" ref="60314308"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -520,7 +604,7 @@
</object>
<object class="NSScrollView" id="610703353">
<reference key="NSNextResponder" ref="60314308"/>
<int key="NSvFlags">268</int>
<int key="NSvFlags">273</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSClipView" id="844110079">
@ -531,7 +615,7 @@
<object class="NSTableView" id="714838401">
<reference key="NSNextResponder" ref="844110079"/>
<int key="NSvFlags">256</int>
<string key="NSFrameSize">{312, 95}</string>
<string key="NSFrameSize">{312, 102}</string>
<reference key="NSSuperview" ref="844110079"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
@ -623,7 +707,7 @@
<int key="NSTableViewDraggingDestinationStyle">0</int>
</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="NSNextKeyView" ref="714838401"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -639,7 +723,7 @@
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<reference key="NSTarget" ref="610703353"/>
<string key="NSAction">_doScroller:</string>
<double key="NSPercent">0.8529411764705882</double>
<double key="NSPercent">0.96938775510204078</double>
</object>
<object class="NSScroller" id="522064761">
<reference key="NSNextResponder" ref="610703353"/>
@ -669,7 +753,7 @@
</object>
<reference ref="188199142"/>
</object>
<string key="NSFrame">{{276, 63}, {314, 113}}</string>
<string key="NSFrame">{{276, 63}, {314, 120}}</string>
<reference key="NSSuperview" ref="60314308"/>
<reference key="NSNextKeyView" ref="844110079"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -681,8 +765,32 @@
<reference key="NSCornerView" ref="188199142"/>
<bytes key="NSScrollAmts">QSAAAEEgAABBmAAAQZgAAA</bytes>
</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>
<string key="NSFrame">{{10, 33}, {607, 327}}</string>
</object>
</object>
<string key="NSFrame">{{10, 33}, {607, 427}}</string>
<reference key="NSSuperview" ref="389252804"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
</object>
<string key="NSLabel">SSH Servers</string>
@ -692,14 +800,14 @@
<object class="NSTabViewItem" id="740075218">
<string key="NSIdentifier">2</string>
<object class="NSView" key="NSView" id="187214803">
<reference key="NSNextResponder" ref="389252804"/>
<int key="NSvFlags">256</int>
<nil key="NSNextResponder"/>
<int key="NSvFlags">274</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSButton" id="625816566">
<reference key="NSNextResponder" ref="187214803"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{189, 287}, {177, 29}}</string>
<int key="NSvFlags">269</int>
<string key="NSFrame">{{195, 374}, {236, 29}}</string>
<reference key="NSSuperview" ref="187214803"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
@ -721,8 +829,8 @@
</object>
<object class="NSButton" id="633518934">
<reference key="NSNextResponder" ref="187214803"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{189, 256}, {270, 29}}</string>
<int key="NSvFlags">269</int>
<string key="NSFrame">{{195, 343}, {236, 29}}</string>
<reference key="NSSuperview" ref="187214803"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
@ -744,15 +852,15 @@
</object>
<object class="NSButton" id="625121428">
<reference key="NSNextResponder" ref="187214803"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{189, 225}, {270, 29}}</string>
<int key="NSvFlags">269</int>
<string key="NSFrame">{{195, 312}, {236, 29}}</string>
<reference key="NSSuperview" ref="187214803"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="132399775">
<int key="NSCellFlags">67239424</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="NSControlView" ref="625121428"/>
<int key="NSButtonFlags">1211912703</int>
@ -766,8 +874,7 @@
</object>
</object>
</object>
<string key="NSFrame">{{10, 33}, {607, 327}}</string>
<reference key="NSSuperview" ref="389252804"/>
<string key="NSFrame">{{10, 33}, {607, 427}}</string>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
</object>
<string key="NSLabel">Options</string>
@ -783,7 +890,7 @@
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSScrollView" id="486362220">
<reference key="NSNextResponder" ref="311013698"/>
<int key="NSvFlags">256</int>
<int key="NSvFlags">274</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSClipView" id="708990027">
@ -794,7 +901,7 @@
<object class="NSTextView" id="758761310">
<reference key="NSNextResponder" ref="708990027"/>
<int key="NSvFlags">2322</int>
<string key="NSFrameSize">{596, 14}</string>
<string key="NSFrameSize">{596, 114}</string>
<reference key="NSSuperview" ref="708990027"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<object class="NSTextContainer" key="NSTextContainer" id="548968659">
@ -874,7 +981,7 @@
<nil key="NSDelegate"/>
</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="NSNextKeyView" ref="758761310"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -889,7 +996,7 @@
<object class="NSScroller" id="794963499">
<reference key="NSNextResponder" ref="486362220"/>
<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"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<reference key="NSTarget" ref="486362220"/>
@ -900,7 +1007,7 @@
<object class="NSScroller" id="324242772">
<reference key="NSNextResponder" ref="486362220"/>
<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"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<int key="NSsFlags">1</int>
@ -910,7 +1017,7 @@
<double key="NSPercent">0.94565218687057495</double>
</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="NSNextKeyView" ref="708990027"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
@ -920,7 +1027,7 @@
<reference key="NSContentView" ref="708990027"/>
</object>
</object>
<string key="NSFrame">{{10, 33}, {607, 327}}</string>
<string key="NSFrame">{{10, 33}, {607, 427}}</string>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
</object>
<string key="NSLabel">Log Messages</string>
@ -928,17 +1035,17 @@
<reference key="NSTabView" ref="389252804"/>
</object>
</object>
<reference key="NSSelectedTabViewItem" ref="740075218"/>
<reference key="NSSelectedTabViewItem" ref="762265164"/>
<reference key="NSFont" ref="696441443"/>
<int key="NSTvFlags">0</int>
<bool key="NSDrawsBackground">YES</bool>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="187214803"/>
<reference ref="60314308"/>
</object>
</object>
</object>
<string key="NSFrameSize">{611, 369}</string>
<string key="NSFrameSize">{611, 469}</string>
<reference key="NSSuperview"/>
<object class="NSDictionary" key="NSViewAnimations">
<string key="NS.key.0">subviews</string>
@ -962,8 +1069,8 @@
</object>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
</object>
<string key="NSScreenRect">{{0, 0}, {800, 578}}</string>
<string key="NSMinSize">{213, 129}</string>
<string key="NSScreenRect">{{0, 0}, {1280, 778}}</string>
<string key="NSMinSize">{611, 491}</string>
<string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string>
</object>
<object class="NSArrayController" id="678105904">
@ -979,10 +1086,6 @@
<string key="NSClassName">SshuttleController</string>
</object>
<object class="NSUserDefaultsController" id="582889489">
<object class="NSMutableArray" key="NSDeclaredKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>debug</string>
</object>
<bool key="NSSharedInstance">YES</bool>
</object>
<object class="NSCustomObject" id="735871403">
@ -1000,6 +1103,9 @@
<string>statusMsg</string>
<string>status</string>
<string>isValid</string>
<string>useDns</string>
<string>title</string>
<string>latencyControl</string>
</object>
<string key="NSObjectClassName">SshuttleServer</string>
<bool key="NSEditable">YES</bool>
@ -1109,35 +1215,6 @@
</object>
<int key="connectionID">480</int>
</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="IBBindingConnection" key="connection">
<string key="label">selectedIndex: selection.autoNets</string>
@ -1454,6 +1531,83 @@
</object>
<int key="connectionID">557</int>
</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 class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
@ -1554,15 +1708,18 @@
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="889877302"/>
<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="840157770"/>
<reference ref="538356055"/>
<reference ref="776974664"/>
<reference ref="610703353"/>
<reference ref="26015719"/>
<reference ref="244571541"/>
<reference ref="962924480"/>
<reference ref="705215911"/>
<reference ref="451647466"/>
<reference ref="459566505"/>
</object>
<reference key="parent" ref="762265164"/>
</object>
@ -1962,6 +2119,78 @@
<reference key="object" ref="132399775"/>
<reference key="parent" ref="625121428"/>
</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 class="NSMutableDictionary" key="flattenedProperties">
@ -2055,6 +2284,18 @@
<string>552.IBAttributePlaceholdersKey</string>
<string>552.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 class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
@ -2062,13 +2303,13 @@
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<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>{{317, 387}, {611, 369}}</string>
<string>{{324, 171}, {611, 469}}</string>
<boolean value="YES"/>
<boolean value="NO"/>
<boolean value="YES"/>
<string>{213, 107}</string>
<string>{611, 469}</string>
<object class="NSMutableDictionary">
<string key="NS.key.0">ToolTip</string>
<object class="IBToolTipAttribute" key="NS.object.0">
@ -2232,6 +2473,32 @@
</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="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 class="NSMutableDictionary" key="unlocalizedProperties">
@ -2250,7 +2517,7 @@
</object>
</object>
<nil key="sourceID"/>
<int key="maxID">557</int>
<int key="maxID">584</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
@ -2280,6 +2547,7 @@
<bool key="EncodedWithXMLCoder">YES</bool>
<string>autoReconnectField</string>
<string>debugField</string>
<string>latencyControlField</string>
<string>logField</string>
<string>prefsWindow</string>
<string>routingField</string>
@ -2295,6 +2563,7 @@
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
</object>
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">

View File

@ -1,5 +1,17 @@
exec >&2
redo-ifchange runpython.c
gcc -Wall -o $3 runpython.c \
ARCHES=""
printf "Platforms: "
if [ -d /usr/libexec/gcc/darwin ]; then
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
fi
printf "\n"
gcc $ARCHES \
-Wall -o $3 runpython.c \
-I/usr/include/python2.5 \
-lpython2.5

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ import my
configchange_callback = setconnect_callback = None
objc_validator = objc.signature('@@:N^@o^@')
def config_changed():
@ -39,7 +40,7 @@ class SshuttleNet(NSObject):
def setSubnet_(self, v):
self._k_subnet = v
config_changed()
@objc.accessor
@objc_validator
def validateSubnet_error_(self, value, error):
#print 'validateSubnet!'
return True, _validate_ip(value), error
@ -49,7 +50,7 @@ class SshuttleNet(NSObject):
def setWidth_(self, v):
self._k_width = v
config_changed()
@objc.accessor
@objc_validator
def validateWidth_error_(self, value, error):
#print 'validateWidth!'
return True, _validate_width(value), error
@ -58,6 +59,9 @@ NET_ALL = 0
NET_AUTO = 1
NET_MANUAL = 2
LAT_BANDWIDTH = 0
LAT_INTERACTIVE = 1
class SshuttleServer(NSObject):
def init(self):
self = super(SshuttleServer, self).init()
@ -93,12 +97,29 @@ class SshuttleServer(NSObject):
return False
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):
return getattr(self, '_k_host', None)
def setHost_(self, v):
self._k_host = v
self.setTitle_(None)
config_changed()
@objc.accessor
@objc_validator
def validateHost_error_(self, value, error):
#print 'validatehost! %r %r %r' % (self, value, error)
while value.startswith('-'):
@ -109,6 +130,7 @@ class SshuttleServer(NSObject):
return getattr(self, '_k_nets', [])
def setNets_(self, v):
self._k_nets = v
self.setTitle_(None)
config_changed()
def netsHidden(self):
#print 'checking netsHidden'
@ -122,6 +144,8 @@ class SshuttleServer(NSObject):
def setAutoNets_(self, v):
self._k_autoNets = v
self.setNetsHidden_(-1)
self.setUseDns_(v == NET_ALL)
self.setTitle_(None)
config_changed()
def autoHosts(self):
@ -129,3 +153,15 @@ class SshuttleServer(NSObject):
def setAutoHosts_(self, v):
self._k_autoHosts = v
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
exec >&2
./debug.app/Contents/MacOS/run
./debug.app/Contents/MacOS/Sshuttle

View File

@ -1,14 +0,0 @@
import os
pid = os.fork()
if pid == 0:
# child
try:
os.setsid()
#os.execvp('sudo', ['sudo', 'SSH_ASKPASS=%s' % os.path.abspath('askpass.py'), 'ssh', 'afterlife', 'ls'])
os.execvp('ssh', ['ssh', 'afterlife', 'ls'])
finally:
os._exit(44)
else:
# parent
os.wait()

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%% *}'"