Skip to content

Commit

Permalink
Add support for OpenBSD
Browse files Browse the repository at this point in the history
  • Loading branch information
vieira authored and brianmay committed Mar 2, 2016
1 parent e8047ce commit cedc8dc
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 1 deletion.
66 changes: 66 additions & 0 deletions sshuttle/methods/pf.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,70 @@ def add_rules(self, includes, port, dnsport, nslist):
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 <forward_subnets> {%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 <forward_subnets> route-to lo0 keep state'
]

if len(nslist) > 0:
tables.append(
b'table <dns_servers> {%s}' %
b','.join([ns[1].encode("ASCII") for ns in nslist]))
translating_rules.append(
b'pass in on lo0 inet proto udp to <dns_servers>'
b'port 53 rdr-to 127.0.0.1 port %r' % dnsport)
filtering_rules.append(
b'pass out inet proto udp to '
b'<dns_servers> 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

Expand Down Expand Up @@ -252,6 +316,8 @@ def _get_natlook_port(self, xport):

if sys.platform == 'darwin':
pf = Darwin()
elif sys.platform.startswith('openbsd'):
pf = OpenBsd()
else:
pf = FreeBsd()

Expand Down
102 changes: 101 additions & 1 deletion sshuttle/tests/test_methods_pf.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

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():
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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 <forward_subnets> {!1.2.3.66/32,1.2.3.0/24}\n'
b'table <dns_servers> {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'<dns_servers>port 53 rdr-to 127.0.0.1 port 1027\n'
b'pass out inet proto tcp to '
b'<forward_subnets> route-to lo0 keep state\n'
b'pass out inet proto udp to '
b'<dns_servers> 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()

0 comments on commit cedc8dc

Please sign in to comment.