mirror of
https://github.com/sshuttle/sshuttle.git
synced 2024-11-22 16:03:57 +01:00
Re-introduce ipfw support for sshuttle on FreeBSD with support for --DNS option as well
Sponsored-by: rsync.net
This commit is contained in:
parent
e8ceccc3d5
commit
5e90491344
@ -98,6 +98,8 @@ def get_auto_method():
|
|||||||
method_name = "nat"
|
method_name = "nat"
|
||||||
elif _program_exists('pfctl'):
|
elif _program_exists('pfctl'):
|
||||||
method_name = "pf"
|
method_name = "pf"
|
||||||
|
elif _program_exists('ipfw'):
|
||||||
|
method_name = "ipfw"
|
||||||
else:
|
else:
|
||||||
raise Fatal(
|
raise Fatal(
|
||||||
"can't find either iptables or pfctl; check your PATH")
|
"can't find either iptables or pfctl; check your PATH")
|
||||||
|
268
sshuttle/methods/ipfw.py
Normal file
268
sshuttle/methods/ipfw.py
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import struct
|
||||||
|
import subprocess as ssubprocess
|
||||||
|
from sshuttle.methods import BaseMethod
|
||||||
|
from sshuttle.helpers import log, debug1, debug3, \
|
||||||
|
Fatal, family_to_string
|
||||||
|
|
||||||
|
recvmsg = None
|
||||||
|
try:
|
||||||
|
# try getting recvmsg from python
|
||||||
|
import socket as pythonsocket
|
||||||
|
getattr(pythonsocket.socket, "recvmsg")
|
||||||
|
socket = pythonsocket
|
||||||
|
recvmsg = "python"
|
||||||
|
except AttributeError:
|
||||||
|
# try getting recvmsg from socket_ext library
|
||||||
|
try:
|
||||||
|
import socket_ext
|
||||||
|
getattr(socket_ext.socket, "recvmsg")
|
||||||
|
socket = socket_ext
|
||||||
|
recvmsg = "socket_ext"
|
||||||
|
except ImportError:
|
||||||
|
import socket
|
||||||
|
|
||||||
|
IP_BINDANY = 24
|
||||||
|
IP_RECVDSTADDR = 7
|
||||||
|
SOL_IPV6 = 41
|
||||||
|
IPV6_RECVDSTADDR = 74
|
||||||
|
|
||||||
|
if recvmsg == "python":
|
||||||
|
def recv_udp(listener, bufsize):
|
||||||
|
debug3('Accept UDP python using recvmsg.\n')
|
||||||
|
data, ancdata, msg_flags, srcip = listener.recvmsg(4096, socket.CMSG_SPACE(4))
|
||||||
|
dstip = None
|
||||||
|
family = None
|
||||||
|
for cmsg_level, cmsg_type, cmsg_data in ancdata:
|
||||||
|
if cmsg_level == socket.SOL_IP and cmsg_type == IP_RECVDSTADDR:
|
||||||
|
port = 53
|
||||||
|
ip = socket.inet_ntop(socket.AF_INET, cmsg_data[0:4])
|
||||||
|
dstip = (ip, port)
|
||||||
|
break
|
||||||
|
return (srcip, dstip, data)
|
||||||
|
elif recvmsg == "socket_ext":
|
||||||
|
def recv_udp(listener, bufsize):
|
||||||
|
debug3('Accept UDP using socket_ext recvmsg.\n')
|
||||||
|
srcip, data, adata, flags = listener.recvmsg((bufsize,), socket.CMSG_SPACE(4))
|
||||||
|
dstip = None
|
||||||
|
family = None
|
||||||
|
for a in adata:
|
||||||
|
if a.cmsg_level == socket.SOL_IP and a.cmsg_type == IP_RECVDSTADDR:
|
||||||
|
port = 53
|
||||||
|
ip = socket.inet_ntop(socket.AF_INET, cmsg_data[0:4])
|
||||||
|
dstip = (ip, port)
|
||||||
|
break
|
||||||
|
return (srcip, dstip, data[0])
|
||||||
|
else:
|
||||||
|
def recv_udp(listener, bufsize):
|
||||||
|
debug3('Accept UDP using recvfrom.\n')
|
||||||
|
data, srcip = listener.recvfrom(bufsize)
|
||||||
|
return (srcip, None, data)
|
||||||
|
|
||||||
|
|
||||||
|
def ipfw_rule_exists(n):
|
||||||
|
argv = ['ipfw', 'list']
|
||||||
|
env = {
|
||||||
|
'PATH': os.environ['PATH'],
|
||||||
|
'LC_ALL': "C",
|
||||||
|
}
|
||||||
|
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=env)
|
||||||
|
|
||||||
|
found = False
|
||||||
|
for line in p.stdout:
|
||||||
|
if line.startswith(b'%05d ' % n):
|
||||||
|
if not ('ipttl 42' 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]
|
||||||
|
env = {
|
||||||
|
'PATH': os.environ['PATH'],
|
||||||
|
'LC_ALL': "C",
|
||||||
|
}
|
||||||
|
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=env)
|
||||||
|
for line in p.stdout:
|
||||||
|
line = line.decode()
|
||||||
|
assert(line[-1] == '\n')
|
||||||
|
(k, v) = line[:-1].split(': ', 1)
|
||||||
|
_oldctls[k] = v.strip()
|
||||||
|
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 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))
|
||||||
|
|
||||||
|
|
||||||
|
def ipfw_noexit(*args):
|
||||||
|
argv = ['ipfw', '-q'] + list(args)
|
||||||
|
debug1('>> %s\n' % ' '.join(argv))
|
||||||
|
ssubprocess.call(argv)
|
||||||
|
|
||||||
|
class Method(BaseMethod):
|
||||||
|
|
||||||
|
def get_supported_features(self):
|
||||||
|
result = super(Method, self).get_supported_features()
|
||||||
|
result.ipv6 = False
|
||||||
|
result.udp = False #NOTE: Almost there, kernel patch needed
|
||||||
|
result.dns = True
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_tcp_dstip(self, sock):
|
||||||
|
return sock.getsockname()
|
||||||
|
|
||||||
|
def recv_udp(self, udp_listener, bufsize):
|
||||||
|
srcip, dstip, data = recv_udp(udp_listener, bufsize)
|
||||||
|
if not dstip:
|
||||||
|
debug1(
|
||||||
|
"-- ignored UDP from %r: "
|
||||||
|
"couldn't determine destination IP address\n" % (srcip,))
|
||||||
|
return None
|
||||||
|
return srcip, dstip, data
|
||||||
|
|
||||||
|
def send_udp(self, sock, srcip, dstip, data):
|
||||||
|
if not srcip:
|
||||||
|
debug1(
|
||||||
|
"-- ignored UDP to %r: "
|
||||||
|
"couldn't determine source IP address\n" % (dstip,))
|
||||||
|
return
|
||||||
|
|
||||||
|
#debug3('Sending SRC: %r DST: %r\n' % (srcip, dstip))
|
||||||
|
sender = socket.socket(sock.family, socket.SOCK_DGRAM)
|
||||||
|
sender.setsockopt(socket.SOL_IP, IP_BINDANY, 1)
|
||||||
|
sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||||
|
sender.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
|
||||||
|
sender.bind(srcip)
|
||||||
|
sender.sendto(data,dstip)
|
||||||
|
sender.close()
|
||||||
|
|
||||||
|
def setup_udp_listener(self, udp_listener):
|
||||||
|
if udp_listener.v4 is not None:
|
||||||
|
udp_listener.v4.setsockopt(socket.SOL_IP, IP_RECVDSTADDR, 1)
|
||||||
|
#if udp_listener.v6 is not None:
|
||||||
|
# udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVDSTADDR, 1)
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
#XXX: Any risk from this?
|
||||||
|
ipfw_noexit('delete', '1')
|
||||||
|
|
||||||
|
while _changedctls:
|
||||||
|
name = _changedctls.pop()
|
||||||
|
oldval = _oldctls[name]
|
||||||
|
_sysctl_set(name, oldval)
|
||||||
|
|
||||||
|
if subnets or dnsport:
|
||||||
|
sysctl_set('net.inet.ip.fw.enable', 1)
|
||||||
|
|
||||||
|
ipfw('add', '1', 'check-state', 'ip',
|
||||||
|
'from', 'any', 'to', 'any')
|
||||||
|
|
||||||
|
ipfw('add', '1', 'skipto', '2',
|
||||||
|
'tcp',
|
||||||
|
'from', 'any', 'to', 'table(125)')
|
||||||
|
ipfw('add', '1', 'fwd', '127.0.0.1,%d' % port,
|
||||||
|
'tcp',
|
||||||
|
'from', 'any', 'to', 'table(126)',
|
||||||
|
'not', 'ipttl', '42', 'keep-state', 'setup')
|
||||||
|
|
||||||
|
ipfw_noexit('table', '124', 'flush')
|
||||||
|
dnscount = 0
|
||||||
|
for f, ip in [i for i in nslist if i[0] == family]:
|
||||||
|
ipfw('table', '124', 'add', '%s' % (ip))
|
||||||
|
dnscount += 1
|
||||||
|
if dnscount > 0:
|
||||||
|
ipfw('add', '1', 'fwd', '127.0.0.1,%d' % dnsport,
|
||||||
|
'udp',
|
||||||
|
'from', 'any', 'to', 'table(124)',
|
||||||
|
'not', 'ipttl', '42')
|
||||||
|
"""if udp:
|
||||||
|
ipfw('add', '1', 'skipto', '2',
|
||||||
|
'udp',
|
||||||
|
'from', 'any', 'to', 'table(125)')
|
||||||
|
ipfw('add', '1', 'fwd', '127.0.0.1,%d' % port,
|
||||||
|
'udp',
|
||||||
|
'from', 'any', 'to', 'table(126)',
|
||||||
|
'not', 'ipttl', '42')
|
||||||
|
"""
|
||||||
|
ipfw('add', '1', 'allow',
|
||||||
|
'udp',
|
||||||
|
'from', 'any', 'to', 'any',
|
||||||
|
'ipttl', '42')
|
||||||
|
|
||||||
|
if subnets:
|
||||||
|
# create new subnet entries
|
||||||
|
for f, swidth, sexclude, snet \
|
||||||
|
in sorted(subnets, key=lambda s: s[1], reverse=True):
|
||||||
|
if sexclude:
|
||||||
|
ipfw('table', '125', 'add', '%s/%s' % (snet, swidth))
|
||||||
|
else:
|
||||||
|
ipfw('table', '126', 'add', '%s/%s' % (snet, swidth))
|
||||||
|
|
||||||
|
def restore_firewall(self, port, family, udp):
|
||||||
|
if family not in [socket.AF_INET]:
|
||||||
|
raise Exception(
|
||||||
|
'Address family "%s" unsupported by tproxy method'
|
||||||
|
% family_to_string(family))
|
||||||
|
|
||||||
|
ipfw_noexit('delete', '1')
|
||||||
|
ipfw_noexit('table', '124', 'flush')
|
||||||
|
ipfw_noexit('table', '125', 'flush')
|
||||||
|
ipfw_noexit('table', '126', 'flush')
|
||||||
|
|
@ -162,7 +162,7 @@ parser.add_argument(
|
|||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--method",
|
"--method",
|
||||||
choices=["auto", "nat", "tproxy", "pf"],
|
choices=["auto", "nat", "tproxy", "pf", "ipfw"],
|
||||||
metavar="TYPE",
|
metavar="TYPE",
|
||||||
default="auto",
|
default="auto",
|
||||||
help="""
|
help="""
|
||||||
|
Loading…
Reference in New Issue
Block a user