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:
parent
dbd0ec757a
commit
9af8f56d1d
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue