Adds support for FreeBSD PF

The PF firewall that is included in the FreeBSD base system does not
have exactly the same data structures as the OSX version. This commit
fixes the offsets and some field types that are also different. Tested
with FreeBSD 10.2 and OSX 10.11.2.
This commit is contained in:
vieira 2015-12-30 02:42:11 +00:00 committed by Brian May
parent e433c599e4
commit 11838d65c2
2 changed files with 136 additions and 90 deletions

View File

@ -25,66 +25,87 @@ def pfctl(args, stdin=None):
return o
_pf_context = {'started_by_sshuttle': False, 'Xtoken': None}
# This are some classes and functions used to support pf in yosemite.
class pf_state_xport(Union):
_fields_ = [("port", c_uint16),
("call_id", c_uint16),
("spi", c_uint32)]
class pf_addr(Structure):
class _pfa(Union):
_fields_ = [("v4", c_uint32), # struct in_addr
("v6", c_uint32 * 4), # struct in6_addr
("addr8", c_uint8 * 16),
("addr16", c_uint16 * 8),
("addr32", c_uint32 * 4)]
_fields_ = [("pfa", _pfa)]
_anonymous_ = ("pfa",)
class pfioc_natlook(Structure):
_fields_ = [("saddr", pf_addr),
("daddr", pf_addr),
("rsaddr", pf_addr),
("rdaddr", pf_addr),
("sxport", pf_state_xport),
("dxport", pf_state_xport),
("rsxport", pf_state_xport),
("rdxport", pf_state_xport),
("af", c_uint8), # sa_family_t
("proto", c_uint8),
("proto_variant", c_uint8),
("direction", c_uint8)]
pfioc_rule = c_char * 3104 # sizeof(struct pfioc_rule)
pfioc_pooladdr = c_char * 1136 # sizeof(struct pfioc_pooladdr)
MAXPATHLEN = 1024
DIOCNATLOOK = ((0x40000000 | 0x80000000) | (
(sizeof(pfioc_natlook) & 0x1fff) << 16) | ((ord('D')) << 8) | (23))
DIOCCHANGERULE = ((0x40000000 | 0x80000000) | (
(sizeof(pfioc_rule) & 0x1fff) << 16) | ((ord('D')) << 8) | (26))
DIOCBEGINADDRS = ((0x40000000 | 0x80000000) | (
(sizeof(pfioc_pooladdr) & 0x1fff) << 16) | ((ord('D')) << 8) | (51))
PF_CHANGE_ADD_TAIL = 2
PF_CHANGE_GET_TICKET = 6
PF_PASS = 0
PF_RDR = 8
PF_OUT = 2
_pf_fd = None
class OsDefs(object):
def __init__(self, platform=None):
if platform is None:
platform = sys.platform
self.platform = platform
# This are some classes and functions used to support pf in yosemite.
if platform == 'darwin':
class pf_state_xport(Union):
_fields_ = [("port", c_uint16),
("call_id", c_uint16),
("spi", c_uint32)]
else:
class pf_state_xport(Union):
_fields_ = [("port", c_uint16),
("call_id", c_uint16)]
class pf_addr(Structure):
class _pfa(Union):
_fields_ = [("v4", c_uint32), # struct in_addr
("v6", c_uint32 * 4), # struct in6_addr
("addr8", c_uint8 * 16),
("addr16", c_uint16 * 8),
("addr32", c_uint32 * 4)]
_fields_ = [("pfa", _pfa)]
_anonymous_ = ("pfa",)
class pfioc_natlook(Structure):
_fields_ = [("saddr", pf_addr),
("daddr", pf_addr),
("rsaddr", pf_addr),
("rdaddr", pf_addr),
("sxport", pf_state_xport),
("dxport", pf_state_xport),
("rsxport", pf_state_xport),
("rdxport", pf_state_xport),
("af", c_uint8), # sa_family_t
("proto", c_uint8),
("proto_variant", c_uint8),
("direction", c_uint8)]
self.pfioc_natlook = pfioc_natlook
# sizeof(struct pfioc_rule)
self.pfioc_rule = c_char * \
(3104 if platform == 'darwin' else 3040)
# sizeof(struct pfioc_pooladdr)
self.pfioc_pooladdr = c_char * 1136
self.MAXPATHLEN = 1024
self.DIOCNATLOOK = (
(0x40000000 | 0x80000000) |
((sizeof(pfioc_natlook) & 0x1fff) << 16) |
((ord('D')) << 8) | (23))
self.DIOCCHANGERULE = (
(0x40000000 | 0x80000000) |
((sizeof(self.pfioc_rule) & 0x1fff) << 16) |
((ord('D')) << 8) | (26))
self.DIOCBEGINADDRS = (
(0x40000000 | 0x80000000) |
((sizeof(self.pfioc_pooladdr) & 0x1fff) << 16) |
((ord('D')) << 8) | (51))
self.PF_CHANGE_ADD_TAIL = 2
self.PF_CHANGE_GET_TICKET = 6
self.PF_PASS = 0
self.PF_RDR = 8
self.PF_OUT = 2
osdefs = OsDefs()
def pf_get_dev():
global _pf_fd
if _pf_fd is None:
@ -103,16 +124,16 @@ def pf_query_nat(family, proto, src_ip, src_port, dst_ip, dst_port):
assert len(packed_src_ip) == len(packed_dst_ip)
length = len(packed_src_ip)
pnl = pfioc_natlook()
pnl = osdefs.pfioc_natlook()
pnl.proto = proto
pnl.direction = PF_OUT
pnl.direction = osdefs.PF_OUT
pnl.af = family
memmove(addressof(pnl.saddr), packed_src_ip, length)
pnl.sxport.port = socket.htons(src_port)
memmove(addressof(pnl.daddr), packed_dst_ip, length)
pnl.sxport.port = socket.htons(src_port)
pnl.dxport.port = socket.htons(dst_port)
ioctl(pf_get_dev(), DIOCNATLOOK,
ioctl(pf_get_dev(), osdefs.DIOCNATLOOK,
(c_char * sizeof(pnl)).from_address(addressof(pnl)))
ip = socket.inet_ntop(
@ -125,26 +146,26 @@ def pf_add_anchor_rule(type, name):
ACTION_OFFSET = 0
POOL_TICKET_OFFSET = 8
ANCHOR_CALL_OFFSET = 1040
RULE_ACTION_OFFSET = 3068
RULE_ACTION_OFFSET = 3068 if osdefs.platform == 'darwin' else 2968
pr = pfioc_rule()
ppa = pfioc_pooladdr()
pr = osdefs.pfioc_rule()
ppa = osdefs.pfioc_pooladdr()
ioctl(pf_get_dev(), DIOCBEGINADDRS, ppa)
ioctl(pf_get_dev(), osdefs.DIOCBEGINADDRS, ppa)
memmove(addressof(pr) + POOL_TICKET_OFFSET, ppa[4:8], 4) # pool_ticket
memmove(addressof(pr) + ANCHOR_CALL_OFFSET, name,
min(MAXPATHLEN, len(name))) # anchor_call = name
min(osdefs.MAXPATHLEN, len(name))) # anchor_call = name
memmove(addressof(pr) + RULE_ACTION_OFFSET,
struct.pack('I', type), 4) # rule.action = type
memmove(addressof(pr) + ACTION_OFFSET, struct.pack(
'I', PF_CHANGE_GET_TICKET), 4) # action = PF_CHANGE_GET_TICKET
ioctl(pf_get_dev(), DIOCCHANGERULE, pr)
'I', osdefs.PF_CHANGE_GET_TICKET), 4) # action = PF_CHANGE_GET_TICKET
ioctl(pf_get_dev(), osdefs.DIOCCHANGERULE, pr)
memmove(addressof(pr) + ACTION_OFFSET, struct.pack(
'I', PF_CHANGE_ADD_TAIL), 4) # action = PF_CHANGE_ADD_TAIL
ioctl(pf_get_dev(), DIOCCHANGERULE, pr)
'I', osdefs.PF_CHANGE_ADD_TAIL), 4) # action = PF_CHANGE_ADD_TAIL
ioctl(pf_get_dev(), osdefs.DIOCCHANGERULE, pr)
class Method(BaseMethod):
@ -220,12 +241,12 @@ class Method(BaseMethod):
pf_status = pfctl('-s all')[0]
if b'\nrdr-anchor "sshuttle" all\n' not in pf_status:
pf_add_anchor_rule(PF_RDR, "sshuttle")
pf_add_anchor_rule(osdefs.PF_RDR, b"sshuttle")
if b'\nanchor "sshuttle" all\n' not in pf_status:
pf_add_anchor_rule(PF_PASS, "sshuttle")
pf_add_anchor_rule(osdefs.PF_PASS, b"sshuttle")
pfctl('-a sshuttle -f /dev/stdin', rules)
if sys.platform == "darwin":
if osdefs.platform == "darwin":
o = pfctl('-E')
_pf_context['Xtoken'] = \
re.search(b'Token : (.+)', o[1]).group(1)
@ -242,7 +263,7 @@ class Method(BaseMethod):
raise Exception("UDP not supported by pf method_name")
pfctl('-a sshuttle -F all')
if sys.platform == "darwin":
if osdefs.platform == "darwin":
if _pf_context['Xtoken'] is not None:
pfctl('-X %s' % _pf_context['Xtoken'].decode("ASCII"))
elif _pf_context['started_by_sshuttle']:

View File

@ -4,6 +4,7 @@ import socket
from sshuttle.methods import get_method
from sshuttle.helpers import Fatal
from sshuttle.methods.pf import OsDefs
def test_get_supported_features():
@ -84,10 +85,11 @@ def test_assert_features():
method.assert_features(features)
@patch('sshuttle.methods.pf.osdefs', OsDefs('darwin'))
@patch('sshuttle.methods.pf.sys.stdout')
@patch('sshuttle.methods.pf.ioctl')
@patch('sshuttle.methods.pf.pf_get_dev')
def test_firewall_command(mock_pf_get_dev, mock_ioctl, mock_stdout):
def test_firewall_command_darwin(mock_pf_get_dev, mock_ioctl, mock_stdout):
method = get_method('pf')
assert not method.firewall_command("somthing")
@ -98,7 +100,30 @@ def test_firewall_command(mock_pf_get_dev, mock_ioctl, mock_stdout):
assert mock_pf_get_dev.mock_calls == [call()]
assert mock_ioctl.mock_calls == [
call(mock_pf_get_dev(), 3226747927, ANY),
call(mock_pf_get_dev(), 0xc0544417, ANY),
]
assert mock_stdout.mock_calls == [
call.write('QUERY_PF_NAT_SUCCESS 0.0.0.0,0\n'),
call.flush(),
]
@patch('sshuttle.methods.pf.osdefs', OsDefs('notdarwin'))
@patch('sshuttle.methods.pf.sys.stdout')
@patch('sshuttle.methods.pf.ioctl')
@patch('sshuttle.methods.pf.pf_get_dev')
def test_firewall_command_notdarwin(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(), 0xc04c4417, ANY),
]
assert mock_stdout.mock_calls == [
call.write('QUERY_PF_NAT_SUCCESS 0.0.0.0,0\n'),
@ -116,7 +141,7 @@ def pfctl(args, stdin=None):
@patch('sshuttle.helpers.verbose', new=3)
@patch('sshuttle.methods.pf.sys.platform', 'darwin')
@patch('sshuttle.methods.pf.osdefs', OsDefs('darwin'))
@patch('sshuttle.methods.pf.pfctl')
@patch('sshuttle.methods.pf.ioctl')
@patch('sshuttle.methods.pf.pf_get_dev')
@ -159,12 +184,12 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
[(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(), 3295691827, ANY),
call(mock_pf_get_dev(), 3424666650, ANY),
call(mock_pf_get_dev(), 3424666650, ANY),
call(mock_pf_get_dev(), 3295691827, ANY),
call(mock_pf_get_dev(), 3424666650, ANY),
call(mock_pf_get_dev(), 3424666650, ANY),
call(mock_pf_get_dev(), 0xC4704433, ANY),
call(mock_pf_get_dev(), 0xCC20441A, ANY),
call(mock_pf_get_dev(), 0xCC20441A, ANY),
call(mock_pf_get_dev(), 0xC4704433, ANY),
call(mock_pf_get_dev(), 0xCC20441A, ANY),
call(mock_pf_get_dev(), 0xCC20441A, ANY),
]
assert mock_pfctl.mock_calls == [
call('-s all'),
@ -197,7 +222,7 @@ def test_setup_firewall_darwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
@patch('sshuttle.helpers.verbose', new=3)
@patch('sshuttle.methods.pf.sys.platform', 'notdarwin')
@patch('sshuttle.methods.pf.osdefs', OsDefs('notdarwin'))
@patch('sshuttle.methods.pf.pfctl')
@patch('sshuttle.methods.pf.ioctl')
@patch('sshuttle.methods.pf.pf_get_dev')
@ -240,12 +265,12 @@ def test_setup_firewall_notdarwin(mock_pf_get_dev, mock_ioctl, mock_pfctl):
[(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(), 3295691827, ANY),
call(mock_pf_get_dev(), 3424666650, ANY),
call(mock_pf_get_dev(), 3424666650, ANY),
call(mock_pf_get_dev(), 3295691827, ANY),
call(mock_pf_get_dev(), 3424666650, ANY),
call(mock_pf_get_dev(), 3424666650, ANY),
call(mock_pf_get_dev(), 0xC4704433, ANY),
call(mock_pf_get_dev(), 0xCBE0441A, ANY),
call(mock_pf_get_dev(), 0xCBE0441A, ANY),
call(mock_pf_get_dev(), 0xC4704433, ANY),
call(mock_pf_get_dev(), 0xCBE0441A, ANY),
call(mock_pf_get_dev(), 0xCBE0441A, ANY),
]
assert mock_pfctl.mock_calls == [
call('-s all'),