From cedc8dc14665eaec78ec994c7fb06398600e15ad Mon Sep 17 00:00:00 2001 From: vieira Date: Wed, 2 Mar 2016 00:26:29 +0000 Subject: [PATCH] Add support for OpenBSD --- sshuttle/methods/pf.py | 66 +++++++++++++++++++ sshuttle/tests/test_methods_pf.py | 102 +++++++++++++++++++++++++++++- 2 files changed, 167 insertions(+), 1 deletion(-) diff --git a/sshuttle/methods/pf.py b/sshuttle/methods/pf.py index e017c12..fc238f8 100644 --- a/sshuttle/methods/pf.py +++ b/sshuttle/methods/pf.py @@ -198,6 +198,70 @@ class FreeBsd(Generic): super(FreeBsd, self).add_rules(rules) +class OpenBsd(Generic): + POOL_TICKET_OFFSET = 4 + RULE_ACTION_OFFSET = 3324 + ANCHOR_CALL_OFFSET = 1036 + + def __init__(self): + class pfioc_natlook(Structure): + pf_addr = Generic.pf_addr + _fields_ = [("saddr", pf_addr), + ("daddr", pf_addr), + ("rsaddr", pf_addr), + ("rdaddr", pf_addr), + ("rdomain", c_uint16), + ("rrdomain", c_uint16), + ("sxport", c_uint16), + ("dxport", c_uint16), + ("rsxport", c_uint16), + ("rdxport", c_uint16), + ("af", c_uint8), # sa_family_t + ("proto", c_uint8), + ("proto_variant", c_uint8), + ("direction", c_uint8)] + + self.pfioc_rule = c_char * 3400 + self.pfioc_natlook = pfioc_natlook + super(OpenBsd, self).__init__() + + def add_anchors(self): + # before adding anchors and rules we must override the skip lo + # that comes by default in openbsd pf.conf so the rules we will add, + # which rely on translating/filtering packets on lo, can work + pfctl('-f /dev/stdin', b'match on lo\n') + super(OpenBsd, self).add_anchors() + + def add_rules(self, includes, port, dnsport, nslist): + tables = [ + b'table {%s}' % b','.join(includes) + ] + translating_rules = [ + b'pass in on lo0 inet proto tcp ' + b'divert-to 127.0.0.1 port %r' % port + ] + filtering_rules = [ + b'pass out inet proto tcp ' + b'to route-to lo0 keep state' + ] + + if len(nslist) > 0: + tables.append( + b'table {%s}' % + b','.join([ns[1].encode("ASCII") for ns in nslist])) + translating_rules.append( + b'pass in on lo0 inet proto udp to ' + b'port 53 rdr-to 127.0.0.1 port %r' % dnsport) + filtering_rules.append( + b'pass out inet proto udp to ' + b' port 53 route-to lo0 keep state') + + rules = b'\n'.join(tables + translating_rules + filtering_rules) \ + + b'\n' + + super(OpenBsd, self).add_rules(rules) + + class Darwin(FreeBsd): RULE_ACTION_OFFSET = 3068 @@ -252,6 +316,8 @@ class Darwin(FreeBsd): if sys.platform == 'darwin': pf = Darwin() +elif sys.platform.startswith('openbsd'): + pf = OpenBsd() else: pf = FreeBsd() diff --git a/sshuttle/tests/test_methods_pf.py b/sshuttle/tests/test_methods_pf.py index b12cdf5..cb7d5f1 100644 --- a/sshuttle/tests/test_methods_pf.py +++ b/sshuttle/tests/test_methods_pf.py @@ -4,7 +4,7 @@ import socket from sshuttle.methods import get_method from sshuttle.helpers import Fatal -from sshuttle.methods.pf import FreeBsd, Darwin +from sshuttle.methods.pf import FreeBsd, Darwin, OpenBsd def test_get_supported_features(): @@ -131,6 +131,29 @@ def test_firewall_command_freebsd(mock_pf_get_dev, mock_ioctl, mock_stdout): ] +@patch('sshuttle.methods.pf.pf', OpenBsd()) +@patch('sshuttle.methods.pf.sys.stdout') +@patch('sshuttle.methods.pf.ioctl') +@patch('sshuttle.methods.pf.pf_get_dev') +def test_firewall_command_openbsd(mock_pf_get_dev, mock_ioctl, mock_stdout): + method = get_method('pf') + assert not method.firewall_command("somthing") + + command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % ( + socket.AF_INET, socket.IPPROTO_TCP, + "127.0.0.1", 1025, "127.0.0.2", 1024) + assert method.firewall_command(command) + + assert mock_pf_get_dev.mock_calls == [call()] + assert mock_ioctl.mock_calls == [ + call(mock_pf_get_dev(), 0xc0504417, ANY), + ] + assert mock_stdout.mock_calls == [ + call.write('QUERY_PF_NAT_SUCCESS 0.0.0.0,0\n'), + call.flush(), + ] + + def pfctl(args, stdin=None): if args == '-s all': return (b'INFO:\nStatus: Disabled\nanother mary had a little lamb\n', @@ -301,3 +324,80 @@ def test_setup_firewall_freebsd(mock_pf_get_dev, mock_ioctl, mock_pfctl): mock_pf_get_dev.reset_mock() mock_pfctl.reset_mock() mock_ioctl.reset_mock() + + +@patch('sshuttle.helpers.verbose', new=3) +@patch('sshuttle.methods.pf.pf', OpenBsd()) +@patch('sshuttle.methods.pf.pfctl') +@patch('sshuttle.methods.pf.ioctl') +@patch('sshuttle.methods.pf.pf_get_dev') +def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl): + mock_pfctl.side_effect = pfctl + + method = get_method('pf') + assert method.name == 'pf' + + with pytest.raises(Exception) as excinfo: + method.setup_firewall( + 1024, 1026, + [(10, u'2404:6800:4004:80c::33')], + 10, + [(10, 64, False, u'2404:6800:4004:80c::'), + (10, 128, True, u'2404:6800:4004:80c::101f')], + True) + assert str(excinfo.value) \ + == 'Address family "AF_INET6" unsupported by pf method_name' + assert mock_pf_get_dev.mock_calls == [] + assert mock_ioctl.mock_calls == [] + assert mock_pfctl.mock_calls == [] + + with pytest.raises(Exception) as excinfo: + method.setup_firewall( + 1025, 1027, + [(2, u'1.2.3.33')], + 2, + [(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')], + True) + assert str(excinfo.value) == 'UDP not supported by pf method_name' + assert mock_pf_get_dev.mock_calls == [] + assert mock_ioctl.mock_calls == [] + assert mock_pfctl.mock_calls == [] + + method.setup_firewall( + 1025, 1027, + [(2, u'1.2.3.33')], + 2, + [(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')], + False) + assert mock_ioctl.mock_calls == [ + call(mock_pf_get_dev(), 0xcd48441a, ANY), + call(mock_pf_get_dev(), 0xcd48441a, ANY), + ] + assert mock_pfctl.mock_calls == [ + call('-f /dev/stdin', b'match on lo\n'), + call('-s all'), + call('-a sshuttle -f /dev/stdin', + b'table {!1.2.3.66/32,1.2.3.0/24}\n' + b'table {1.2.3.33}\n' + b'pass in on lo0 inet proto tcp divert-to 127.0.0.1 port 1025\n' + b'pass in on lo0 inet proto udp to ' + b'port 53 rdr-to 127.0.0.1 port 1027\n' + b'pass out inet proto tcp to ' + b' route-to lo0 keep state\n' + b'pass out inet proto udp to ' + b' port 53 route-to lo0 keep state\n'), + call('-e'), + ] + mock_pf_get_dev.reset_mock() + mock_ioctl.reset_mock() + mock_pfctl.reset_mock() + + method.restore_firewall(1025, 2, False) + assert mock_ioctl.mock_calls == [] + assert mock_pfctl.mock_calls == [ + call('-a sshuttle -F all'), + call("-d"), + ] + mock_pf_get_dev.reset_mock() + mock_pfctl.reset_mock() + mock_ioctl.reset_mock()