mirror of
https://github.com/sshuttle/sshuttle.git
synced 2025-01-05 21:48:54 +01:00
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:
parent
da4ce19121
commit
aaa6062329
@ -43,17 +43,9 @@ Client side Requirements
|
|||||||
| | | * IPv6 TCP + |
|
| | | * IPv6 TCP + |
|
||||||
| | | * IPv6 UDP + |
|
| | | * 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. |
|
| 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
|
Server side Requirements
|
||||||
------------------------
|
------------------------
|
||||||
|
@ -119,7 +119,7 @@ H,auto-hosts scan for remote hostnames and update local /etc/hosts
|
|||||||
N,auto-nets automatically determine subnets to route
|
N,auto-nets automatically determine subnets to route
|
||||||
dns capture local DNS requests and forward to the remote DNS server
|
dns capture local DNS requests and forward to the remote DNS server
|
||||||
ns-hosts= capture and forward remote DNS requests to the following servers
|
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
|
python= path to python interpreter on the remote server
|
||||||
r,remote= ssh hostname (and optional username) of remote sshuttle server
|
r,remote= ssh hostname (and optional username) of remote sshuttle server
|
||||||
x,exclude= exclude this subnet (can be used more than once)
|
x,exclude= exclude this subnet (can be used more than once)
|
||||||
@ -181,7 +181,7 @@ try:
|
|||||||
includes = parse_subnet_file(opt.subnets)
|
includes = parse_subnet_file(opt.subnets)
|
||||||
if not opt.method:
|
if not opt.method:
|
||||||
method_name = "auto"
|
method_name = "auto"
|
||||||
elif opt.method in ["auto", "nat", "tproxy", "ipfw", "pf"]:
|
elif opt.method in ["auto", "nat", "tproxy", "pf"]:
|
||||||
method_name = opt.method
|
method_name = opt.method
|
||||||
else:
|
else:
|
||||||
o.fatal("method_name %s not supported" % opt.method)
|
o.fatal("method_name %s not supported" % opt.method)
|
||||||
|
@ -178,12 +178,11 @@ def main(method_name, syslog):
|
|||||||
try:
|
try:
|
||||||
debug1('firewall manager: setting up.\n')
|
debug1('firewall manager: setting up.\n')
|
||||||
|
|
||||||
do_wait = None
|
|
||||||
nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6]
|
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]
|
subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6]
|
||||||
if port_v6 > 0:
|
if port_v6 > 0:
|
||||||
debug2('firewall manager: setting up IPv6.\n')
|
debug2('firewall manager: setting up IPv6.\n')
|
||||||
do_wait = method.setup_firewall(
|
method.setup_firewall(
|
||||||
port_v6, dnsport_v6, nslist_v6,
|
port_v6, dnsport_v6, nslist_v6,
|
||||||
socket.AF_INET6, subnets_v6, udp)
|
socket.AF_INET6, subnets_v6, udp)
|
||||||
elif len(subnets_v6) > 0:
|
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]
|
subnets_v4 = [i for i in subnets if i[0] == socket.AF_INET]
|
||||||
if port_v4 > 0:
|
if port_v4 > 0:
|
||||||
debug2('firewall manager: setting up IPv4.\n')
|
debug2('firewall manager: setting up IPv4.\n')
|
||||||
do_wait = method.setup_firewall(
|
method.setup_firewall(
|
||||||
port_v4, dnsport_v4, nslist_v4,
|
port_v4, dnsport_v4, nslist_v4,
|
||||||
socket.AF_INET, subnets_v4, udp)
|
socket.AF_INET, subnets_v4, udp)
|
||||||
elif len(subnets_v4) > 0:
|
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
|
# to stay running so that we don't need a *second* password
|
||||||
# authentication at shutdown time - that cleanup is important!
|
# authentication at shutdown time - that cleanup is important!
|
||||||
while 1:
|
while 1:
|
||||||
if do_wait is not None:
|
|
||||||
do_wait()
|
|
||||||
line = stdin.readline(128)
|
line = stdin.readline(128)
|
||||||
if line.startswith('HOST '):
|
if line.startswith('HOST '):
|
||||||
(name, ip) = line[5:].strip().split(',', 1)
|
(name, ip) = line[5:].strip().split(',', 1)
|
||||||
|
@ -86,14 +86,12 @@ def get_method(method_name):
|
|||||||
|
|
||||||
|
|
||||||
def get_auto_method():
|
def get_auto_method():
|
||||||
if _program_exists('ipfw'):
|
if _program_exists('iptables'):
|
||||||
method_name = "ipfw"
|
|
||||||
elif _program_exists('iptables'):
|
|
||||||
method_name = "nat"
|
method_name = "nat"
|
||||||
elif _program_exists('pfctl'):
|
elif _program_exists('pfctl'):
|
||||||
method_name = "pf"
|
method_name = "pf"
|
||||||
else:
|
else:
|
||||||
raise Fatal(
|
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)
|
return get_method(method_name)
|
||||||
|
@ -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
|
|
@ -227,7 +227,7 @@ conflicts between client and server.
|
|||||||
|
|
||||||
Unlike most VPNs, sshuttle forwards sessions, not packets.
|
Unlike most VPNs, sshuttle forwards sessions, not packets.
|
||||||
That is, it uses kernel transparent proxying (`iptables
|
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
|
capture outgoing TCP sessions, then creates entirely
|
||||||
separate TCP sessions out to the original destination at
|
separate TCP sessions out to the original destination at
|
||||||
the other end of the tunnel.
|
the other end of the tunnel.
|
||||||
@ -256,24 +256,6 @@ between the two separate streams, so a tcp-based tunnel is
|
|||||||
fine.
|
fine.
|
||||||
|
|
||||||
|
|
||||||
# BUGS
|
|
||||||
|
|
||||||
On MacOS 10.6 (at least up to 10.6.6), your network will
|
|
||||||
stop responding about 10 minutes after the first time you
|
|
||||||
start sshuttle, because of a MacOS kernel bug relating to
|
|
||||||
arp and the net.inet.ip.scopedroute sysctl. To fix it,
|
|
||||||
just switch your wireless off and on. Sshuttle makes the
|
|
||||||
kernel setting it changes permanent, so this won't happen
|
|
||||||
again, even after a reboot.
|
|
||||||
|
|
||||||
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
|
# SEE ALSO
|
||||||
|
|
||||||
`ssh`(1), `python`(1)
|
`ssh`(1), `python`(1)
|
||||||
|
@ -97,8 +97,6 @@ def test_main(mock_get_method, mock_setup_daemon, mock_rewrite_etc_hosts):
|
|||||||
2,
|
2,
|
||||||
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
|
||||||
True),
|
True),
|
||||||
call().setup_firewall()(),
|
|
||||||
call().setup_firewall()(),
|
|
||||||
call().setup_firewall(1024, 0, [], 10, [], True),
|
call().setup_firewall(1024, 0, [], 10, [], True),
|
||||||
call().setup_firewall(1025, 0, [], 2, [], True),
|
call().setup_firewall(1025, 0, [], 2, [], True),
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user