Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-introduce ipfw support for sshuttle on FreeBSD #130

Merged
merged 1 commit into from Jan 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions sshuttle/methods/__init__.py
Expand Up @@ -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")
Expand Down
268 changes: 268 additions & 0 deletions 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')

2 changes: 1 addition & 1 deletion sshuttle/options.py
Expand Up @@ -162,7 +162,7 @@ def __call__(self, parser, namespace, values, option_string=None):
)
parser.add_argument(
"--method",
choices=["auto", "nat", "tproxy", "pf"],
choices=["auto", "nat", "tproxy", "pf", "ipfw"],
metavar="TYPE",
default="auto",
help="""
Expand Down