diff --git a/neutron_fwaas/privileged/netlink_constants.py b/neutron_fwaas/privileged/netlink_constants.py index e1199f284..376150fdc 100644 --- a/neutron_fwaas/privileged/netlink_constants.py +++ b/neutron_fwaas/privileged/netlink_constants.py @@ -78,5 +78,9 @@ NFCT_CB_FAILURE = -1 NFNL_SUBSYS_CTNETLINK = 0 BUFFER = 1024 +# IPv6 address memory buffer +ADDR_BUFFER_6 = 16 +ADDR_BUFFER_4 = 4 IPVERSION_SOCKET = {4: socket.AF_INET, 6: socket.AF_INET6} +IPVERSION_BUFFER = {4: ADDR_BUFFER_4, 6: ADDR_BUFFER_6} diff --git a/neutron_fwaas/privileged/netlink_lib.py b/neutron_fwaas/privileged/netlink_lib.py index bfd01a88f..4e39a5ba6 100644 --- a/neutron_fwaas/privileged/netlink_lib.py +++ b/neutron_fwaas/privileged/netlink_lib.py @@ -56,6 +56,7 @@ DATA_CALLBACK = None ATTR_POSITIONS = { 'icmp': [('type', 6), ('code', 7), ('src', 4), ('dst', 5), ('id', 8)], + 'icmpv6': [('type', 6), ('code', 7), ('src', 4), ('dst', 5), ('id', 8)], 'tcp': [('sport', 7), ('dport', 8), ('src', 5), ('dst', 6)], 'udp': [('sport', 6), ('dport', 7), ('src', 4), ('dst', 5)] } @@ -91,10 +92,10 @@ class ConntrackManager(object): def __init__(self, family_socket=None): self.family_socket = family_socket self.set_functions = { - 'src': {4: nfct.nfct_set_attr_u32, - 6: nfct.nfct_set_attr_u64}, - 'dst': {4: nfct.nfct_set_attr_u32, - 6: nfct.nfct_set_attr_u64}, + 'src': {4: nfct.nfct_set_attr, + 6: nfct.nfct_set_attr}, + 'dst': {4: nfct.nfct_set_attr, + 6: nfct.nfct_set_attr}, 'ipversion': {4: nfct.nfct_set_attr_u8, 6: nfct.nfct_set_attr_u8}, 'protocol': {4: nfct.nfct_set_attr_u8, @@ -109,8 +110,8 @@ class ConntrackManager(object): 6: nfct.nfct_set_attr_u16}, 'dport': {4: nfct.nfct_set_attr_u16, 6: nfct.nfct_set_attr_u16}, } - self.converters = {'src': libc.inet_addr, - 'dst': libc.inet_addr, + self.converters = {'src': str, + 'dst': str, 'ipversion': nl_constants.IPVERSION_SOCKET.get, 'protocol': constants.IP_PROTOCOL_MAP.get, 'code': int, @@ -164,12 +165,22 @@ class ConntrackManager(object): if result == nl_constants.NFCT_CB_FAILURE: LOG.warning("Netlink query failed") + def _convert_text_to_binary(self, source, addr_family): + dest = ctypes.create_string_buffer( + nl_constants.IPVERSION_BUFFER[addr_family]) + libc.inet_pton(nl_constants.IPVERSION_SOCKET[addr_family], + source, dest) + return dest.raw + def _set_attributes(self, conntrack, entry): ipversion = entry.get('ipversion', 4) for attr, value in entry.items(): set_function = self.set_functions[attr][ipversion] target = TARGET[attr][ipversion] converter = self.converters[attr] + if attr in ['src', 'dst']: + # convert src and dst of IPv4 and IPv6 into same format + value = self._convert_text_to_binary(value, ipversion) set_function(conntrack, target, converter(value)) def _callback_register(self, message_type, callback_func, data): diff --git a/neutron_fwaas/services/firewall/drivers/linux/netlink_conntrack.py b/neutron_fwaas/services/firewall/drivers/linux/netlink_conntrack.py index 5615ef1db..7058a9e01 100644 --- a/neutron_fwaas/services/firewall/drivers/linux/netlink_conntrack.py +++ b/neutron_fwaas/services/firewall/drivers/linux/netlink_conntrack.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron_lib import constants from oslo_log import log as logging from neutron_fwaas.privileged import netlink_lib as nl_lib @@ -110,16 +111,21 @@ class ConntrackNetlink(conntrack_base.ConntrackDriverBase): ENTRY_MATCHES = 0 ENTRY_IS_HIGHER = 1 rule_ipversion = rule_filter[0] + if entry[0] < rule_ipversion: return ENTRY_IS_LOWER elif entry[0] > rule_ipversion: return ENTRY_IS_HIGHER rule_protocol = rule_filter[1] + if rule_protocol: + if rule_protocol == constants.PROTO_NAME_IPV6_ICMP: + rule_protocol = constants.PROTO_NAME_IPV6_ICMP_LEGACY if entry[1] < rule_protocol: return ENTRY_IS_LOWER elif entry[1] > rule_protocol: return ENTRY_IS_HIGHER + sport_range = rule_filter[2] if sport_range: sport_range = [int(port) for port in sport_range] diff --git a/neutron_fwaas/tests/functional/privileged/test_netlink_lib.py b/neutron_fwaas/tests/functional/privileged/test_netlink_lib.py index 2211f7983..90024d479 100644 --- a/neutron_fwaas/tests/functional/privileged/test_netlink_lib.py +++ b/neutron_fwaas/tests/functional/privileged/test_netlink_lib.py @@ -16,24 +16,34 @@ from neutron.agent.linux import utils as linux_utils from neutron.tests.common import net_helpers from neutron.tests.functional import base as functional_base +from oslo_log import log as logging import neutron_fwaas.privileged.netlink_lib as nl_lib +LOG = logging.getLogger(__name__) -CONNTRACK_CMDS = ( - ['conntrack', '-I', '-p', 'tcp', - '-s', '1.1.1.1', '-d', '2.2.2.2', - '--sport', '1', '--dport', '2', - '--state', 'ESTABLISHED', '--timeout', '1234'], - ['conntrack', '-I', '-p', 'udp', - '-s', '1.1.1.1', '-d', '2.2.2.2', - '--sport', '1', '--dport', '2', - '--timeout', '1234'], - ['conntrack', '-I', '-p', 'icmp', - '-s', '1.1.1.1', '-d', '2.2.2.2', - '--icmp-type', '8', '--icmp-code', '0', '--icmp-id', '3333', - '--timeout', '1234'], -) + +def check_nf_conntrack_ipv6_is_loaded(): + try: + output = linux_utils.execute(['lsmod']) + except RuntimeError: + msg = "Failed execute command lsmod!" + raise RuntimeError(msg) + if 'nf_conntrack_ipv6' in output: + return True + return False + + +def _create_entries(namespace, conntrack_cmds): + for cmd in conntrack_cmds: + exec_cmd = ['ip', 'netns', 'exec', namespace] + cmd + try: + linux_utils.execute(exec_cmd, + run_as_root=True, + check_exit_code=True, + extra_ok_codes=[1]) + except RuntimeError: + raise Exception('Error while creating entry') class NetlinkLibTestCase(functional_base.BaseSudoTestCase): @@ -45,20 +55,28 @@ class NetlinkLibTestCase(functional_base.BaseSudoTestCase): as expected. """ - def _create_entries(self, namespace, conntrack_cmds): - for cmd in conntrack_cmds: - exec_cmd = ['ip', 'netns', 'exec', namespace] + cmd - try: - linux_utils.execute(exec_cmd, - run_as_root=True, - check_exit_code=True, - extra_ok_codes=[1]) - except RuntimeError: - raise Exception('Error while creating entry') + CONNTRACK_CMDS = ( + ['conntrack', '-I', '-p', 'tcp', + '-s', '1.1.1.1', '-d', '2.2.2.2', + '--sport', '1', '--dport', '2', + '--state', 'ESTABLISHED', '--timeout', '1234'], + ['conntrack', '-I', '-p', 'udp', + '-s', '1.1.1.1', '-d', '2.2.2.2', + '--sport', '1', '--dport', '2', + '--timeout', '1234'], + ['conntrack', '-I', '-p', 'icmp', + '-s', '1.1.1.1', '-d', '2.2.2.2', + '--icmp-type', '8', '--icmp-code', '0', '--icmp-id', '3333', + '--timeout', '1234'], + ['conntrack', '-I', '-p', 'icmp', + '-s', '1.1.1.1', '-d', '2.2.2.2', + '--icmp-type', '8', '--icmp-code', '0', '--icmp-id', '3333', + '--timeout', '1234'], + ) def test_list_entries(self): namespace = self.useFixture(net_helpers.NamespaceFixture()).name - self._create_entries(namespace, CONNTRACK_CMDS) + _create_entries(namespace, self.CONNTRACK_CMDS) expected = ( (4, 'icmp', 8, 0, '1.1.1.1', '2.2.2.2', 3333), (4, 'tcp', 1, 2, '1.1.1.1', '2.2.2.2'), @@ -69,7 +87,7 @@ class NetlinkLibTestCase(functional_base.BaseSudoTestCase): def _delete_entry(self, delete_entries, remain_entries): namespace = self.useFixture(net_helpers.NamespaceFixture()).name - self._create_entries(namespace, CONNTRACK_CMDS) + _create_entries(namespace, self.CONNTRACK_CMDS) nl_lib.delete_entries(namespace=namespace, entries=delete_entries) entries_list = nl_lib.list_entries(namespace) self.assertEqual(remain_entries, entries_list) @@ -109,7 +127,61 @@ class NetlinkLibTestCase(functional_base.BaseSudoTestCase): def test_flush_entries(self): namespace = self.useFixture(net_helpers.NamespaceFixture()).name - self._create_entries(namespace, CONNTRACK_CMDS) + _create_entries(namespace, self.CONNTRACK_CMDS) + nl_lib.flush_entries(namespace) + entries_list = nl_lib.list_entries(namespace) + self.assertEqual((), entries_list) + + +class NetlinkLibTestCaseIPv6(functional_base.BaseSudoTestCase): + + CONNTRACK_CMDS = ( + ['conntrack', '-I', '-p', 'icmp', + '-s', '1.1.1.1', '-d', '2.2.2.2', + '--icmp-type', '8', '--icmp-code', '0', '--icmp-id', '3333', + '--timeout', '1234'], + ['conntrack', '-I', '-p', 'icmpv6', + '-s', '10::10', '-d', '20::20', + '--icmpv6-type', '128', '--icmpv6-code', '0', '--icmpv6-id', '3456', + '--timeout', '1234'], + ) + + def setUp(self): + super(NetlinkLibTestCaseIPv6, self).setUp() + if not check_nf_conntrack_ipv6_is_loaded(): + self.skipTest( + "nf_conntrack_ipv6 module wasn't loaded. Please load" + "this module into your system if you want to use " + "netlink conntrack with ipv6" + ) + + def test_list_entries(self): + namespace = self.useFixture(net_helpers.NamespaceFixture()).name + _create_entries(namespace, self.CONNTRACK_CMDS) + expected = ( + (4, 'icmp', 8, 0, '1.1.1.1', '2.2.2.2', 3333), + (6, 'icmpv6', 128, 0, '10::10', '20::20', 3456), + ) + entries_list = nl_lib.list_entries(namespace=namespace) + self.assertEqual(expected, entries_list) + + def _delete_entry(self, delete_entries, remain_entries): + namespace = self.useFixture(net_helpers.NamespaceFixture()).name + _create_entries(namespace, self.CONNTRACK_CMDS) + nl_lib.delete_entries(namespace=namespace, entries=delete_entries) + entries_list = nl_lib.list_entries(namespace) + self.assertEqual(remain_entries, entries_list) + + def test_delete_icmpv6_entry(self): + icmp_entry = [(6, 'icmpv6', 128, 0, '10::10', '20::20', 3456)] + remain_entries = ( + (4, 'icmp', 8, 0, '1.1.1.1', '2.2.2.2', 3333), + ) + self._delete_entry(icmp_entry, remain_entries) + + def test_flush_entries(self): + namespace = self.useFixture(net_helpers.NamespaceFixture()).name + _create_entries(namespace, self.CONNTRACK_CMDS) nl_lib.flush_entries(namespace) entries_list = nl_lib.list_entries(namespace) self.assertEqual((), entries_list) diff --git a/neutron_fwaas/tests/unit/privileged/test_netlink_lib.py b/neutron_fwaas/tests/unit/privileged/test_netlink_lib.py index 960c9c34a..f0762e71a 100644 --- a/neutron_fwaas/tests/unit/privileged/test_netlink_lib.py +++ b/neutron_fwaas/tests/unit/privileged/test_netlink_lib.py @@ -32,6 +32,9 @@ FAKE_TCP_ENTRY = {'ipversion': 4, 'protocol': 'tcp', FAKE_UDP_ENTRY = {'ipversion': 4, 'protocol': 'udp', 'sport': 1, 'dport': 2, 'src': '1.1.1.1', 'dst': '2.2.2.2'} +FAKE_ICMPV6_ENTRY = {'ipversion': 6, 'protocol': 'ipv6-icmp', + 'sport': 1, 'dport': 2, 'type': '8', 'code': '0', + 'id': 3456, 'src': '10::10', 'dst': '20::20'} class NetlinkLibTestCase(base.BaseTestCase): @@ -107,13 +110,56 @@ class NetlinkLibTestCase(base.BaseTestCase): calls = [ mock.call(conntrack_filter, nl_constants.ATTR_IPV4_SRC, - nl_lib.libc.inet_addr(FAKE_ENTRY['src'])), + str(conntrack._convert_text_to_binary( + FAKE_ENTRY['src'], 4))), mock.call(conntrack_filter, nl_constants.ATTR_IPV4_DST, - nl_lib.libc.inet_addr(FAKE_ENTRY['dst'])), + str(conntrack._convert_text_to_binary( + FAKE_ENTRY['dst'], 4))), ] - nl_lib.nfct.nfct_set_attr_u32.assert_has_calls(calls, - any_order=True) + nl_lib.nfct.nfct_set_attr.assert_has_calls(calls, any_order=True) + nl_lib.nfct.nfct_destroy.assert_called_once() + nl_lib.nfct.nfct_close.assert_called_once() + + def test_conntrack_delete_icmpv6_entry(self): + conntrack_filter = mock.Mock() + nl_lib.nfct.nfct_new.return_value = conntrack_filter + with nl_lib.ConntrackManager() as conntrack: + nl_lib.nfct.nfct_open.assert_called_once() + conntrack.delete_entries([FAKE_ICMPV6_ENTRY]) + calls = [ + mock.call(conntrack_filter, + nl_constants.ATTR_L3PROTO, + nl_constants.IPVERSION_SOCKET[6]), + mock.call(conntrack_filter, + nl_constants.ATTR_L4PROTO, + constants.IP_PROTOCOL_MAP['ipv6-icmp']), + mock.call(conntrack_filter, + nl_constants.ATTR_ICMP_CODE, + int(FAKE_ICMPV6_ENTRY['code'])), + mock.call(conntrack_filter, + nl_constants.ATTR_ICMP_TYPE, + int(FAKE_ICMPV6_ENTRY['type'])) + ] + nl_lib.nfct.nfct_set_attr_u8.assert_has_calls(calls, + any_order=True) + calls = [ + mock.call(conntrack_filter, + nl_constants.ATTR_ICMP_ID, + nl_lib.libc.htons(FAKE_ICMPV6_ENTRY['id'])), + ] + nl_lib.nfct.nfct_set_attr_u16.assert_has_calls(calls) + calls = [ + mock.call(conntrack_filter, + nl_constants.ATTR_IPV6_SRC, + str(conntrack._convert_text_to_binary( + FAKE_ENTRY['src'], 6))), + mock.call(conntrack_filter, + nl_constants.ATTR_IPV6_DST, + str(conntrack._convert_text_to_binary( + FAKE_ENTRY['dst'], 6))), + ] + nl_lib.nfct.nfct_set_attr.assert_has_calls(calls, any_order=True) nl_lib.nfct.nfct_destroy.assert_called_once() nl_lib.nfct.nfct_close.assert_called_once() @@ -146,13 +192,14 @@ class NetlinkLibTestCase(base.BaseTestCase): calls = [ mock.call(conntrack_filter, nl_constants.ATTR_IPV4_SRC, - nl_lib.libc.inet_addr(FAKE_UDP_ENTRY['src'])), + str(conntrack._convert_text_to_binary( + FAKE_UDP_ENTRY['src'], 4))), mock.call(conntrack_filter, nl_constants.ATTR_IPV4_DST, - nl_lib.libc.inet_addr(FAKE_UDP_ENTRY['dst'])), + str(conntrack._convert_text_to_binary( + FAKE_UDP_ENTRY['dst'], 4))), ] - nl_lib.nfct.nfct_set_attr_u32.assert_has_calls(calls, - any_order=True) + nl_lib.nfct.nfct_set_attr.assert_has_calls(calls, any_order=True) nl_lib.nfct.nfct_destroy.assert_called_once() nl_lib.nfct.nfct_close.assert_called_once() @@ -185,13 +232,14 @@ class NetlinkLibTestCase(base.BaseTestCase): calls = [ mock.call(conntrack_filter, nl_constants.ATTR_IPV4_SRC, - nl_lib.libc.inet_addr(FAKE_TCP_ENTRY['src'])), + str(conntrack._convert_text_to_binary( + FAKE_TCP_ENTRY['src'], 4))), mock.call(conntrack_filter, nl_constants.ATTR_IPV4_DST, - nl_lib.libc.inet_addr(FAKE_TCP_ENTRY['dst'])), + str(conntrack._convert_text_to_binary( + FAKE_TCP_ENTRY['dst'], 4))), ] - nl_lib.nfct.nfct_set_attr_u32.assert_has_calls(calls, - any_order=True) + nl_lib.nfct.nfct_set_attr.assert_has_calls(calls, any_order=True) nl_lib.nfct.nfct_destroy.assert_called_once() nl_lib.nfct.nfct_close.assert_called_once() @@ -253,24 +301,29 @@ class NetlinkLibTestCase(base.BaseTestCase): calls = [ mock.call(conntrack_filter, nl_constants.ATTR_IPV4_SRC, - nl_lib.libc.inet_addr(FAKE_TCP_ENTRY['src'])), + str(conntrack._convert_text_to_binary( + FAKE_TCP_ENTRY['src'], 4))), mock.call(conntrack_filter, nl_constants.ATTR_IPV4_DST, - nl_lib.libc.inet_addr(FAKE_TCP_ENTRY['dst'])), + str(conntrack._convert_text_to_binary( + FAKE_TCP_ENTRY['dst'], 4))), mock.call(conntrack_filter, nl_constants.ATTR_IPV4_SRC, - nl_lib.libc.inet_addr(FAKE_UDP_ENTRY['src'])), + str(conntrack._convert_text_to_binary( + FAKE_UDP_ENTRY['src'], 4))), mock.call(conntrack_filter, nl_constants.ATTR_IPV4_DST, - nl_lib.libc.inet_addr(FAKE_UDP_ENTRY['dst'])), + str(conntrack._convert_text_to_binary( + FAKE_UDP_ENTRY['dst'], 4))), mock.call(conntrack_filter, nl_constants.ATTR_IPV4_SRC, - nl_lib.libc.inet_addr(FAKE_ENTRY['src'])), + str(conntrack._convert_text_to_binary( + FAKE_ENTRY['src'], 4))), mock.call(conntrack_filter, nl_constants.ATTR_IPV4_DST, - nl_lib.libc.inet_addr(FAKE_ENTRY['dst'])), + str(conntrack._convert_text_to_binary( + FAKE_UDP_ENTRY['dst'], 4))), ] - nl_lib.nfct.nfct_set_attr_u32.assert_has_calls(calls, - any_order=True) + nl_lib.nfct.nfct_set_attr.assert_has_calls(calls, any_order=True) nl_lib.nfct.nfct_destroy.assert_called_once() nl_lib.nfct.nfct_close.assert_called_once()