diff --git a/neutron/agent/linux/iptables_firewall.py b/neutron/agent/linux/iptables_firewall.py index 9c41ba0f36c..3a19f5a5ee8 100644 --- a/neutron/agent/linux/iptables_firewall.py +++ b/neutron/agent/linux/iptables_firewall.py @@ -14,9 +14,13 @@ # under the License. import collections +import ctypes +from ctypes import util +import sys import netaddr from neutron_lib import constants +from neutron_lib.utils import helpers from oslo_config import cfg from oslo_log import log as logging from oslo_utils import netutils @@ -40,6 +44,7 @@ CHAIN_NAME_PREFIX = {firewall.INGRESS_DIRECTION: 'i', IPSET_DIRECTION = {firewall.INGRESS_DIRECTION: 'src', firewall.EGRESS_DIRECTION: 'dst'} comment_rule = iptables_manager.comment_rule +libc = ctypes.CDLL(util.find_library('libc.so.6')) def get_hybrid_port_name(port_name): @@ -87,6 +92,7 @@ class IptablesFirewallDriver(firewall.FirewallDriver): self.updated_rule_sg_ids = set() self.updated_sg_members = set() self.devices_with_updated_sg_members = collections.defaultdict(list) + self._iptables_protocol_name_map = {} @property def ports(self): @@ -667,10 +673,44 @@ class IptablesFirewallDriver(firewall.FirewallDriver): comment=ic.ALLOW_ASSOC)] return iptables_rules + def _local_protocol_name_map(self): + local_protocol_name_map = {} + try: + class protoent(ctypes.Structure): + _fields_ = [("p_name", ctypes.c_char_p), + ("p_aliases", ctypes.POINTER(ctypes.c_char_p)), + ("p_proto", ctypes.c_int)] + libc.getprotoent.restype = ctypes.POINTER(protoent) + libc.setprotoent(0) + while True: + pr = libc.getprotoent() + if not pr: + break + r = pr[0] + p_name = helpers.safe_decode_utf8(r.p_name) + local_protocol_name_map[str(r.p_proto)] = p_name + except Exception: + LOG.exception("Unable to create local protocol name map: %s", + sys.exc_info()[0]) + finally: + libc.endprotoent() + return local_protocol_name_map + + def _protocol_name_map(self): + if not self._iptables_protocol_name_map: + tmp_map = n_const.IPTABLES_PROTOCOL_NAME_MAP.copy() + tmp_map.update(self._local_protocol_name_map()) + self._iptables_protocol_name_map = tmp_map + return self._iptables_protocol_name_map + + def _iptables_protocol_name(self, protocol): + # protocol zero is a special case and requires no '-p' + if protocol and protocol != '0': + return self._protocol_name_map().get(protocol, protocol) + def _protocol_arg(self, protocol, is_port): iptables_rule = [] - rule_protocol = n_const.IPTABLES_PROTOCOL_NAME_MAP.get(protocol, - protocol) + rule_protocol = self._iptables_protocol_name(protocol) # protocol zero is a special case and requires no '-p' if rule_protocol: iptables_rule = ['-p', rule_protocol] @@ -686,7 +726,7 @@ class IptablesFirewallDriver(firewall.FirewallDriver): if port_range_min is None: return args - protocol = n_const.IPTABLES_PROTOCOL_NAME_MAP.get(protocol, protocol) + protocol = self._iptables_protocol_name(protocol) if protocol in ['icmp', 'ipv6-icmp']: protocol_type = 'icmpv6' if protocol == 'ipv6-icmp' else 'icmp' # Note(xuhanp): port_range_min/port_range_max represent diff --git a/neutron/tests/unit/agent/linux/test_iptables_firewall.py b/neutron/tests/unit/agent/linux/test_iptables_firewall.py index 3a5edebf9e5..06f899ce0e3 100644 --- a/neutron/tests/unit/agent/linux/test_iptables_firewall.py +++ b/neutron/tests/unit/agent/linux/test_iptables_firewall.py @@ -436,6 +436,22 @@ class IptablesFirewallTestCase(BaseIptablesFirewallTestCase): egress = None self._test_prepare_port_filter(rule, ingress, egress) + def test_filter_ipv4_ingress_protocol_999_local(self): + # There is no protocol 999, so let's return a mapping + # that says there is and make sure the rule is created + # using the name and not the number. + rule = {'ethertype': 'IPv4', + 'direction': 'ingress', + 'protocol': '999'} + ingress = mock.call.add_rule('ifake_dev', + '-p fooproto -j RETURN', + comment=None) + egress = None + with mock.patch.object(self.firewall, + '_local_protocol_name_map') as lpnm: + lpnm.return_value = {'999': 'fooproto'} + self._test_prepare_port_filter(rule, ingress, egress) + def test_filter_ipv4_egress(self): rule = {'ethertype': 'IPv4', 'direction': 'egress'}