summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorErmal Luci <ermal.luci@gmail.com>2017-01-18 09:53:42 -0800
committerBrian May <brian@linuxpenguins.xyz>2017-01-28 11:36:26 +1100
commit5e90491344983230f63455538b7ec6938cbf36ea (patch)
tree6336ee6974d3dc101418dc4d5cb5e0581b640272
parente8ceccc3d5462ec35d134a5ada544e972a2f8fa9 (diff)
Re-introduce ipfw support for sshuttle on FreeBSD with support for --DNS option as well
Sponsored-by: rsync.net
-rw-r--r--sshuttle/methods/__init__.py2
-rw-r--r--sshuttle/methods/ipfw.py268
-rw-r--r--sshuttle/options.py2
3 files changed, 271 insertions, 1 deletions
diff --git a/sshuttle/methods/__init__.py b/sshuttle/methods/__init__.py
index 34699da..8cce0e7 100644
--- a/sshuttle/methods/__init__.py
+++ b/sshuttle/methods/__init__.py
@@ -98,6 +98,8 @@ def get_auto_method():
method_name = "nat"
elif _program_exists('pfctl'):
method_name = "pf"
+ elif _program_exists('ipfw'):
+ method_name = "ipfw"
else:
raise Fatal(
"can't find either iptables or pfctl; check your PATH")
diff --git a/sshuttle/methods/ipfw.py b/sshuttle/methods/ipfw.py
new file mode 100644
index 0000000..97d2ca6
--- /dev/null
+++ b/sshuttle/methods/ipfw.py
@@ -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')
+
diff --git a/sshuttle/options.py b/sshuttle/options.py
index 9eec931..d97d7ae 100644
--- a/sshuttle/options.py
+++ b/sshuttle/options.py
@@ -162,7 +162,7 @@ parser.add_argument(
)
parser.add_argument(
"--method",
- choices=["auto", "nat", "tproxy", "pf"],
+ choices=["auto", "nat", "tproxy", "pf", "ipfw"],
metavar="TYPE",
default="auto",
help="""