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.
...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.
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
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.
Instead, get a list of known sysctls in the interesting prefix (net.inet.ip)
and check if there's an entry in the list for each sysctl we want to change.
If there isn't, then don't try to change it.
This fixes a problem with FreeBSD, which doesn't have
net.inet.ip.scopedroute but also doesn't need it. Probably also fixes MacOS
10.5, which probably didn't have that either, but I don't know for sure.
Reported by Ed Maste.
'del' is an abbreviation that happened to work because of substring matching
in earlier versions of ipfw, but apparently they're planning to remove the
substring matching eventually. In any case, 'delete' has always worked, so
there's no downside to using that.
Reported by Ed Maste.
If your machine is a firewall/router, it affects whether people behind the
router can use your sshuttle connection - in the same way that it affects
whether they can route *anything* through you. And thus, it should be set
by the admin, not by sshuttle.
sshuttle works fine for the local user either way.
(This also affects MacOS since it's a BSD variant.)
It turns out 'established' doesn't work the way I expected it to from
iptables; it's not stateful. It just checks the TCP flags to see if the
connection *thinks* it's already established, and follows the rule if so.
That caused the first packet of each new connection to set sent to our
transproxy, but not the subsequent ones, so weird stuff happened.
With this change, any (matching) connection created *after* starting sshuttle
will get forwarded, but pre-existing ones - most importantly, sshuttle's own
ssh connection - will not.
And with this (plus the previous commit), sshuttle works on MacOS, including
10.6!
It comes down to this:
sysctl_set('net.inet.ip.scopedroute', 0)
I say "mostly" because actually it doesn't fix it; sshuttle doesn't know
what to do with the received connection, so there must be a minor bug
remaining somewhere. I'll fix that next.
Thanks to dkf <dfortunato@gmail.com> on the sshuttle mailing list for
suggesting the magic fix. He points at this post in particular:
http://discussions.apple.com/thread.jspa?messageID=11558355�
that gave him the necessary clue.
Also, add 127.0.0.0/8 to the default list of excludes. If you want to route
0/0, you almost certainly *don't* want to route localhost to the remote ssh
server's localhost!
Thanks to Edward for the suggestion.
Pointed out by nisc on github. If people use an unusual umask or have funny
permissions on /etc/hosts, sshuttle would screw it up.
We also use hardlinks to atomically backup the original /etc/hosts to
/etc/hosts.sbak the first time, rather than manually copying it. Not sure
why I didn't think of that before.
Now if you use --auto-hosts (-H), the client will ask the server to spawn a
hostwatcher to add names. That, in turn, will send names back to the
server, which sends them back to the client, which sends them to the
firewall subprocess, which will write them to /etc/hosts. Whew!
Only the firewall process can write to /etc/hosts, of course, because only
he's running as root.
Since the name discovery process is kind of slow, we cache the names in
~/.sshuttle.hosts on the remote server.
Right now, most of the names are discovered using nmblookup and smbclient,
as well as by reading the existing entries in /etc/hosts. What would really
be nice would be to query active directory or mdns somehow... but I don't
really know how those work, so this is what you get for now :) It's pretty
neat, at least.
With this rule, we don't interfere with already-established (or incoming)
connections to routes that we're about to take over. This is what
happens by default in Linux/iptables.
Now if you do
./sshuttle -Nr username@myservername
It'll automatically route the "local" subnets (ie., stuff in the routing
table) from myservername. This is (hopefully a reasonable default setting
for most people.