Netlink solution to improve FWaaS performance

When the Firewall is updated, the conntrack entries will be deleted
by conntrack-tools with each rule associated with each firewall rules.
In large scale system, updating so much rules will call a large number
of subprocesses to implement the "conntrack -D" commands. That will
consume the system resource and time.

This patch proves of using Netlink to delete conntrack entries when updating
firewall-rules.

Using Netlink will save about 90 percent of time that used by conntrack-tools.

For detail information, visit: https://goo.gl/3tm9Fx

Change-Id: I5babfd02090547ad886552201f843fa34761ce8a
Co-Authored-By: Cao Xuan Hoang <hoangcx@vn.fujitsu.com>
This commit is contained in:
Ha Van Tu 2016-10-21 15:24:40 +07:00
parent c3310516d7
commit 6d5afd1a6f
8 changed files with 691 additions and 86 deletions

View File

@ -4,4 +4,7 @@
[Filters]
privsep-rootwrap: PathFilter, privsep-helper, root, privsep-helper, --config-file, /etc/(?!\.\.).*, --privsep_context, neutron_fwaas.privileged.default
privsep: PathFilter, privsep-helper, root,
--config-file, /etc,
--privsep_context, neutron_fwaas.privileged.default,
--privsep_sock_path, /

View File

@ -0,0 +1,268 @@
# 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.
#
"""
Conntrack - A simple python interface to libnetfilter_conntrack using ctypes.
"""
NFCT_OF_SHOW_LAYER3_BIT = 0
NFCT_OF_SHOW_LAYER3 = (1 << NFCT_OF_SHOW_LAYER3_BIT)
NFCT_OF_TIME_BIT = 1
NFCT_OF_TIME = (1 << NFCT_OF_TIME_BIT)
NFCT_OF_ID_BIT = 2
NFCT_OF_ID = (1 << NFCT_OF_ID_BIT)
CONNTRACK = 0
# Callback return codes
NFCT_CB_FAILURE = -1 # failure
NFCT_CB_STOP = 0 # stop the query
NFCT_CB_CONTINUE = 1 # keep iterating through data
NFCT_CB_STOLEN = 2 # like continue, but ct is not freed
# Queries
NFCT_Q_CREATE = 0
NFCT_Q_UPDATE = 1
NFCT_Q_DESTROY = 2
NFCT_Q_GET = 3
NFCT_Q_FLUSH = 4
NFCT_Q_DUMP = 5
NFCT_Q_DUMP_RESET = 6
NFCT_Q_CREATE_UPDATE = 7
NFCT_Q_DUMP_FILTER = 8
NFCT_Q_DUMP_FILTER_RESET = 9
# Message types
NFCT_T_UNKNOWN = 0
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_T_ERROR_BIT = 31
NFCT_T_ERROR = (1 << NFCT_T_ERROR_BIT)
# Attributes
ATTR_ORIG_IPV4_SRC = 0 # u32 bits
ATTR_IPV4_SRC = ATTR_ORIG_IPV4_SRC # alias
ATTR_ORIG_IPV4_DST = 1 # u32 bits
ATTR_IPV4_DST = ATTR_ORIG_IPV4_DST # alias
ATTR_REPL_IPV4_SRC = 2 # u32 bits
ATTR_REPL_IPV4_DST = 3 # u32 bits
ATTR_ORIG_IPV6_SRC = 4 # u128 bits
ATTR_IPV6_SRC = ATTR_ORIG_IPV6_SRC # alias
ATTR_ORIG_IPV6_DST = 5 # u128 bits
ATTR_IPV6_DST = ATTR_ORIG_IPV6_DST # alias
ATTR_REPL_IPV6_SRC = 6 # u128 bits
ATTR_REPL_IPV6_DST = 7 # u128 bits
ATTR_ORIG_PORT_SRC = 8 # u16 bits
ATTR_PORT_SRC = ATTR_ORIG_PORT_SRC # alias
ATTR_ORIG_PORT_DST = 9 # u16 bits
ATTR_PORT_DST = ATTR_ORIG_PORT_DST # alias
ATTR_REPL_PORT_SRC = 10 # u16 bits
ATTR_REPL_PORT_DST = 11 # u16 bits
ATTR_ICMP_TYPE = 12 # u8 bits
ATTR_ICMP_CODE = 13 # u8 bits
ATTR_ICMP_ID = 14 # u16 bits
ATTR_ORIG_L3PROTO = 15 # u8 bits
ATTR_L3PROTO = ATTR_ORIG_L3PROTO # alias
ATTR_REPL_L3PROTO = 16 # u8 bits
ATTR_ORIG_L4PROTO = 17 # u8 bits
ATTR_L4PROTO = ATTR_ORIG_L4PROTO # alias
ATTR_REPL_L4PROTO = 18 # u8 bits
ATTR_TCP_STATE = 19 # u8 bits
ATTR_SNAT_IPV4 = 20 # u32 bits
ATTR_DNAT_IPV4 = 21 # u32 bits
ATTR_SNAT_PORT = 22 # u16 bits
ATTR_DNAT_PORT = 23 # u16 bits
ATTR_TIMEOUT = 24 # u32 bits
ATTR_MARK = 25 # u32 bits
ATTR_ORIG_COUNTER_PACKETS = 26 # u32 bits
ATTR_REPL_COUNTER_PACKETS = 27 # u32 bits
ATTR_ORIG_COUNTER_BYTES = 28 # u32 bits
ATTR_REPL_COUNTER_BYTES = 29 # u32 bits
ATTR_USE = 30 # u32 bits
ATTR_ID = 31 # u32 bits
ATTR_STATUS = 32 # u32 bits
ATTR_TCP_FLAGS_ORIG = 33 # u8 bits
ATTR_TCP_FLAGS_REPL = 34 # u8 bits
ATTR_TCP_MASK_ORIG = 35 # u8 bits
ATTR_TCP_MASK_REPL = 36 # u8 bits
ATTR_MASTER_IPV4_SRC = 37 # u32 bits
ATTR_MASTER_IPV4_DST = 38 # u32 bits
ATTR_MASTER_IPV6_SRC = 39 # u128 bits
ATTR_MASTER_IPV6_DST = 40 # u128 bits
ATTR_MASTER_PORT_SRC = 41 # u16 bits
ATTR_MASTER_PORT_DST = 42 # u16 bits
ATTR_MASTER_L3PROTO = 43 # u8 bits
ATTR_MASTER_L4PROTO = 44 # u8 bits
ATTR_SECMARK = 45 # u32 bits
ATTR_ORIG_NAT_SEQ_CORRECTION_POS = 46 # u32 bits
ATTR_ORIG_NAT_SEQ_OFFSET_BEFORE = 47 # u32 bits
ATTR_ORIG_NAT_SEQ_OFFSET_AFTER = 48 # u32 bits
ATTR_REPL_NAT_SEQ_CORRECTION_POS = 49 # u32 bits
ATTR_REPL_NAT_SEQ_OFFSET_BEFORE = 50 # u32 bits
ATTR_REPL_NAT_SEQ_OFFSET_AFTER = 51 # u32 bits
ATTR_SCTP_STATE = 52 # u8 bits
ATTR_SCTP_VTAG_ORIG = 53 # u32 bits
ATTR_SCTP_VTAG_REPL = 54 # u32 bits
ATTR_HELPER_NAME = 55 # string (30 bytes max)
ATTR_DCCP_STATE = 56 # u8 bits
ATTR_DCCP_ROLE = 57 # u8 bits
ATTR_DCCP_HANDSHAKE_SEQ = 58 # u64 bits
ATTR_MAX = 59
ATTR_GRP_ORIG_IPV4 = 0 # struct nfct_attr_grp_ipv4
ATTR_GRP_REPL_IPV4 = 1 # struct nfct_attr_grp_ipv4
ATTR_GRP_ORIG_IPV6 = 2 # struct nfct_attr_grp_ipv6
ATTR_GRP_REPL_IPV6 = 3 # struct nfct_attr_grp_ipv6
ATTR_GRP_ORIG_PORT = 4 # struct nfct_attr_grp_port
ATTR_GRP_REPL_PORT = 5 # struct nfct_attr_grp_port
ATTR_GRP_ICMP = 6 # struct nfct_attr_grp_icmp
ATTR_GRP_MASTER_IPV4 = 7 # struct nfct_attr_grp_ipv4
ATTR_GRP_MASTER_IPV6 = 8 # struct nfct_attr_grp_ipv6
ATTR_GRP_MASTER_PORT = 9 # struct nfct_attr_grp_port
ATTR_GRP_ORIG_COUNTERS = 10 # struct nfct_attr_grp_ctrs
ATTR_GRP_REPL_COUNTERS = 11 # struct nfct_attr_grp_ctrs
ATTR_GRP_MAX = 12
ATTR_EXP_MASTER = 0 # pointer to conntrack object
ATTR_EXP_EXPECTED = 1 # pointer to conntrack object
ATTR_EXP_MASK = 2 # pointer to conntrack object
ATTR_EXP_TIMEOUT = 3 # u32 bits
ATTR_EXP_MAX = 4
# NFCT_*printf output format
NFCT_O_PLAIN = 0
NFCT_O_DEFAULT = NFCT_O_PLAIN
NFCT_O_XML = 1
NFCT_O_MAX = 2
NFCT_CMP_ALL = 0
NFCT_CMP_ORIG = (1 << 0)
NFCT_CMP_REPL = (1 << 1)
NFCT_CMP_TIMEOUT_EQ = (1 << 2)
NFCT_CMP_TIMEOUT_GT = (1 << 3)
NFCT_CMP_TIMEOUT_GE = (NFCT_CMP_TIMEOUT_EQ | NFCT_CMP_TIMEOUT_GT)
NFCT_CMP_TIMEOUT_LT = (1 << 4)
NFCT_CMP_TIMEOUT_LE = (NFCT_CMP_TIMEOUT_EQ | NFCT_CMP_TIMEOUT_LT)
NFCT_CMP_MASK = (1 << 5)
NFCT_CMP_STRICT = (1 << 6)
# Conntrack options
CT_OPT_ORIG_SRC_BIT = 0
CT_OPT_ORIG_SRC = (1 << CT_OPT_ORIG_SRC_BIT)
CT_OPT_ORIG_DST_BIT = 1
CT_OPT_ORIG_DST = (1 << CT_OPT_ORIG_DST_BIT)
CT_OPT_ORIG = (CT_OPT_ORIG_SRC | CT_OPT_ORIG_DST)
CT_OPT_REPL_SRC_BIT = 2
CT_OPT_REPL_SRC = (1 << CT_OPT_REPL_SRC_BIT)
CT_OPT_REPL_DST_BIT = 3
CT_OPT_REPL_DST = (1 << CT_OPT_REPL_DST_BIT)
CT_OPT_REPL = (CT_OPT_REPL_SRC | CT_OPT_REPL_DST)
CT_OPT_PROTO_BIT = 4
CT_OPT_PROTO = (1 << CT_OPT_PROTO_BIT)
CT_OPT_TUPLE_ORIG = (CT_OPT_ORIG | CT_OPT_PROTO)
CT_OPT_TUPLE_REPL = (CT_OPT_REPL | CT_OPT_PROTO)
CT_OPT_TIMEOUT_BIT = 5
CT_OPT_TIMEOUT = (1 << CT_OPT_TIMEOUT_BIT)
CT_OPT_STATUS_BIT = 6
CT_OPT_STATUS = (1 << CT_OPT_STATUS_BIT)
CT_OPT_ZERO_BIT = 7
CT_OPT_ZERO = (1 << CT_OPT_ZERO_BIT)
CT_OPT_EVENT_MASK_BIT = 8
CT_OPT_EVENT_MASK = (1 << CT_OPT_EVENT_MASK_BIT)
CT_OPT_EXP_SRC_BIT = 9
CT_OPT_EXP_SRC = (1 << CT_OPT_EXP_SRC_BIT)
CT_OPT_EXP_DST_BIT = 10
CT_OPT_EXP_DST = (1 << CT_OPT_EXP_DST_BIT)
CT_OPT_MASK_SRC_BIT = 11
CT_OPT_MASK_SRC = (1 << CT_OPT_MASK_SRC_BIT)
CT_OPT_MASK_DST_BIT = 12
CT_OPT_MASK_DST = (1 << CT_OPT_MASK_DST_BIT)
CT_OPT_NATRANGE_BIT = 13
CT_OPT_NATRANGE = (1 << CT_OPT_NATRANGE_BIT)
CT_OPT_MARK_BIT = 14
CT_OPT_MARK = (1 << CT_OPT_MARK_BIT)
CT_OPT_ID_BIT = 15
CT_OPT_ID = (1 << CT_OPT_ID_BIT)
CT_OPT_FAMILY_BIT = 16
CT_OPT_FAMILY = (1 << CT_OPT_FAMILY_BIT)
CT_OPT_SRC_NAT_BIT = 17
CT_OPT_SRC_NAT = (1 << CT_OPT_SRC_NAT_BIT)
CT_OPT_DST_NAT_BIT = 18
CT_OPT_DST_NAT = (1 << CT_OPT_DST_NAT_BIT)
CT_OPT_OUTPUT_BIT = 19
CT_OPT_OUTPUT = (1 << CT_OPT_OUTPUT_BIT)
CT_OPT_SECMARK_BIT = 20
CT_OPT_SECMARK = (1 << CT_OPT_SECMARK_BIT)
CT_OPT_BUFFERSIZE_BIT = 21
CT_OPT_BUFFERSIZE = (1 << CT_OPT_BUFFERSIZE_BIT)
CT_OPT_ANY_NAT_BIT = 22
CT_OPT_ANY_NAT = (1 << CT_OPT_ANY_NAT_BIT)
CT_OPT_ZONE_BIT = 23
CT_OPT_ZONE = (1 << CT_OPT_ZONE_BIT)
CT_COMPARISON = (CT_OPT_PROTO | CT_OPT_ORIG | CT_OPT_REPL | CT_OPT_MARK |
CT_OPT_SECMARK | CT_OPT_STATUS | CT_OPT_ID | CT_OPT_ZONE)

View File

@ -0,0 +1,26 @@
# 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 oslo_privsep import capabilities as c
from oslo_privsep import priv_context
# It is expected that most (if not all) neutron operations can be
# executed with these privileges.
default = priv_context.PrivContext(
__name__,
cfg_section='privsep',
pypath=__name__ + '.default',
# TODO(gus): CAP_SYS_ADMIN is required (only?) for manipulating
# network namespaces. SYS_ADMIN is a lot of scary powers, so
# consider breaking this out into a separate minimal context.
capabilities=[c.CAP_SYS_ADMIN, c.CAP_NET_ADMIN],
)

View File

@ -0,0 +1,272 @@
# 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.
#
"""
Conntrack - A simple python interface to libnetfilter_conntrack using ctypes.
"""
import ctypes as c
from ctypes.util import find_library
import os
from socket import AF_INET
from socket import AF_INET6
from pyroute2 import netns as pynetns
from oslo_log import log as logging
from neutron_fwaas._i18n import _LE
from neutron_fwaas.common import netlink_constants as nl_constants
from neutron_fwaas import privileged
LOG = logging.getLogger(__name__)
nfct = c.CDLL(find_library('netfilter_conntrack'))
libc = c.CDLL(find_library('libc.so.6'))
proto_num = {
'tcp': 6,
'udp': 17,
'icmp': 1,
}
family_socket = {
4: AF_INET,
6: AF_INET6,
}
NFCT_CALLBACK = c.CFUNCTYPE(c.c_int, c.c_int, c.c_void_p, c.c_void_p)
def _list(family=4):
"""
Get list of active conntrack entries.
:param: family: ipversion of conntrack entries to be listed.
:return: entries: list of conntrack entries.
"""
entries = []
buf = c.create_string_buffer(1024)
@NFCT_CALLBACK
def cb(type, ct, data):
nfct.nfct_snprintf(buf, 1024, ct, type, 0, nl_constants.NFCT_OF_TIME)
entries.append(buf.value)
return nl_constants.NFCT_CB_CONTINUE
h = nfct.nfct_open(nl_constants.CONNTRACK, 0)
if not h:
LOG.exception(_LE("nfct_open failed!"))
return entries
nfct.nfct_callback_register(h, nl_constants.NFCT_T_ALL, cb, 0)
ret = nfct.nfct_query(h, nl_constants.NFCT_Q_DUMP,
c.byref(c.c_int(family_socket[family])))
if ret == -1:
nfct.nfct_close(h)
LOG.exception(_LE("nfct_query failed!"))
return entries
nfct.nfct_close(h)
return entries
def _kill(**kwargs):
"""
Delete specified conntrack entries.
:param: kwargs: entry information
:return: None
"""
family = kwargs.get('family', 4)
protocol = kwargs.get('protocol', 'tcp')
source_address = kwargs.get('src', '0.0.0.0')
destination_address = kwargs.get('dst', '0.0.0.0')
ct = nfct.nfct_new()
if not ct:
LOG.exception(_LE("nfct_new failed!"))
return
nfct.nfct_set_attr_u8(ct, nl_constants.ATTR_L3PROTO, family_socket[family])
if family == 4:
nfct.nfct_set_attr_u32(ct, nl_constants.ATTR_IPV4_SRC,
libc.inet_addr(source_address))
nfct.nfct_set_attr_u32(ct, nl_constants.ATTR_IPV4_DST,
libc.inet_addr(destination_address))
elif family == 6:
nfct.nfct_set_attr_u64(ct, nl_constants.ATTR_IPV6_SRC,
libc.inet_addr(source_address))
nfct.nfct_set_attr_u64(ct, nl_constants.ATTR_IPV6_DST,
libc.inet_addr(destination_address))
else:
LOG.exception(_LE("Unsupported protocol family!"))
nfct.nfct_set_attr_u8(ct, nl_constants.ATTR_L4PROTO, proto_num[protocol])
if protocol == 'icmp':
nfct.nfct_set_attr_u8(ct, nl_constants.ATTR_ICMP_TYPE,
kwargs.get('icmp_type', 8))
nfct.nfct_set_attr_u8(ct, nl_constants.ATTR_ICMP_CODE,
kwargs.get('icmp_code', 0))
nfct.nfct_set_attr_u16(ct, nl_constants.ATTR_ICMP_ID,
libc.htons(kwargs.get('icmp_id'), 0))
else:
nfct.nfct_set_attr_u16(ct, nl_constants.ATTR_PORT_SRC,
libc.htons(kwargs.get('sport')))
nfct.nfct_set_attr_u16(ct, nl_constants.ATTR_PORT_DST,
libc.htons(kwargs.get('dport')))
h = nfct.nfct_open(nl_constants.CONNTRACK, 0)
if not h:
LOG.exception(_LE("nfct_open failed!"))
else:
ret = nfct.nfct_query(h, nl_constants.NFCT_Q_DESTROY, ct)
if ret == -1:
LOG.exception(_LE("Deleting conntrack failed"))
nfct.nfct_close(h)
nfct.nfct_destroy(ct)
def _flush():
ct = nfct.nfct_new()
if not ct:
libc.perror("nfct_new")
raise LOG.exception(_LE("nfct_new failed!"))
return
h = nfct.nfct_open(nl_constants.CONNTRACK, 0)
if not h:
libc.perror("nfct_open")
raise LOG.exception(_LE("nfct_open failed!"))
else:
ret = nfct.nfct_query(h, nl_constants.NFCT_Q_FLUSH, ct)
if ret == -1:
libc.perror("nfct_query")
raise LOG.exception(_LE("nfct_query failed!"))
nfct.nfct_close(h)
nfct.nfct_destroy(ct)
def _parse_entry(entry, ipversion):
"""
Parse entry to a tuple
:param entry: Array from entry string split
:param ipversion: ipversion used to get this entry
:return: a tuple of parsed entry
example: (4, 'tcp', '1111', '2222', '1.1.1.1', '2.2.2.2')
"""
protocol = entry[1]
if protocol == 'tcp':
src_address = entry[5].split('=')[1]
dst_address = entry[6].split('=')[1]
sport = entry[7].split('=')[1]
dport = entry[8].split('=')[1]
elif protocol == 'udp':
src_address = entry[4].split('=')[1]
dst_address = entry[5].split('=')[1]
sport = entry[6].split('=')[1]
dport = entry[7].split('=')[1]
elif protocol == 'icmp':
src_address = entry[4].split('=')[1]
dst_address = entry[5].split('=')[1]
icmp_type = entry[6].split('=')[1]
icmp_code = entry[7].split('=')[1]
icmp_id = entry[8].split('=')[1]
parsed_entry = (ipversion, protocol, icmp_type, icmp_code,
src_address, dst_address, icmp_id,)
return parsed_entry
parsed_entry = (ipversion, protocol, sport,
dport, src_address, dst_address,)
return parsed_entry
@privileged.default.entrypoint
def list_entries(namespace):
"""
List, parse and sort all entries
:param namespace:
:return: sorted list of entry tuples.
example: [(4, 'icmp', '8', '0', '1.1.1.1', '2.2.2.2'),
(4, 'tcp', '1111', '2222', '1.1.1.1', '2.2.2.2')]
"""
entries = []
if namespace:
fd = pynetns.setns(namespace)
ipversions = [4, 6]
for ipversion in ipversions:
xentries = _list(ipversion)
for entry in xentries:
sentry = entry.split()
xentry = _parse_entry(sentry, ipversion)
entries.append(xentry)
os.close(fd)
return sorted(entries)
def _kill_entry(entry):
"""
Kill the entry
:param entry: (ipversion, protocol, sport, dport, saddress, daddress)
"""
if entry[1] == 'icmp':
_kill(family=entry[0], protocol=entry[1],
src=entry[4], dst=entry[5],
icmp_type=int(entry[2]), icmp_code=int(entry[3]),
icmp_id=int(entry[6]))
else:
_kill(family=entry[0], protocol=entry[1],
src=entry[4], dst=entry[5],
sport=int(entry[2]), dport=int(entry[3]))
@privileged.default.entrypoint
def kill_entries(namespace, entries):
if namespace:
fd = pynetns.setns(namespace)
for entry in entries:
_kill_entry(entry)
os.close(fd)
@privileged.default.entrypoint
def flush_entries(namespace):
if namespace:
fd = pynetns.setns(namespace)
_flush()
os.close(fd)

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron.agent.common import config
from neutron.common import rpc as n_rpc
from neutron import context
from oslo_config import cfg
@ -61,6 +62,7 @@ class FWaaSL3AgentExtension(l3_extension.L3AgentExtension):
f_resources.FIREWALL_RULE]
def initialize(self, connection, driver_type):
config.setup_privsep()
self._register_rpc_consumers(connection)
def consume_api(self, agent_api):

View File

@ -12,14 +12,13 @@
# 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 iptables_manager
from neutron.agent.linux import utils as linux_utils
from oslo_log import log as logging
from neutron_fwaas._i18n import _LE
from neutron_fwaas.common import fwaas_constants as f_const
from neutron_fwaas.extensions import firewall as fw_ext
from neutron_fwaas.privileged import netlink_lib
from neutron_fwaas.services.firewall.drivers import fwaas_base
LOG = logging.getLogger(__name__)
@ -257,26 +256,6 @@ class IptablesFwaasDriver(fwaas_base.FwaasDriverBase):
def _find_new_rules(self, pre_firewall, firewall):
return self._find_removed_rules(firewall, pre_firewall)
def _get_conntrack_cmd_from_rule(self, ipt_mgr, rule=None):
prefixcmd = ['ip', 'netns', 'exec'] + [ipt_mgr.namespace]
cmd = ['conntrack', '-D']
if rule:
conntrack_filter = self._get_conntrack_filter_from_rule(rule)
exec_cmd = prefixcmd + cmd + conntrack_filter
else:
exec_cmd = prefixcmd + cmd
return exec_cmd
def _remove_conntrack_by_cmd(self, cmd):
if cmd:
try:
linux_utils.execute(cmd, run_as_root=True,
check_exit_code=True,
extra_ok_codes=[1])
except RuntimeError:
LOG.exception(
_LE("Failed execute conntrack command %s"), str(cmd))
def _remove_conntrack_new_firewall(self, agent_mode, apply_list, firewall):
"""Remove conntrack when create new firewall"""
routers_list = list(set(apply_list))
@ -285,8 +264,7 @@ class IptablesFwaasDriver(fwaas_base.FwaasDriverBase):
agent_mode, router_info)
for ipt_if_prefix in ipt_if_prefix_list:
ipt_mgr = ipt_if_prefix['ipt']
cmd = self._get_conntrack_cmd_from_rule(ipt_mgr)
self._remove_conntrack_by_cmd(cmd)
self._flush_conntrack_netlink(ipt_mgr.namespace)
def _remove_conntrack_updated_firewall(self, agent_mode,
apply_list, pre_firewall, firewall):
@ -302,27 +280,78 @@ class IptablesFwaasDriver(fwaas_base.FwaasDriverBase):
i_rules = self._find_new_rules(pre_firewall, firewall)
r_rules = self._find_removed_rules(pre_firewall, firewall)
removed_conntrack_rules_list = ch_rules + i_rules + r_rules
rules = []
for rule in removed_conntrack_rules_list:
cmd = self._get_conntrack_cmd_from_rule(ipt_mgr, rule)
self._remove_conntrack_by_cmd(cmd)
rules.append(self._get_filters_from_rules(rule))
rules = sorted(list(set(rules)))
self._remove_conntrack_netlink(ipt_mgr.namespace, rules)
def _get_conntrack_filter_from_rule(self, rule):
"""Get conntrack filter from rule.
The key for get conntrack filter is protocol, destination_port
and source_port. If we want to take more keys, add to the list.
@staticmethod
def _entry2delete(rule, entry):
"""
conntrack_filter = []
keys = [['-p', 'protocol'], ['-f', 'ip_version'],
['--dport', 'destination_port'], ['--sport', 'source_port']]
Check if an entry will be delete or not
:param rule: (ipversion, protocol, sport, dport)
:param entry: (ipversion, protocol, sport, dport, saddress, daddress)
:return: True if the entry matches the rule
The entry matches the rule if it has the same ipversion, protocol or
entry source port, destination port sequentially in rule source port,
destination port range.
"""
return (
(entry[0] == rule[0]) and (not rule[1] or entry[1] == rule[1]) and
(not rule[2] or int(entry[2]) in range(int(rule[2].split(':')[0]),
int(rule[2].split(':')[-1]) + 1)) and
(not rule[2] or int(entry[2]) in range(int(rule[2].split(':')[0]),
int(rule[2].split(':')[-1]) + 1)))
def _remove_conntrack_netlink(self, namespace, rules):
# Getting a list of all entries
entries = netlink_lib.list_entries(namespace)
# Compare each entry to each rule to define that this entry
# is to delete or not.
# rule and entry have the same parameters order to be comparable:
# rule: (ipversion, protocol, sport, dport)
# entry: (ipversion, protocol, sport, dport, saddress, daddress)
# rules and entries were sorted lists of tuples to reduce the
# number of comparisons.
dentries = []
ientry = 0
entryNumber = len(entries)
for rule in rules:
while ientry < entryNumber and entries[ientry] < rule:
ientry += 1
while (ientry < entryNumber and
self._entry2delete(rule, entries[ientry])):
dentries.append(entries[ientry])
ientry += 1
# Calling to netlink_lib.kill_entries to delete entries
netlink_lib.kill_entries(namespace, dentries)
def _flush_conntrack_netlink(self, namespace):
netlink_lib.flush_entries(namespace)
def _get_filters_from_rules(self, rule):
"""Parse parameters from firewall rules
:param: rule: A firewall rule
:return filter: Tuple of parameters
example: (4, 'tcp', 1111, 2222, '1.1.1.1', '2.2.2.2')
"""
keys = ['ip_version', 'protocol', 'source_port', 'destination_port']
addr_keys = ['source_ip_address', 'destination_ip_address']
rule_filter = []
for key in keys:
if rule.get(key[1]):
if key[1] == 'ip_version':
conntrack_filter.append(key[0])
conntrack_filter.append('ipv' + str(rule.get(key[1])))
else:
conntrack_filter.append(key[0])
conntrack_filter.append(rule.get(key[1]))
return conntrack_filter
rule_filter.append(rule.get(key) or '')
for key in addr_keys:
if not rule.get(key):
rule_filter.append('')
else:
rule_filter.append(rule.get(key))
return tuple(rule_filter)
def _remove_default_chains(self, nsid):
"""Remove fwaas default policy chain."""

View File

@ -27,6 +27,8 @@ FAKE_DST_PREFIX = '20.0.0.0/24'
FAKE_PROTOCOL = 'tcp'
FAKE_SRC_PORT = 5000
FAKE_DST_PORT = 22
FAKE_ENTRY = [(4, 'icmp', '8', '0', '1000', '1.1.1.1', '2.2.2.2'),
(4, 'tcp', '1111', '23', '1.1.1.1', '2.2.2.2'), ]
FAKE_FW_ID = 'fake-fw-uuid'
FW_LEGACY = 'legacy'
@ -40,6 +42,12 @@ class IptablesFwaasTestCase(base.BaseTestCase):
self.iptables_cls_p = mock.patch(
'neutron.agent.linux.iptables_manager.IptablesManager')
self.iptables_cls_p.start()
self.netlink_kill_p = mock.patch(
'neutron_fwaas.privileged.netlink_lib.kill_entries')
self.netlink_kill_p.start()
self.netlink_flush_p = mock.patch(
'neutron_fwaas.privileged.netlink_lib.flush_entries')
self.netlink_flush = self.netlink_flush_p.start()
self.firewall = fwaas.IptablesFwaasDriver()
def _fake_rules_v4(self, fwid, apply_list):
@ -285,13 +293,11 @@ class IptablesFwaasTestCase(base.BaseTestCase):
self.firewall.create_firewall(FW_LEGACY, apply_list, firewall)
for router_info_inst in apply_list:
namespace = router_info_inst.iptables_manager.namespace
cmd = ['ip', 'netns', 'exec', namespace, 'conntrack', '-D']
calls = [
mock.call(cmd, run_as_root=True, check_exit_code=True,
extra_ok_codes=[1])]
self.utils_exec.assert_has_calls(calls)
calls = [mock.call(namespace)]
self.netlink_flush.assert_has_calls(calls)
def test_remove_conntrack_inserted_rule(self):
@mock.patch('neutron_fwaas.privileged.netlink_lib.kill_entries')
def test_remove_conntrack_inserted_rule(self, mock_kill):
apply_list = self._fake_apply_list()
rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list)
firewall = self._fake_firewall(rule_list)
@ -304,21 +310,22 @@ class IptablesFwaasTestCase(base.BaseTestCase):
'id': 'fake-fw-rule'}
rule_list.insert(2, insert_rule)
firewall = self._fake_firewall(rule_list)
self.firewall.update_firewall(FW_LEGACY, apply_list, firewall)
for router_info_inst in apply_list:
namespace = router_info_inst.iptables_manager.namespace
cmd1 = ['ip', 'netns', 'exec', namespace, 'conntrack',
'-D', '-p', 'tcp', '-f', 'ipv4', '--dport', '23']
cmd2 = ['ip', 'netns', 'exec', namespace, 'conntrack',
'-D', '-p', 'icmp', '-f', 'ipv4']
calls = [
mock.call(cmd1, run_as_root=True, check_exit_code=True,
extra_ok_codes=[1]),
mock.call(cmd2, run_as_root=True, check_exit_code=True,
extra_ok_codes=[1])]
self.utils_exec.assert_has_calls(calls)
with mock.patch('neutron_fwaas.privileged.'
'netlink_lib.list_entries') as list_entries:
list_entries.return_value = FAKE_ENTRY
self.firewall.update_firewall(FW_LEGACY, apply_list, firewall)
calls = [
mock.call(namespace, [(4, 'icmp', '8', '0', '1000',
'1.1.1.1', '2.2.2.2'),
(4, 'tcp', '1111', '23',
'1.1.1.1', '2.2.2.2')])
]
mock_kill.assert_has_calls(calls)
def test_remove_conntrack_removed_rule(self):
@mock.patch('neutron_fwaas.privileged.netlink_lib.kill_entries')
def test_remove_conntrack_removed_rule(self, mock_kill):
apply_list = self._fake_apply_list()
rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list)
firewall = self._fake_firewall(rule_list)
@ -327,21 +334,20 @@ class IptablesFwaasTestCase(base.BaseTestCase):
remove_rule = rule_list[1]
rule_list.remove(remove_rule)
firewall = self._fake_firewall(rule_list)
self.firewall.update_firewall(FW_LEGACY, apply_list, firewall)
for router_info_inst in apply_list:
namespace = router_info_inst.iptables_manager.namespace
cmd1 = ['ip', 'netns', 'exec', namespace, 'conntrack',
'-D', '-p', 'tcp', '-f', 'ipv4', '--dport', '23']
cmd2 = ['ip', 'netns', 'exec', namespace, 'conntrack',
'-D', '-p', 'tcp', '-f', 'ipv4', '--dport', '22']
calls = [
mock.call(cmd1, run_as_root=True, check_exit_code=True,
extra_ok_codes=[1]),
mock.call(cmd2, run_as_root=True, check_exit_code=True,
extra_ok_codes=[1])]
self.utils_exec.assert_has_calls(calls)
with mock.patch('neutron_fwaas.privileged.'
'netlink_lib.list_entries') as list_entries:
list_entries.return_value = FAKE_ENTRY
self.firewall.update_firewall(FW_LEGACY, apply_list, firewall)
calls = [
mock.call(namespace, [(4, 'tcp', '1111', '23',
'1.1.1.1', '2.2.2.2')])
]
mock_kill.assert_has_calls(calls)
def test_remove_conntrack_changed_rule(self):
@mock.patch('neutron_fwaas.privileged.netlink_lib.kill_entries')
def test_remove_conntrack_changed_rule(self, mock_kill):
apply_list = self._fake_apply_list()
rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list)
firewall = self._fake_firewall(rule_list)
@ -349,20 +355,18 @@ class IptablesFwaasTestCase(base.BaseTestCase):
income_rule = {'enabled': True,
'action': 'deny',
'ip_version': 4,
'protocol': 'icmp',
'protocol': 'tcp',
'id': 'fake-fw-rule2'}
rule_list[1] = income_rule
rule_list[2] = income_rule
firewall = self._fake_firewall(rule_list)
self.firewall.update_firewall(FW_LEGACY, apply_list, firewall)
for router_info_inst in apply_list:
namespace = router_info_inst.iptables_manager.namespace
cmd1 = ['ip', 'netns', 'exec', namespace, 'conntrack', '-D',
'-p', 'tcp', '-f', 'ipv4', '--dport', '22']
cmd2 = ['ip', 'netns', 'exec', namespace, 'conntrack', '-D',
'-p', 'icmp', '-f', 'ipv4']
calls = [
mock.call(cmd1, run_as_root=True, check_exit_code=True,
extra_ok_codes=[1]),
mock.call(cmd2, run_as_root=True, check_exit_code=True,
extra_ok_codes=[1])]
self.utils_exec.assert_has_calls(calls)
with mock.patch('neutron_fwaas.privileged.'
'netlink_lib.list_entries') as list_entries:
list_entries.return_value = FAKE_ENTRY
self.firewall.update_firewall(FW_LEGACY, apply_list, firewall)
calls = [
mock.call(namespace, [(4, 'tcp', '1111', '23',
'1.1.1.1', '2.2.2.2')])
]
mock_kill.assert_has_calls(calls)

View File

@ -16,7 +16,8 @@ oslo.messaging>=5.14.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0
oslo.service>=1.10.0 # Apache-2.0
oslo.utils>=3.18.0 # Apache-2.0
oslo.privsep>=1.9.0 # Apache-2.0
pyroute2>=0.4.12 # Apache-2.0 (+ dual licensed GPL2)
# This project does depend on neutron as a library, but the
# openstack tooling does not play nicely with projects that
# are not publicly available in pypi.