Merge "Add netlink-lib to manage conntrack entries"
This commit is contained in:
commit
994947fa37
91
neutron/privileged/agent/linux/netlink_constants.py
Normal file
91
neutron/privileged/agent/linux/netlink_constants.py
Normal file
@ -0,0 +1,91 @@
|
||||
# Copyright (c) 2017 Fujitsu Limited
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Some parts are based on python-conntrack:
|
||||
# Copyright (c) 2009-2011,2015 Andrew Grigorev <andrew@ei-grad.ru>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
|
||||
import socket
|
||||
|
||||
|
||||
CONNTRACK = 0
|
||||
|
||||
NFCT_O_PLAIN = 0
|
||||
|
||||
NFCT_OF_TIME_BIT = 1
|
||||
NFCT_OF_TIME = 1 << NFCT_OF_TIME_BIT
|
||||
|
||||
NFCT_Q_DESTROY = 2
|
||||
NFCT_Q_FLUSH = 4
|
||||
NFCT_Q_DUMP = 5
|
||||
NFCT_T_DESTROY_BIT = 2
|
||||
NFCT_T_DESTROY = 1 << NFCT_T_DESTROY_BIT
|
||||
|
||||
ATTR_IPV4_SRC = 0
|
||||
ATTR_IPV4_DST = 1
|
||||
ATTR_IPV6_SRC = 4
|
||||
ATTR_IPV6_DST = 5
|
||||
ATTR_PORT_SRC = 8
|
||||
ATTR_PORT_DST = 9
|
||||
ATTR_ICMP_TYPE = 12
|
||||
ATTR_ICMP_CODE = 13
|
||||
ATTR_ICMP_ID = 14
|
||||
ATTR_L3PROTO = 15
|
||||
ATTR_L4PROTO = 17
|
||||
ATTR_ZONE = 61
|
||||
|
||||
NFCT_T_NEW_BIT = 0
|
||||
NFCT_T_NEW = 1 << NFCT_T_NEW_BIT
|
||||
NFCT_T_UPDATE_BIT = 1
|
||||
NFCT_T_UPDATE = 1 << NFCT_T_UPDATE_BIT
|
||||
NFCT_T_DESTROY_BIT = 2
|
||||
NFCT_T_DESTROY = 1 << NFCT_T_DESTROY_BIT
|
||||
|
||||
NFCT_T_ALL = NFCT_T_NEW | NFCT_T_UPDATE | NFCT_T_DESTROY
|
||||
|
||||
NFCT_CB_CONTINUE = 1
|
||||
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}
|
||||
|
||||
ENTRY_IS_LOWER = -1
|
||||
ENTRY_MATCHES = 0
|
||||
ENTRY_IS_HIGHER = 1
|
275
neutron/privileged/agent/linux/netlink_lib.py
Normal file
275
neutron/privileged/agent/linux/netlink_lib.py
Normal file
@ -0,0 +1,275 @@
|
||||
# Copyright (c) 2017 Fujitsu Limited
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Some parts are based on python-conntrack:
|
||||
# Copyright (c) 2009-2011,2015 Andrew Grigorev <andrew@ei-grad.ru>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
|
||||
import ctypes
|
||||
from ctypes import util
|
||||
import re
|
||||
|
||||
from neutron_lib import constants
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron._i18n import _, _LW
|
||||
from neutron.common import exceptions
|
||||
from neutron import privileged
|
||||
from neutron.privileged.agent.linux import netlink_constants as nl_constants
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
nfct = ctypes.CDLL(util.find_library('netfilter_conntrack'))
|
||||
libc = ctypes.CDLL(util.find_library('libc.so.6'))
|
||||
|
||||
IP_VERSIONS = [constants.IP_VERSION_4, constants.IP_VERSION_6]
|
||||
DATA_CALLBACK = None
|
||||
|
||||
# position of attribute in raw conntrack entry
|
||||
ATTR_POSITIONS = {
|
||||
'icmp': [('type', 6), ('code', 7), ('src', 4), ('dst', 5), ('id', 8),
|
||||
('zone', 16)],
|
||||
'icmpv6': [('type', 6), ('code', 7), ('src', 4), ('dst', 5), ('id', 8),
|
||||
('zone', 16)],
|
||||
'tcp': [('sport', 7), ('dport', 8), ('src', 5), ('dst', 6), ('zone', 15)],
|
||||
'udp': [('sport', 6), ('dport', 7), ('src', 4), ('dst', 5), ('zone', 14)]
|
||||
}
|
||||
|
||||
TARGET = {'src': {4: nl_constants.ATTR_IPV4_SRC,
|
||||
6: nl_constants.ATTR_IPV6_SRC},
|
||||
'dst': {4: nl_constants.ATTR_IPV4_DST,
|
||||
6: nl_constants.ATTR_IPV6_DST},
|
||||
'ipversion': {4: nl_constants.ATTR_L3PROTO,
|
||||
6: nl_constants.ATTR_L3PROTO},
|
||||
'protocol': {4: nl_constants.ATTR_L4PROTO,
|
||||
6: nl_constants.ATTR_L4PROTO},
|
||||
'code': {4: nl_constants.ATTR_ICMP_CODE,
|
||||
6: nl_constants.ATTR_ICMP_CODE},
|
||||
'type': {4: nl_constants.ATTR_ICMP_TYPE,
|
||||
6: nl_constants.ATTR_ICMP_TYPE},
|
||||
'id': {4: nl_constants.ATTR_ICMP_ID,
|
||||
6: nl_constants.ATTR_ICMP_ID},
|
||||
'sport': {4: nl_constants.ATTR_PORT_SRC,
|
||||
6: nl_constants.ATTR_PORT_SRC},
|
||||
'dport': {4: nl_constants.ATTR_PORT_DST,
|
||||
6: nl_constants.ATTR_PORT_DST},
|
||||
'zone': {4: nl_constants.ATTR_ZONE,
|
||||
6: nl_constants.ATTR_ZONE}
|
||||
}
|
||||
|
||||
NFCT_CALLBACK = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int,
|
||||
ctypes.c_void_p, ctypes.c_void_p)
|
||||
|
||||
|
||||
class ConntrackManager(object):
|
||||
def __init__(self, family_socket=None):
|
||||
self.family_socket = family_socket
|
||||
self.set_functions = {
|
||||
'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,
|
||||
6: nfct.nfct_set_attr_u8},
|
||||
'type': {4: nfct.nfct_set_attr_u8,
|
||||
6: nfct.nfct_set_attr_u8},
|
||||
'code': {4: nfct.nfct_set_attr_u8,
|
||||
6: nfct.nfct_set_attr_u8},
|
||||
'id': {4: nfct.nfct_set_attr_u16,
|
||||
6: nfct.nfct_set_attr_u16},
|
||||
'sport': {4: nfct.nfct_set_attr_u16,
|
||||
6: nfct.nfct_set_attr_u16},
|
||||
'dport': {4: nfct.nfct_set_attr_u16,
|
||||
6: nfct.nfct_set_attr_u16},
|
||||
'zone': {4: nfct.nfct_set_attr_u16,
|
||||
6: nfct.nfct_set_attr_u16}
|
||||
}
|
||||
|
||||
self.converters = {'src': str,
|
||||
'dst': str,
|
||||
'ipversion': nl_constants.IPVERSION_SOCKET.get,
|
||||
'protocol': constants.IP_PROTOCOL_MAP.get,
|
||||
'code': int,
|
||||
'type': int,
|
||||
'id': libc.htons,
|
||||
'sport': libc.htons,
|
||||
'dport': libc.htons,
|
||||
'zone': int
|
||||
}
|
||||
|
||||
def list_entries(self):
|
||||
entries = []
|
||||
raw_entry = ctypes.create_string_buffer(nl_constants.BUFFER)
|
||||
|
||||
@NFCT_CALLBACK
|
||||
def callback(type_, conntrack, data):
|
||||
nfct.nfct_snprintf(raw_entry, nl_constants.BUFFER,
|
||||
conntrack, type_,
|
||||
nl_constants.NFCT_O_PLAIN,
|
||||
nl_constants.NFCT_OF_TIME)
|
||||
entries.append(raw_entry.value)
|
||||
return nl_constants.NFCT_CB_CONTINUE
|
||||
|
||||
self._callback_register(nl_constants.NFCT_T_ALL,
|
||||
callback, DATA_CALLBACK)
|
||||
|
||||
data_ref = self._get_ref(self.family_socket or
|
||||
nl_constants.IPVERSION_SOCKET[4])
|
||||
self._query(nl_constants.NFCT_Q_DUMP, data_ref)
|
||||
return entries
|
||||
|
||||
def delete_entries(self, entries):
|
||||
conntrack = nfct.nfct_new()
|
||||
try:
|
||||
for entry in entries:
|
||||
self._set_attributes(conntrack, entry)
|
||||
self._query(nl_constants.NFCT_Q_DESTROY, conntrack)
|
||||
except Exception as e:
|
||||
msg = _("Failed to delete conntrack entries %s") % e
|
||||
LOG.critical(msg)
|
||||
raise exceptions.CTZoneExhaustedError()
|
||||
finally:
|
||||
nfct.nfct_destroy(conntrack)
|
||||
|
||||
def _query(self, query_type, query_data):
|
||||
result = nfct.nfct_query(self.conntrack_handler, query_type,
|
||||
query_data)
|
||||
if result == nl_constants.NFCT_CB_FAILURE:
|
||||
LOG.warning(_LW("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):
|
||||
nfct.nfct_callback_register(self.conntrack_handler,
|
||||
message_type, callback_func, data)
|
||||
|
||||
def _get_ref(self, data):
|
||||
return ctypes.byref(ctypes.c_int(data))
|
||||
|
||||
def __enter__(self):
|
||||
self.conntrack_handler = nfct.nfct_open(
|
||||
nl_constants.CONNTRACK,
|
||||
nl_constants.NFNL_SUBSYS_CTNETLINK)
|
||||
if not self.conntrack_handler:
|
||||
msg = _("Failed to open new conntrack handler")
|
||||
LOG.critical(msg)
|
||||
raise exceptions.CTZoneExhaustedError()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
nfct.nfct_close(self.conntrack_handler)
|
||||
|
||||
|
||||
def _parse_entry(entry, ipversion, zone):
|
||||
"""Parse entry from text to Python tuple
|
||||
|
||||
:param entry: raw conntrack entry
|
||||
:param ipversion: ip version 4 or 6
|
||||
:return: conntrack entry in Python tuple in format
|
||||
(ipversion, protocol, sport, dport, src_ip, dst_ip, zone)
|
||||
example: (4, 'tcp', '1', '2', '1.1.1.1', '2.2.2.2', 1)
|
||||
The attributes are ordered to be easy to compare with other entries
|
||||
and compare with firewall rule
|
||||
"""
|
||||
protocol = entry[1]
|
||||
parsed_entry = [ipversion, protocol]
|
||||
for attr, position in ATTR_POSITIONS[protocol]:
|
||||
val = entry[position].partition('=')[2]
|
||||
try:
|
||||
parsed_entry.append(int(val))
|
||||
except ValueError:
|
||||
parsed_entry.append(val)
|
||||
parsed_entry[-1] = zone
|
||||
return tuple(parsed_entry)
|
||||
|
||||
|
||||
@privileged.default.entrypoint
|
||||
def list_entries(zone):
|
||||
"""List and parse all conntrack entries in zone
|
||||
|
||||
:param zone: zone in which entries belong to
|
||||
:return: sorted list of conntrack entries in Python tuple with sort key
|
||||
is dest port
|
||||
example: [(4, 'icmp', '8', '0', '1.1.1.1', '2.2.2.2', '1234'),
|
||||
(4, 'tcp', '1', '2', '1.1.1.1', '2.2.2.2')]
|
||||
"""
|
||||
parsed_entries = []
|
||||
for ipversion in IP_VERSIONS:
|
||||
with ConntrackManager(nl_constants.IPVERSION_SOCKET[ipversion]) \
|
||||
as conntrack:
|
||||
raw_entries = [entry for entry in conntrack.list_entries() if
|
||||
re.search(r'\bzone={}\b'.format(zone), entry) is
|
||||
not None]
|
||||
|
||||
for raw_entry in raw_entries:
|
||||
_entry = raw_entry.split()
|
||||
parsed_entry = _parse_entry(_entry, ipversion, zone)
|
||||
parsed_entries.append(parsed_entry)
|
||||
# sort by dest port
|
||||
return sorted(parsed_entries, key=lambda x: x[3])
|
||||
|
||||
|
||||
@privileged.default.entrypoint
|
||||
def delete_entries(entries):
|
||||
"""Delete selected entries
|
||||
|
||||
:param entries: list of parsed (as tuple) entries to delete
|
||||
:return: None
|
||||
"""
|
||||
entry_args = []
|
||||
for entry in entries:
|
||||
entry_arg = {'ipversion': entry[0], 'protocol': entry[1]}
|
||||
for idx, attr in enumerate(ATTR_POSITIONS[entry_arg['protocol']]):
|
||||
entry_arg[attr[0]] = entry[idx + 2]
|
||||
entry_args.append(entry_arg)
|
||||
|
||||
with ConntrackManager() as conntrack:
|
||||
conntrack.delete_entries(entry_args)
|
110
neutron/tests/functional/agent/linux/test_netlink_lib.py
Normal file
110
neutron/tests/functional/agent/linux/test_netlink_lib.py
Normal file
@ -0,0 +1,110 @@
|
||||
# Copyright (c) 2017 Fujitsu Limited
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from neutron.agent.linux import utils as linux_utils
|
||||
from neutron.privileged.agent.linux import netlink_lib as nl_lib
|
||||
from neutron.tests.functional import base as functional_base
|
||||
|
||||
|
||||
class NetlinkLibTestCase(functional_base.BaseSudoTestCase):
|
||||
"""Functional test for netlink_lib: List, delete, flush conntrack entries.
|
||||
|
||||
For each function, first we add a specific namespace, then create real
|
||||
conntrack entries. netlink_lib function will do list, delete and flush
|
||||
these entries. This class will test this netlink_lib function work
|
||||
as expected.
|
||||
"""
|
||||
|
||||
def _create_entries(self, zone):
|
||||
conntrack_cmds = (
|
||||
['conntrack', '-I', '-p', 'tcp',
|
||||
'-s', '1.1.1.1', '-d', '2.2.2.2',
|
||||
'--sport', '1', '--dport', '2',
|
||||
'--state', 'ESTABLISHED', '--timeout', '1234', '-w', zone],
|
||||
['conntrack', '-I', '-p', 'udp',
|
||||
'-s', '1.1.1.1', '-d', '2.2.2.2',
|
||||
'--sport', '4', '--dport', '5',
|
||||
'--timeout', '1234', '-w', zone],
|
||||
['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', '-w', zone],
|
||||
)
|
||||
|
||||
for cmd in conntrack_cmds:
|
||||
try:
|
||||
linux_utils.execute(cmd,
|
||||
run_as_root=True,
|
||||
check_exit_code=True,
|
||||
extra_ok_codes=[1])
|
||||
except RuntimeError:
|
||||
raise Exception('Error while creating entry')
|
||||
|
||||
def _delete_entry(self, delete_entries, remain_entries, zone):
|
||||
nl_lib.delete_entries(entries=delete_entries)
|
||||
entries_list = nl_lib.list_entries(zone=zone)
|
||||
self.assertEqual(remain_entries, entries_list)
|
||||
|
||||
def test_list_entries(self):
|
||||
_zone = 10
|
||||
self._create_entries(zone=_zone)
|
||||
expected = (
|
||||
(4, 'icmp', 8, 0, '1.1.1.1', '2.2.2.2', 3333, _zone),
|
||||
(4, 'tcp', 1, 2, '1.1.1.1', '2.2.2.2', _zone),
|
||||
(4, 'udp', 4, 5, '1.1.1.1', '2.2.2.2', _zone)
|
||||
)
|
||||
entries_list = nl_lib.list_entries(zone=_zone)
|
||||
self.assertEqual(expected, entries_list)
|
||||
|
||||
def test_delete_icmp_entry(self):
|
||||
_zone = 20
|
||||
self._create_entries(zone=_zone)
|
||||
icmp_entry = [(4, 'icmp', 8, 0, '1.1.1.1', '2.2.2.2', 3333, _zone)]
|
||||
remain_entries = (
|
||||
(4, 'tcp', 1, 2, '1.1.1.1', '2.2.2.2', _zone),
|
||||
(4, 'udp', 4, 5, '1.1.1.1', '2.2.2.2', _zone),
|
||||
)
|
||||
self._delete_entry(icmp_entry, remain_entries, _zone)
|
||||
|
||||
def test_delete_tcp_entry(self):
|
||||
_zone = 30
|
||||
self._create_entries(zone=_zone)
|
||||
tcp_entry = [(4, 'tcp', 1, 2, '1.1.1.1', '2.2.2.2', _zone)]
|
||||
remain_entries = (
|
||||
(4, 'icmp', 8, 0, '1.1.1.1', '2.2.2.2', 3333, _zone),
|
||||
(4, 'udp', 4, 5, '1.1.1.1', '2.2.2.2', _zone)
|
||||
)
|
||||
self._delete_entry(tcp_entry, remain_entries, _zone)
|
||||
|
||||
def test_delete_udp_entry(self):
|
||||
_zone = 40
|
||||
self._create_entries(zone=_zone)
|
||||
udp_entry = [(4, 'udp', 4, 5, '1.1.1.1', '2.2.2.2', _zone)]
|
||||
remain_entries = (
|
||||
(4, 'icmp', 8, 0, '1.1.1.1', '2.2.2.2', 3333, _zone),
|
||||
(4, 'tcp', 1, 2, '1.1.1.1', '2.2.2.2', _zone)
|
||||
)
|
||||
self._delete_entry(udp_entry, remain_entries, _zone)
|
||||
|
||||
def test_delete_multiple_entries(self):
|
||||
_zone = 50
|
||||
self._create_entries(zone=_zone)
|
||||
delete_entries = (
|
||||
(4, 'icmp', 8, 0, '1.1.1.1', '2.2.2.2', 3333, _zone),
|
||||
(4, 'tcp', 1, 2, '1.1.1.1', '2.2.2.2', _zone),
|
||||
(4, 'udp', 4, 5, '1.1.1.1', '2.2.2.2', _zone)
|
||||
)
|
||||
remain_entries = ()
|
||||
self._delete_entry(delete_entries, remain_entries, _zone)
|
0
neutron/tests/unit/privileged/__init__.py
Normal file
0
neutron/tests/unit/privileged/__init__.py
Normal file
0
neutron/tests/unit/privileged/agent/__init__.py
Normal file
0
neutron/tests/unit/privileged/agent/__init__.py
Normal file
342
neutron/tests/unit/privileged/agent/linux/test_netlink_lib.py
Normal file
342
neutron/tests/unit/privileged/agent/linux/test_netlink_lib.py
Normal file
@ -0,0 +1,342 @@
|
||||
# Copyright (c) 2017 Fujitsu Limited
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import mock
|
||||
from neutron_lib import constants
|
||||
import testtools
|
||||
|
||||
from neutron.common import exceptions
|
||||
from neutron.privileged.agent.linux import netlink_constants as nl_constants
|
||||
from neutron.privileged.agent.linux import netlink_lib as nl_lib
|
||||
from neutron.tests import base
|
||||
|
||||
FAKE_ICMP_ENTRY = {'ipversion': 4, 'protocol': 'icmp',
|
||||
'type': '8', 'code': '0', 'id': 1234,
|
||||
'src': '1.1.1.1', 'dst': '2.2.2.2', 'zone': 1}
|
||||
FAKE_TCP_ENTRY = {'ipversion': 4, 'protocol': 'tcp',
|
||||
'sport': 1, 'dport': 2,
|
||||
'src': '1.1.1.1', 'dst': '2.2.2.2', 'zone': 1}
|
||||
FAKE_UDP_ENTRY = {'ipversion': 4, 'protocol': 'udp',
|
||||
'sport': 1, 'dport': 2,
|
||||
'src': '1.1.1.1', 'dst': '2.2.2.2', 'zone': 1}
|
||||
|
||||
|
||||
class NetlinkLibTestCase(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(NetlinkLibTestCase, self).setUp()
|
||||
nl_lib.nfct = mock.Mock()
|
||||
nl_lib.libc = mock.Mock()
|
||||
|
||||
def test_open_new_conntrack_handler_failed(self):
|
||||
nl_lib.nfct.nfct_open.return_value = None
|
||||
with testtools.ExpectedException(exceptions.CTZoneExhaustedError):
|
||||
with nl_lib.ConntrackManager():
|
||||
nl_lib.nfct.nfct_open.assert_called_once_with()
|
||||
nl_lib.nfct.nfct_close.assert_not_called()
|
||||
|
||||
def test_open_new_conntrack_handler_pass(self):
|
||||
with nl_lib.ConntrackManager():
|
||||
nl_lib.nfct.nfct_open.assert_called_once_with(
|
||||
nl_constants.CONNTRACK, nl_constants.NFNL_SUBSYS_CTNETLINK)
|
||||
nl_lib.nfct.nfct_close.assert_called_once_with(nl_lib.nfct.nfct_open(
|
||||
nl_constants.CONNTRACK, nl_constants.NFNL_SUBSYS_CTNETLINK))
|
||||
|
||||
def test_conntrack_list_entries(self):
|
||||
with nl_lib.ConntrackManager() as conntrack:
|
||||
|
||||
nl_lib.nfct.nfct_open.assert_called_once_with(
|
||||
nl_constants.CONNTRACK, nl_constants.NFNL_SUBSYS_CTNETLINK)
|
||||
|
||||
conntrack.list_entries()
|
||||
|
||||
nl_lib.nfct.nfct_callback_register.assert_has_calls(
|
||||
[mock.call(nl_lib.nfct.nfct_open(), nl_constants.NFCT_T_ALL,
|
||||
mock.ANY, None)])
|
||||
nl_lib.nfct.nfct_query.assert_called_once_with(
|
||||
nl_lib.nfct.nfct_open(
|
||||
nl_constants.CONNTRACK,
|
||||
nl_constants.NFNL_SUBSYS_CTNETLINK),
|
||||
nl_constants.NFCT_Q_DUMP,
|
||||
mock.ANY)
|
||||
nl_lib.nfct.nfct_close.assert_called_once_with(nl_lib.nfct.nfct_open(
|
||||
nl_constants.CONNTRACK, nl_constants.NFNL_SUBSYS_CTNETLINK))
|
||||
|
||||
def test_conntrack_new_failed(self):
|
||||
nl_lib.nfct.nfct_new.return_value = None
|
||||
with nl_lib.ConntrackManager() as conntrack:
|
||||
nl_lib.nfct.nfct_open.assert_called_once_with(
|
||||
nl_constants.CONNTRACK,
|
||||
nl_constants.NFNL_SUBSYS_CTNETLINK)
|
||||
conntrack.delete_entries([FAKE_ICMP_ENTRY])
|
||||
nl_lib.nfct.nfct_new.assert_called_once_with()
|
||||
nl_lib.nfct.nfct_destroy.assert_called_once_with(None)
|
||||
nl_lib.nfct.nfct_close.assert_called_once_with(nl_lib.nfct.nfct_open(
|
||||
nl_constants.CONNTRACK,
|
||||
nl_constants.NFNL_SUBSYS_CTNETLINK))
|
||||
|
||||
def test_conntrack_delete_icmp_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_with(
|
||||
nl_constants.CONNTRACK,
|
||||
nl_constants.NFNL_SUBSYS_CTNETLINK)
|
||||
conntrack.delete_entries([FAKE_ICMP_ENTRY])
|
||||
calls = [
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_L3PROTO,
|
||||
nl_constants.IPVERSION_SOCKET[4]),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_L4PROTO,
|
||||
constants.IP_PROTOCOL_MAP['icmp']),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_ICMP_CODE,
|
||||
int(FAKE_ICMP_ENTRY['code'])),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_ICMP_TYPE,
|
||||
int(FAKE_ICMP_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_ICMP_ENTRY['id'])),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_ZONE,
|
||||
int(FAKE_ICMP_ENTRY['zone']))
|
||||
]
|
||||
nl_lib.nfct.nfct_set_attr_u16.assert_has_calls(calls,
|
||||
any_order=True)
|
||||
calls = [
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_IPV4_SRC,
|
||||
str(conntrack._convert_text_to_binary(
|
||||
FAKE_ICMP_ENTRY['src'], 4))
|
||||
),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_IPV4_DST,
|
||||
str(conntrack._convert_text_to_binary(
|
||||
FAKE_ICMP_ENTRY['dst'], 4))
|
||||
),
|
||||
]
|
||||
nl_lib.nfct.nfct_set_attr.assert_has_calls(calls, any_order=True)
|
||||
nl_lib.nfct.nfct_destroy.assert_called_once_with(conntrack_filter)
|
||||
nl_lib.nfct.nfct_close.assert_called_once_with(nl_lib.nfct.nfct_open(
|
||||
nl_constants.CONNTRACK,
|
||||
nl_constants.NFNL_SUBSYS_CTNETLINK))
|
||||
|
||||
def test_conntrack_delete_udp_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_with(
|
||||
nl_constants.CONNTRACK,
|
||||
nl_constants.NFNL_SUBSYS_CTNETLINK)
|
||||
conntrack.delete_entries([FAKE_UDP_ENTRY])
|
||||
calls = [
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_L3PROTO,
|
||||
nl_constants.IPVERSION_SOCKET[4]),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_L4PROTO,
|
||||
constants.IP_PROTOCOL_MAP['udp'])
|
||||
]
|
||||
nl_lib.nfct.nfct_set_attr_u8.assert_has_calls(calls,
|
||||
any_order=True)
|
||||
calls = [
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_PORT_SRC,
|
||||
nl_lib.libc.htons(FAKE_UDP_ENTRY['sport'])),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_PORT_DST,
|
||||
nl_lib.libc.htons(FAKE_UDP_ENTRY['dport'])),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_ZONE,
|
||||
int(FAKE_ICMP_ENTRY['zone']))
|
||||
]
|
||||
nl_lib.nfct.nfct_set_attr_u16.assert_has_calls(calls,
|
||||
any_order=True)
|
||||
calls = [
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_IPV4_SRC,
|
||||
str(conntrack._convert_text_to_binary(
|
||||
FAKE_UDP_ENTRY['src'], 4))
|
||||
),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_IPV4_DST,
|
||||
str(conntrack._convert_text_to_binary(
|
||||
FAKE_UDP_ENTRY['dst'], 4))
|
||||
),
|
||||
]
|
||||
nl_lib.nfct.nfct_set_attr.assert_has_calls(calls, any_order=True)
|
||||
nl_lib.nfct.nfct_destroy.assert_called_once_with(conntrack_filter)
|
||||
nl_lib.nfct.nfct_close.assert_called_once_with(nl_lib.nfct.nfct_open(
|
||||
nl_constants.CONNTRACK,
|
||||
nl_constants.NFNL_SUBSYS_CTNETLINK))
|
||||
|
||||
def test_conntrack_delete_tcp_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_with(
|
||||
nl_constants.CONNTRACK,
|
||||
nl_constants.NFNL_SUBSYS_CTNETLINK)
|
||||
conntrack.delete_entries([FAKE_TCP_ENTRY])
|
||||
calls = [
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_L3PROTO,
|
||||
nl_constants.IPVERSION_SOCKET[4]),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_L4PROTO,
|
||||
constants.IP_PROTOCOL_MAP['tcp'])
|
||||
]
|
||||
nl_lib.nfct.nfct_set_attr_u8.assert_has_calls(calls,
|
||||
any_order=True)
|
||||
calls = [
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_PORT_SRC,
|
||||
nl_lib.libc.htons(FAKE_TCP_ENTRY['sport'])),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_PORT_DST,
|
||||
nl_lib.libc.htons(FAKE_TCP_ENTRY['dport'])),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_ZONE,
|
||||
int(FAKE_ICMP_ENTRY['zone']))
|
||||
]
|
||||
nl_lib.nfct.nfct_set_attr_u16.assert_has_calls(calls,
|
||||
any_order=True)
|
||||
calls = [
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_IPV4_SRC,
|
||||
str(conntrack._convert_text_to_binary(
|
||||
FAKE_TCP_ENTRY['src'], 4))
|
||||
),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_IPV4_DST,
|
||||
str(conntrack._convert_text_to_binary(
|
||||
FAKE_TCP_ENTRY['dst'], 4))
|
||||
),
|
||||
]
|
||||
|
||||
nl_lib.nfct.nfct_set_attr.assert_has_calls(calls, any_order=True)
|
||||
nl_lib.nfct.nfct_destroy.assert_called_once_with(conntrack_filter)
|
||||
nl_lib.nfct.nfct_close.assert_called_once_with(nl_lib.nfct.nfct_open(
|
||||
nl_constants.CONNTRACK,
|
||||
nl_constants.NFNL_SUBSYS_CTNETLINK))
|
||||
|
||||
def test_conntrack_delete_entries(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_with(
|
||||
nl_constants.CONNTRACK,
|
||||
nl_constants.NFNL_SUBSYS_CTNETLINK)
|
||||
conntrack.delete_entries([FAKE_ICMP_ENTRY,
|
||||
FAKE_TCP_ENTRY,
|
||||
FAKE_UDP_ENTRY])
|
||||
calls = [
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_L3PROTO,
|
||||
nl_constants.IPVERSION_SOCKET[4]),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_L4PROTO,
|
||||
constants.IP_PROTOCOL_MAP['tcp']),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_L3PROTO,
|
||||
nl_constants.IPVERSION_SOCKET[4]),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_L4PROTO,
|
||||
constants.IP_PROTOCOL_MAP['udp']),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_L3PROTO,
|
||||
nl_constants.IPVERSION_SOCKET[4]),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_L4PROTO,
|
||||
constants.IP_PROTOCOL_MAP['icmp']),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_ICMP_CODE,
|
||||
int(FAKE_ICMP_ENTRY['code'])),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_ICMP_TYPE,
|
||||
int(FAKE_ICMP_ENTRY['type']))
|
||||
]
|
||||
nl_lib.nfct.nfct_set_attr_u8.assert_has_calls(calls,
|
||||
any_order=True)
|
||||
calls = [
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_PORT_SRC,
|
||||
nl_lib.libc.htons(FAKE_TCP_ENTRY['sport'])),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_PORT_DST,
|
||||
nl_lib.libc.htons(FAKE_TCP_ENTRY['dport'])),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_ZONE,
|
||||
int(FAKE_TCP_ENTRY['zone'])),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_PORT_SRC,
|
||||
nl_lib.libc.htons(FAKE_UDP_ENTRY['sport'])),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_PORT_DST,
|
||||
nl_lib.libc.htons(FAKE_UDP_ENTRY['dport'])),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_ZONE,
|
||||
int(FAKE_UDP_ENTRY['zone'])),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_ICMP_ID,
|
||||
nl_lib.libc.htons(FAKE_ICMP_ENTRY['id'])),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_ZONE,
|
||||
int(FAKE_ICMP_ENTRY['zone']))
|
||||
]
|
||||
nl_lib.nfct.nfct_set_attr_u16.assert_has_calls(calls,
|
||||
any_order=True)
|
||||
calls = [
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_IPV4_SRC,
|
||||
str(conntrack._convert_text_to_binary(
|
||||
FAKE_TCP_ENTRY['src'], 4))
|
||||
),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_IPV4_DST,
|
||||
str(conntrack._convert_text_to_binary(
|
||||
FAKE_TCP_ENTRY['dst'], 4))),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_IPV4_SRC,
|
||||
str(conntrack._convert_text_to_binary(
|
||||
FAKE_UDP_ENTRY['src'], 4))
|
||||
),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_IPV4_DST,
|
||||
str(conntrack._convert_text_to_binary(
|
||||
FAKE_UDP_ENTRY['dst'], 4))
|
||||
),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_IPV4_SRC,
|
||||
str(conntrack._convert_text_to_binary(
|
||||
FAKE_ICMP_ENTRY['src'], 4))
|
||||
),
|
||||
mock.call(conntrack_filter,
|
||||
nl_constants.ATTR_IPV4_DST,
|
||||
str(conntrack._convert_text_to_binary(
|
||||
FAKE_ICMP_ENTRY['dst'], 4))
|
||||
),
|
||||
]
|
||||
nl_lib.nfct.nfct_set_attr.assert_has_calls(calls, any_order=True)
|
||||
nl_lib.nfct.nfct_destroy.assert_called_once_with(conntrack_filter)
|
||||
nl_lib.nfct.nfct_close.assert_called_once_with(nl_lib.nfct.nfct_open(
|
||||
nl_constants.CONNTRACK,
|
||||
nl_constants.NFNL_SUBSYS_CTNETLINK))
|
Loading…
Reference in New Issue
Block a user