ovs-fw: Enhance port ranges with masks

The algorithm for masking port range was taken from networking-ovs-dpdk.
Future step will be to move the algorithm to neutron-lib and reuse in
networking-ovs-dpdk.

Change-Id: I4573eac9a2e04c1f126d26591d2e3207b6150337
This commit is contained in:
Jakub Libosvar 2016-02-23 15:52:57 +00:00
parent dbd0ec757a
commit 9af8f56d1d
5 changed files with 163 additions and 23 deletions

View File

@ -15,11 +15,11 @@
import netaddr
from oslo_log import log as logging
import six
from neutron.agent import firewall
from neutron.agent.linux.openvswitch_firewall import constants as ovsfw_consts
from neutron.common import constants
from neutron.common import utils
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants \
as ovs_consts
@ -72,9 +72,7 @@ def create_protocol_flows(direction, flow_template, port, rule):
pass
flows = create_port_range_flows(flow_template, rule)
if not flows:
return [flow_template]
return flows
return flows or [flow_template]
def create_port_range_flows(flow_template, rule):
@ -83,27 +81,31 @@ def create_port_range_flows(flow_template, rule):
return []
flows = []
src_port_match = '{:s}_src'.format(protocol)
#FIXME(jlibosva): Actually source_port_range_min is just a dead code in
# security groups rpc layer and should be removed
src_port_min = rule.get('source_port_range_min')
src_port_max = rule.get('source_port_range_max')
dst_port_match = '{:s}_dst'.format(protocol)
dst_port_min = rule.get('port_range_min')
dst_port_max = rule.get('port_range_max')
dst_port_range = []
if dst_port_min and dst_port_max:
dst_port_range = utils.port_rule_masking(dst_port_min, dst_port_max)
src_port_range = []
if src_port_min and src_port_max:
for port in six.moves.range(src_port_min, src_port_max + 1):
src_port_range = utils.port_rule_masking(src_port_min, src_port_max)
for port in src_port_range:
flow = flow_template.copy()
flow[src_port_match] = port
try:
for port in six.moves.range(dst_port_min, dst_port_max + 1):
if dst_port_range:
for port in dst_port_range:
dst_flow = flow.copy()
dst_flow[dst_port_match] = port
flows.append(dst_flow)
except TypeError:
else:
flows.append(flow)
elif dst_port_min and dst_port_max:
for port in six.moves.range(dst_port_min, dst_port_max + 1):
else:
for port in dst_port_range:
flow = flow_template.copy()
flow[dst_port_match] = port
flows.append(flow)

View File

@ -24,6 +24,7 @@ import decimal
import errno
import functools
import hashlib
import math
import multiprocessing
import os
import random
@ -51,6 +52,8 @@ from neutron.common import constants as n_const
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
LOG = logging.getLogger(__name__)
SYNCHRONIZED_PREFIX = 'neutron-'
# Unsigned 16 bit MAX.
MAX_UINT16 = 0xffff
synchronized = lockutils.synchronized_with_prefix(SYNCHRONIZED_PREFIX)
@ -544,3 +547,101 @@ def safe_decode_utf8(s):
if six.PY3 and isinstance(s, bytes):
return s.decode('utf-8', 'surrogateescape')
return s
#TODO(jlibosva): Move this to neutron-lib and reuse in networking-ovs-dpdk
def _create_mask(lsb_mask):
return (MAX_UINT16 << int(math.floor(math.log(lsb_mask, 2)))) \
& MAX_UINT16
def _reduce_mask(mask, step=1):
mask <<= step
return mask & MAX_UINT16
def _increase_mask(mask, step=1):
for index in range(step):
mask >>= 1
mask |= 0x8000
return mask
def _hex_format(number):
return format(number, '#06x')
def port_rule_masking(port_min, port_max):
# Check port_max >= port_min.
if port_max < port_min:
raise ValueError(_("'port_max' is smaller than 'port_min'"))
# Rules to be added to OVS.
rules = []
# Loop from the lower part. Increment port_min.
bit_right = 1
mask = MAX_UINT16
t_port_min = port_min
while True:
# Obtain last significative bit.
bit_min = port_min & bit_right
# Take care of first bit.
if bit_right == 1:
if bit_min > 0:
rules.append("%s" % (_hex_format(t_port_min)))
else:
mask = _create_mask(2)
rules.append("%s/%s" % (_hex_format(t_port_min & mask),
_hex_format(mask)))
elif bit_min == 0:
mask = _create_mask(bit_right)
t_port_min += bit_right
# If the temporal variable we are using exceeds the
# port_max value, exit the loop.
if t_port_min > port_max:
break
rules.append("%s/%s" % (_hex_format(t_port_min & mask),
_hex_format(mask)))
# If the temporal variable we are using exceeds the
# port_max value, exit the loop.
if t_port_min > port_max:
break
bit_right <<= 1
# Loop from the higher part.
bit_position = int(round(math.log(port_max, 2)))
bit_left = 1 << bit_position
mask = MAX_UINT16
mask = _reduce_mask(mask, bit_position)
# Find the most significative bit of port_max, higher
# than the most significative bit of port_min.
while mask < MAX_UINT16:
bit_max = port_max & bit_left
bit_min = port_min & bit_left
if bit_max > bit_min:
# Difference found.
break
# Rotate bit_left to the right and increase mask.
bit_left >>= 1
mask = _increase_mask(mask)
while bit_left > 1:
# Obtain next most significative bit.
bit_left >>= 1
bit_max = port_max & bit_left
if bit_left == 1:
if bit_max == 0:
rules.append("%s" % (_hex_format(port_max)))
else:
mask = _create_mask(2)
rules.append("%s/%s" % (_hex_format(port_max & mask),
_hex_format(mask)))
elif bit_max > 0:
t_port_max = port_max - bit_max
mask = _create_mask(bit_left)
rules.append("%s/%s" % (_hex_format(t_port_max),
_hex_format(mask)))
return rules

View File

@ -336,7 +336,7 @@ class TestOVSFirewallDriver(base.BaseTestCase):
priority=70,
reg5=self.port_ofport,
table=ovs_consts.RULES_INGRESS_TABLE,
tcp_dst=123)
tcp_dst='0x007b')
calls = self.mock_bridge.br.add_flow.call_args_list
for call in exp_ingress_classifier, exp_egress_classifier, filter_rule:
self.assertIn(call, calls)

View File

@ -166,8 +166,8 @@ class TestCreateProtocolFlows(base.BaseTestCase):
'actions': 'resubmit(,{:d})'.format(
ovs_consts.ACCEPT_OR_INGRESS_TABLE),
'nw_proto': constants.PROTO_NUM_TCP,
'tcp_dst': port
} for port in range(22, 24)]
'tcp_dst': '0x0016/0xfffe'
}]
self._test_create_protocol_flows_helper(
firewall.EGRESS_DIRECTION, rule, expected_flows)
@ -189,10 +189,8 @@ class TestCreatePortRangeFlows(base.BaseTestCase):
'port_range_max': 11,
}
expected_flows = [
{'tcp_src': 123, 'tcp_dst': 10},
{'tcp_src': 123, 'tcp_dst': 11},
{'tcp_src': 124, 'tcp_dst': 10},
{'tcp_src': 124, 'tcp_dst': 11},
{'tcp_src': '0x007b', 'tcp_dst': '0x000a/0xfffe'},
{'tcp_src': '0x007c', 'tcp_dst': '0x000a/0xfffe'},
]
self._test_create_port_range_flows_helper(expected_flows, rule)
@ -203,8 +201,8 @@ class TestCreatePortRangeFlows(base.BaseTestCase):
'source_port_range_max': 124,
}
expected_flows = [
{'tcp_src': 123},
{'tcp_src': 124},
{'tcp_src': '0x007b'},
{'tcp_src': '0x007c'},
]
self._test_create_port_range_flows_helper(expected_flows, rule)
@ -215,8 +213,7 @@ class TestCreatePortRangeFlows(base.BaseTestCase):
'port_range_max': 11,
}
expected_flows = [
{'tcp_dst': 10},
{'tcp_dst': 11},
{'tcp_dst': '0x000a/0xfffe'},
]
self._test_create_port_range_flows_helper(expected_flows, rule)

View File

@ -738,3 +738,43 @@ class TestSafeDecodeUtf8(base.BaseTestCase):
s = bytes('test-py2', 'utf_16')
decoded_str = utils.safe_decode_utf8(s)
self.assertIsInstance(decoded_str, six.text_type)
class TestPortRuleMasking(base.BaseTestCase):
def test_port_rule_masking(self):
compare_rules = lambda x, y: set(x) == set(y) and len(x) == len(y)
# Test 1.
port_min = 5
port_max = 12
expected_rules = ['0x0005', '0x000c', '0x0006/0xfffe',
'0x0008/0xfffc']
rules = utils.port_rule_masking(port_min, port_max)
self.assertTrue(compare_rules(rules, expected_rules))
# Test 2.
port_min = 20
port_max = 130
expected_rules = ['0x0014/0xfffe', '0x0016/0xfffe', '0x0018/0xfff8',
'0x0020/0xffe0', '0x0040/0xffc0', '0x0080/0xfffe',
'0x0082']
rules = utils.port_rule_masking(port_min, port_max)
self.assertEqual(expected_rules, rules)
# Test 3.
port_min = 4501
port_max = 33057
expected_rules = ['0x1195', '0x1196/0xfffe', '0x1198/0xfff8',
'0x11a0/0xffe0', '0x11c0/0xffc0', '0x1200/0xfe00',
'0x1400/0xfc00', '0x1800/0xf800', '0x2000/0xe000',
'0x4000/0xc000', '0x8021/0xff00', '0x8101/0xffe0',
'0x8120/0xfffe']
rules = utils.port_rule_masking(port_min, port_max)
self.assertEqual(expected_rules, rules)
def test_port_rule_masking_min_higher_than_max(self):
port_min = 10
port_max = 5
with testtools.ExpectedException(ValueError):
utils.port_rule_masking(port_min, port_max)