Remove IPFW support.

This is no longer used by modern MacOSX and not getting tested.

It also required a do_wait() function which was a complication for
sshuttle as a whole.

Can get resurrected if required.
This commit is contained in:
Brian May 2015-12-06 11:33:52 +11:00
parent da4ce19121
commit aaa6062329
7 changed files with 7 additions and 277 deletions

View File

@ -43,17 +43,9 @@ Client side Requirements
| | | * IPv6 TCP + |
| | | * IPv6 UDP + |
+-------+--------+------------+-----------------------------------------------+
| BSD | IPFW | * IPv4 TCP | Your kernel needs to be compiled with |
| | | | `IPFIREWALL_FORWARD` and you need to have ipfw|
| | | | available. |
+-------+--------+------------+-----------------------------------------------+
| MacOS | PF | * IPv4 TCP + You need to have the pfctl command. |
+-------+--------+------------+-----------------------------------------------+
The IPFW method is depreciated. It was originally required for MacOS support,
however is no longer maintained. It is likely to get removed from future
versions of sshuttle.
Server side Requirements
------------------------

View File

@ -119,7 +119,7 @@ H,auto-hosts scan for remote hostnames and update local /etc/hosts
N,auto-nets automatically determine subnets to route
dns capture local DNS requests and forward to the remote DNS server
ns-hosts= capture and forward remote DNS requests to the following servers
method= auto, nat, tproxy, pf or ipfw
method= auto, nat, tproxy or pf
python= path to python interpreter on the remote server
r,remote= ssh hostname (and optional username) of remote sshuttle server
x,exclude= exclude this subnet (can be used more than once)
@ -181,7 +181,7 @@ try:
includes = parse_subnet_file(opt.subnets)
if not opt.method:
method_name = "auto"
elif opt.method in ["auto", "nat", "tproxy", "ipfw", "pf"]:
elif opt.method in ["auto", "nat", "tproxy", "pf"]:
method_name = opt.method
else:
o.fatal("method_name %s not supported" % opt.method)

View File

@ -178,12 +178,11 @@ def main(method_name, syslog):
try:
debug1('firewall manager: setting up.\n')
do_wait = None
nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6]
subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6]
if port_v6 > 0:
debug2('firewall manager: setting up IPv6.\n')
do_wait = method.setup_firewall(
method.setup_firewall(
port_v6, dnsport_v6, nslist_v6,
socket.AF_INET6, subnets_v6, udp)
elif len(subnets_v6) > 0:
@ -193,7 +192,7 @@ def main(method_name, syslog):
subnets_v4 = [i for i in subnets if i[0] == socket.AF_INET]
if port_v4 > 0:
debug2('firewall manager: setting up IPv4.\n')
do_wait = method.setup_firewall(
method.setup_firewall(
port_v4, dnsport_v4, nslist_v4,
socket.AF_INET, subnets_v4, udp)
elif len(subnets_v4) > 0:
@ -213,8 +212,6 @@ def main(method_name, 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 is not None:
do_wait()
line = stdin.readline(128)
if line.startswith('HOST '):
(name, ip) = line[5:].strip().split(',', 1)

View File

@ -86,14 +86,12 @@ def get_method(method_name):
def get_auto_method():
if _program_exists('ipfw'):
method_name = "ipfw"
elif _program_exists('iptables'):
if _program_exists('iptables'):
method_name = "nat"
elif _program_exists('pfctl'):
method_name = "pf"
else:
raise Fatal(
"can't find either ipfw, iptables or pfctl; check your PATH")
"can't find either iptables or pfctl; check your PATH")
return get_method(method_name)

View File

@ -1,237 +0,0 @@
import sys
import select
import socket
import struct
import subprocess as ssubprocess
from sshuttle.helpers import log, debug1, debug3, islocal, \
Fatal, family_to_string
from sshuttle.methods import BaseMethod
# python doesn't have a definition for this
IPPROTO_DIVERT = 254
def ipfw_rule_exists(n):
argv = ['ipfw', 'list']
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE)
found = False
for line in p.stdout:
if line.startswith('%05d ' % n):
if not ('ipttl 42' in line
or ('skipto %d' % (n + 1)) in line
or 'check-state' in line):
log('non-sshuttle ipfw rule: %r\n' % line.strip())
raise Fatal('non-sshuttle ipfw rule #%d already exists!' % n)
found = True
rv = p.wait()
if rv:
raise Fatal('%r returned %d' % (argv, rv))
return found
_oldctls = {}
def _fill_oldctls(prefix):
argv = ['sysctl', prefix]
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE)
for line in p.stdout:
assert(line[-1] == '\n')
(k, v) = line[:-1].split(': ', 1)
_oldctls[k] = v
rv = p.wait()
if rv:
raise Fatal('%r returned %d' % (argv, rv))
if not line:
raise Fatal('%r returned no data' % (argv,))
def _sysctl_set(name, val):
argv = ['sysctl', '-w', '%s=%s' % (name, val)]
debug1('>> %s\n' % ' '.join(argv))
return ssubprocess.call(argv, stdout=open('/dev/null', 'w'))
_changedctls = []
def sysctl_set(name, val, permanent=False):
PREFIX = 'net.inet.ip'
assert(name.startswith(PREFIX + '.'))
val = str(val)
if not _oldctls:
_fill_oldctls(PREFIX)
if not (name in _oldctls):
debug1('>> No such sysctl: %r\n' % name)
return False
oldval = _oldctls[name]
if val != oldval:
rv = _sysctl_set(name, val)
if rv == 0 and permanent:
debug1('>> ...saving permanently in /etc/sysctl.conf\n')
f = open('/etc/sysctl.conf', 'a')
f.write('\n'
'# Added by sshuttle\n'
'%s=%s\n' % (name, val))
f.close()
else:
_changedctls.append(name)
return True
def _udp_unpack(p):
src = (socket.inet_ntoa(p[12:16]), struct.unpack('!H', p[20:22])[0])
dst = (socket.inet_ntoa(p[16:20]), struct.unpack('!H', p[22:24])[0])
return src, dst
def _udp_repack(p, src, dst):
addrs = socket.inet_aton(src[0]) + socket.inet_aton(dst[0])
ports = struct.pack('!HH', src[1], dst[1])
return p[:12] + addrs + ports + p[24:]
_real_dns_server = [None]
def _handle_diversion(divertsock, dnsport):
p, tag = divertsock.recvfrom(4096)
src, dst = _udp_unpack(p)
debug3('got diverted packet from %r to %r\n' % (src, dst))
if dst[1] == 53:
# outgoing DNS
debug3('...packet is a DNS request.\n')
_real_dns_server[0] = dst
dst = ('127.0.0.1', dnsport)
elif src[1] == dnsport:
if islocal(src[0], divertsock.family):
debug3('...packet is a DNS response.\n')
src = _real_dns_server[0]
else:
log('weird?! unexpected divert from %r to %r\n' % (src, dst))
assert(0)
newp = _udp_repack(p, src, dst)
divertsock.sendto(newp, tag)
def ipfw(*args):
argv = ['ipfw', '-q'] + list(args)
debug1('>> %s\n' % ' '.join(argv))
rv = ssubprocess.call(argv)
if rv:
raise Fatal('%r returned %d' % (argv, rv))
class Method(BaseMethod):
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
# IPv6 not supported
if family not in [socket.AF_INET, ]:
raise Exception(
'Address family "%s" unsupported by ipfw method_name'
% family_to_string(family))
if udp:
raise Exception("UDP not supported by ipfw method_name")
sport = str(port)
xsport = str(port + 1)
# cleanup any existing rules
if ipfw_rule_exists(port):
ipfw('delete', sport)
while _changedctls:
name = _changedctls.pop()
oldval = _oldctls[name]
_sysctl_set(name, oldval)
if subnets or dnsport:
sysctl_set('net.inet.ip.fw.enable', 1)
changed = sysctl_set('net.inet.ip.scopedroute', 0, permanent=True)
if changed:
log("\n"
" WARNING: ONE-TIME NETWORK DISRUPTION:\n"
" =====================================\n"
"sshuttle has changed a MacOS kernel setting to work around\n"
"a bug in MacOS 10.6. This will cause your network to drop\n"
"within 5-10 minutes unless you restart your network\n"
"interface (change wireless networks or unplug/plug the\n"
"ethernet port) NOW, then restart sshuttle. The fix is\n"
"permanent; you only have to do this once.\n\n")
sys.exit(1)
ipfw('add', sport, 'check-state', 'ip',
'from', 'any', 'to', 'any')
if subnets:
# create new subnet entries
for f, swidth, sexclude, snet \
in sorted(subnets, key=lambda s: s[1], reverse=True):
if sexclude:
ipfw('add', sport, 'skipto', xsport,
'tcp',
'from', 'any', 'to', '%s/%s' % (snet, swidth))
else:
ipfw('add', sport, 'fwd', '127.0.0.1,%d' % port,
'tcp',
'from', 'any', 'to', '%s/%s' % (snet, swidth),
'not', 'ipttl', '42', 'keep-state', 'setup')
# This part is much crazier than it is on Linux, because MacOS (at
# least 10.6, and probably other versions, and maybe FreeBSD too)
# doesn't correctly fixup the dstip/dstport for UDP packets when it
# puts them through a 'fwd' rule. It also doesn't fixup the
# srcip/srcport in the response packet. In Linux iptables, all that
# happens magically for us, so we just redirect the packets and relax.
#
# On MacOS, we have to fix the ports ourselves. For that, we use a
# 'divert' socket, which receives raw packets and lets us mangle them.
#
# Here's how it works. Let's say the local DNS server is 1.1.1.1:53,
# and the remote DNS server is 2.2.2.2:53, and the local transproxy
# port is 10.0.0.1:12300, and a client machine is making a request from
# 10.0.0.5:9999. We see a packet like this:
# 10.0.0.5:9999 -> 1.1.1.1:53
# Since the destip:port matches one of our local nameservers, it will
# match a 'fwd' rule, thus grabbing it on the local machine. However,
# the local kernel will then see a packet addressed to *:53 and not
# know what to do with it; there's nobody listening on port 53. Thus,
# we divert it, rewriting it into this:
# 10.0.0.5:9999 -> 10.0.0.1:12300
# This gets proxied out to the server, which sends it to 2.2.2.2:53,
# and the answer comes back, and the proxy sends it back out like this:
# 10.0.0.1:12300 -> 10.0.0.5:9999
# But that's wrong! The original machine expected an answer from
# 1.1.1.1:53, so we have to divert the *answer* and rewrite it:
# 1.1.1.1:53 -> 10.0.0.5:9999
#
# See? Easy stuff.
if dnsport:
divertsock = socket.socket(socket.AF_INET, socket.SOCK_RAW,
IPPROTO_DIVERT)
divertsock.bind(('0.0.0.0', port)) # IP field is ignored
for f, ip in [i for i in nslist if i[0] == family]:
# relabel and then catch outgoing DNS requests
ipfw('add', sport, 'divert', sport,
'udp',
'from', 'any', 'to', '%s/32' % ip, '53',
'not', 'ipttl', '42')
# relabel DNS responses
ipfw('add', sport, 'divert', sport,
'udp',
'from', 'any', str(dnsport), 'to', 'any',
'not', 'ipttl', '42')
def do_wait():
while 1:
r, w, x = select.select([sys.stdin, divertsock], [], [])
if divertsock in r:
_handle_diversion(divertsock, dnsport)
if sys.stdin in r:
return
else:
do_wait = None
return do_wait

View File

@ -227,7 +227,7 @@ conflicts between client and server.
Unlike most VPNs, sshuttle forwards sessions, not packets.
That is, it uses kernel transparent proxying (`iptables
REDIRECT` rules on Linux, or `ipfw fwd` rules on BSD) to
REDIRECT` rules on Linux) to
capture outgoing TCP sessions, then creates entirely
separate TCP sessions out to the original destination at
the other end of the tunnel.
@ -256,24 +256,6 @@ 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.
On MacOS, sshuttle will set the kernel boot flag
net.inet.ip.scopedroute to 0, which interferes with OS X
Internet Sharing and some VPN clients. To reset this flag,
you can remove any reference to net.inet.ip.scopedroute from
/Library/Preferences/SystemConfiguration/com.apple.Boot.plist
and reboot.
# SEE ALSO
`ssh`(1), `python`(1)

View File

@ -97,8 +97,6 @@ def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts):
2,
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
True),
call().setup_firewall()(),
call().setup_firewall()(),
call().setup_firewall(1024, 0, [], 10, [], True),
call().setup_firewall(1025, 0, [], 2, [], True),
]