Skip to content

Commit cedc8dc

Browse files
vieirabrianmay
authored andcommitted
Add support for OpenBSD
1 parent e8047ce commit cedc8dc

File tree

2 files changed

+167
-1
lines changed

2 files changed

+167
-1
lines changed

sshuttle/methods/pf.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,70 @@ def add_rules(self, includes, port, dnsport, nslist):
198198
super(FreeBsd, self).add_rules(rules)
199199

200200

201+
class OpenBsd(Generic):
202+
POOL_TICKET_OFFSET = 4
203+
RULE_ACTION_OFFSET = 3324
204+
ANCHOR_CALL_OFFSET = 1036
205+
206+
def __init__(self):
207+
class pfioc_natlook(Structure):
208+
pf_addr = Generic.pf_addr
209+
_fields_ = [("saddr", pf_addr),
210+
("daddr", pf_addr),
211+
("rsaddr", pf_addr),
212+
("rdaddr", pf_addr),
213+
("rdomain", c_uint16),
214+
("rrdomain", c_uint16),
215+
("sxport", c_uint16),
216+
("dxport", c_uint16),
217+
("rsxport", c_uint16),
218+
("rdxport", c_uint16),
219+
("af", c_uint8), # sa_family_t
220+
("proto", c_uint8),
221+
("proto_variant", c_uint8),
222+
("direction", c_uint8)]
223+
224+
self.pfioc_rule = c_char * 3400
225+
self.pfioc_natlook = pfioc_natlook
226+
super(OpenBsd, self).__init__()
227+
228+
def add_anchors(self):
229+
# before adding anchors and rules we must override the skip lo
230+
# that comes by default in openbsd pf.conf so the rules we will add,
231+
# which rely on translating/filtering packets on lo, can work
232+
pfctl('-f /dev/stdin', b'match on lo\n')
233+
super(OpenBsd, self).add_anchors()
234+
235+
def add_rules(self, includes, port, dnsport, nslist):
236+
tables = [
237+
b'table <forward_subnets> {%s}' % b','.join(includes)
238+
]
239+
translating_rules = [
240+
b'pass in on lo0 inet proto tcp '
241+
b'divert-to 127.0.0.1 port %r' % port
242+
]
243+
filtering_rules = [
244+
b'pass out inet proto tcp '
245+
b'to <forward_subnets> route-to lo0 keep state'
246+
]
247+
248+
if len(nslist) > 0:
249+
tables.append(
250+
b'table <dns_servers> {%s}' %
251+
b','.join([ns[1].encode("ASCII") for ns in nslist]))
252+
translating_rules.append(
253+
b'pass in on lo0 inet proto udp to <dns_servers>'
254+
b'port 53 rdr-to 127.0.0.1 port %r' % dnsport)
255+
filtering_rules.append(
256+
b'pass out inet proto udp to '
257+
b'<dns_servers> port 53 route-to lo0 keep state')
258+
259+
rules = b'\n'.join(tables + translating_rules + filtering_rules) \
260+
+ b'\n'
261+
262+
super(OpenBsd, self).add_rules(rules)
263+
264+
201265
class Darwin(FreeBsd):
202266
RULE_ACTION_OFFSET = 3068
203267

@@ -252,6 +316,8 @@ def _get_natlook_port(self, xport):
252316

253317
if sys.platform == 'darwin':
254318
pf = Darwin()
319+
elif sys.platform.startswith('openbsd'):
320+
pf = OpenBsd()
255321
else:
256322
pf = FreeBsd()
257323

sshuttle/tests/test_methods_pf.py

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from sshuttle.methods import get_method
66
from sshuttle.helpers import Fatal
7-
from sshuttle.methods.pf import FreeBsd, Darwin
7+
from sshuttle.methods.pf import FreeBsd, Darwin, OpenBsd
88

99

1010
def test_get_supported_features():
@@ -131,6 +131,29 @@ def test_firewall_command_freebsd(mock_pf_get_dev, mock_ioctl, mock_stdout):
131131
]
132132

133133

134+
@patch('sshuttle.methods.pf.pf', OpenBsd())
135+
@patch('sshuttle.methods.pf.sys.stdout')
136+
@patch('sshuttle.methods.pf.ioctl')
137+
@patch('sshuttle.methods.pf.pf_get_dev')
138+
def test_firewall_command_openbsd(mock_pf_get_dev, mock_ioctl, mock_stdout):
139+
method = get_method('pf')
140+
assert not method.firewall_command("somthing")
141+
142+
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
143+
socket.AF_INET, socket.IPPROTO_TCP,
144+
"127.0.0.1", 1025, "127.0.0.2", 1024)
145+
assert method.firewall_command(command)
146+
147+
assert mock_pf_get_dev.mock_calls == [call()]
148+
assert mock_ioctl.mock_calls == [
149+
call(mock_pf_get_dev(), 0xc0504417, ANY),
150+
]
151+
assert mock_stdout.mock_calls == [
152+
call.write('QUERY_PF_NAT_SUCCESS 0.0.0.0,0\n'),
153+
call.flush(),
154+
]
155+
156+
134157
def pfctl(args, stdin=None):
135158
if args == '-s all':
136159
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):
301324
mock_pf_get_dev.reset_mock()
302325
mock_pfctl.reset_mock()
303326
mock_ioctl.reset_mock()
327+
328+
329+
@patch('sshuttle.helpers.verbose', new=3)
330+
@patch('sshuttle.methods.pf.pf', OpenBsd())
331+
@patch('sshuttle.methods.pf.pfctl')
332+
@patch('sshuttle.methods.pf.ioctl')
333+
@patch('sshuttle.methods.pf.pf_get_dev')
334+
def test_setup_firewall_openbsd(mock_pf_get_dev, mock_ioctl, mock_pfctl):
335+
mock_pfctl.side_effect = pfctl
336+
337+
method = get_method('pf')
338+
assert method.name == 'pf'
339+
340+
with pytest.raises(Exception) as excinfo:
341+
method.setup_firewall(
342+
1024, 1026,
343+
[(10, u'2404:6800:4004:80c::33')],
344+
10,
345+
[(10, 64, False, u'2404:6800:4004:80c::'),
346+
(10, 128, True, u'2404:6800:4004:80c::101f')],
347+
True)
348+
assert str(excinfo.value) \
349+
== 'Address family "AF_INET6" unsupported by pf method_name'
350+
assert mock_pf_get_dev.mock_calls == []
351+
assert mock_ioctl.mock_calls == []
352+
assert mock_pfctl.mock_calls == []
353+
354+
with pytest.raises(Exception) as excinfo:
355+
method.setup_firewall(
356+
1025, 1027,
357+
[(2, u'1.2.3.33')],
358+
2,
359+
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
360+
True)
361+
assert str(excinfo.value) == 'UDP not supported by pf method_name'
362+
assert mock_pf_get_dev.mock_calls == []
363+
assert mock_ioctl.mock_calls == []
364+
assert mock_pfctl.mock_calls == []
365+
366+
method.setup_firewall(
367+
1025, 1027,
368+
[(2, u'1.2.3.33')],
369+
2,
370+
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
371+
False)
372+
assert mock_ioctl.mock_calls == [
373+
call(mock_pf_get_dev(), 0xcd48441a, ANY),
374+
call(mock_pf_get_dev(), 0xcd48441a, ANY),
375+
]
376+
assert mock_pfctl.mock_calls == [
377+
call('-f /dev/stdin', b'match on lo\n'),
378+
call('-s all'),
379+
call('-a sshuttle -f /dev/stdin',
380+
b'table <forward_subnets> {!1.2.3.66/32,1.2.3.0/24}\n'
381+
b'table <dns_servers> {1.2.3.33}\n'
382+
b'pass in on lo0 inet proto tcp divert-to 127.0.0.1 port 1025\n'
383+
b'pass in on lo0 inet proto udp to '
384+
b'<dns_servers>port 53 rdr-to 127.0.0.1 port 1027\n'
385+
b'pass out inet proto tcp to '
386+
b'<forward_subnets> route-to lo0 keep state\n'
387+
b'pass out inet proto udp to '
388+
b'<dns_servers> port 53 route-to lo0 keep state\n'),
389+
call('-e'),
390+
]
391+
mock_pf_get_dev.reset_mock()
392+
mock_ioctl.reset_mock()
393+
mock_pfctl.reset_mock()
394+
395+
method.restore_firewall(1025, 2, False)
396+
assert mock_ioctl.mock_calls == []
397+
assert mock_pfctl.mock_calls == [
398+
call('-a sshuttle -F all'),
399+
call("-d"),
400+
]
401+
mock_pf_get_dev.reset_mock()
402+
mock_pfctl.reset_mock()
403+
mock_ioctl.reset_mock()

0 commit comments

Comments
 (0)