Support IPv6 for netlink_conntrack

Currently, netlink_conntrack driver does not work with IPv6 - support is
added with this change.

Change-Id: I9f86f936389f8c895345f23ba99b2a7b792e4e0b
This commit is contained in:
Nguyen Phuong An 2017-12-11 17:00:43 +07:00
parent 469593e84d
commit 9ff85d60db
5 changed files with 199 additions and 53 deletions

View File

@ -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}

View File

@ -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):

View File

@ -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]

View File

@ -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)

View File

@ -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()