neutron-fwaas/neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/netlink_conntrack.py

145 lines
5.5 KiB
Python

# 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_lib import constants
from oslo_log import log as logging
from neutron_fwaas.privileged import netlink_lib as nl_lib
from neutron_fwaas.services.firewall.service_drivers.agents.drivers import\
conntrack_base
LOG = logging.getLogger(__name__)
class ConntrackNetlink(conntrack_base.ConntrackDriverBase):
def initialize(self, *args, **kwargs):
LOG.debug('Conntrack Netlink loaded')
def flush_entries(self, namespace):
"""Flush all conntrack entries within the namespace
:param namespace: namespace to flush
:return: None
"""
nl_lib.flush_entries(namespace)
def delete_entries(self, rules, namespace):
rule_filters = (self._get_filter_from_rule(r) for r in rules)
rule_filters = sorted(rule_filters)
entries = nl_lib.list_entries(namespace)
delete_entries = self._get_entries_to_delete(rule_filters, entries)
if delete_entries:
nl_lib.delete_entries(delete_entries, namespace)
def _get_entries_to_delete(self, rule_filters, entries):
"""Specify conntrack entries to delete
:param rule_filters: List of filters parsed from firewall rules
:param entries: all entries within namespace
:return: conntrack entries to delete
"""
# List all entries from namespace, they are already parsed
# to a list of tuples:
# [(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')]
delete_entries = []
entry_index = 0
entry_number = len(entries)
for rule_filter in rule_filters:
while entry_index < entry_number:
# Compare entry with rule
comp = self._compare_entry_and_rule(rule_filter,
entries[entry_index])
# Increase entry_index when entry is under rule
if comp < 0:
entry_index += 1
# Append entry to delete_entry if it matches with rule
elif comp == 0:
delete_entries.append(entries[entry_index])
entry_index += 1
# Switch to new higher rule
else:
break
return delete_entries
@staticmethod
def _get_filter_from_rule(rule):
"""Parse the firewall rule to a tuple
:param rule: firewall rule
:return: a tuple of parsed information
"""
rule_filter = []
keys = ['ip_version', 'protocol',
'source_port', 'destination_port',
'source_ip_address', 'destination_ip_address']
for key in keys:
if key in ['source_port', 'destination_port']:
port_range = rule.get(key, [])
if port_range:
port_lower, sep, port_upper = port_range.partition(':')
port_upper = port_upper if sep else port_lower
port_range = [port_lower, port_upper]
rule_filter.append(port_range or [])
else:
rule_filter.append(rule.get(key, []))
return tuple(rule_filter)
@staticmethod
def _compare_entry_and_rule(rule_filter, entry):
"""Define that the entry should be deleted or not
:param rule_filter: filter that is parsed from a firewall rule
ex: (4, 'tcp', 1, 2)
:param entry: parsed conntrack entry,
ex: (4, 'tcp', 1, 2, '1.1.1.1', '2.2.2.2')
:return: -1 if entry is lower than rule, 0 if entry matches rule,
1 if entry is higher than rule
"""
ENTRY_IS_LOWER = -1
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]
if entry[2] < min(sport_range[0], sport_range[-1]):
return ENTRY_IS_LOWER
elif entry[2] > max(sport_range[0], sport_range[-1]):
return ENTRY_IS_HIGHER
dport_range = rule_filter[3]
if dport_range:
dport_range = [int(port) for port in dport_range]
if entry[3] < min(dport_range[0], dport_range[-1]):
return ENTRY_IS_LOWER
elif entry[3] > max(dport_range[0], dport_range[-1]):
return ENTRY_IS_HIGHER
return ENTRY_MATCHES