From 60c90949d00099e569f863c987e1c748146abad6 Mon Sep 17 00:00:00 2001 From: littlespace Date: Tue, 21 Apr 2026 09:19:01 +0200 Subject: [PATCH] [caclmgrd] Fix EXTERNAL_CLIENT ACL: duplicate rules, multi-port, and stale state Fix duplicate iptables/ip6tables rules for EXTERNAL_CLIENT Control Plane ACL. The rule-emission loop could append identical full commands (ns-prefix + rule) multiple times. Fix builds the full command first and uses a per-table shadow set (acl_cmds_seen) for O(1) duplicate detection. iptables_cmds stays a list to preserve rule ordering. Fix single-port limitation for EXTERNAL_CLIENT ACL: previously each rule overwrote dst_ports with a single-element list, so only the last rule's port was used. Fix accumulates ports via append-if-not-present so all configured ports generate iptables rules. Fix ACL_SERVICES class dict pollution: dst_ports was written back into the class-level ACL_SERVICES dict on every call, causing stale ports from deleted rules to persist across reloads until process restart. dst_ports is now kept strictly local to each call. Fix int/str type mismatch in dst_ports: L4_DST_PORT_RANGE expansion now appends str(port) to stay type-consistent with L4_DST_PORT (which is a string from Redis). The mixed-type list silently broke the duplicate check producing duplicate --dport rules when a port appeared in both a range and an individual rule. Fix acl_cmds_seen scope: the dedup shadow set was reset per service, so duplicate commands generated by two services on the same ACL table were not caught. Moved to per-table scope. Fix ip2me block rules using network address instead of interface IP for non-/32 non-VLAN interfaces. ip_ntwrk.network_address returned the network base address (e.g. 10.0.1.0 for 10.0.1.2/24) instead of the interface's own IP. Fix uses ipaddress.ip_interface(iface_cidr).ip. Fix empty management IP string passed to iptables -s/-d arguments in generate_allow_internal_docker_ip_traffic_commands when get_namespace_mgmt_ip fails. Guard added to return early with a warning when the management IP is unavailable. Tests: - Add dedup assertion to existing parameterized test harness - Add vector: IPv4 multi-port + src-ip - Add vector: L4_DST_PORT_RANGE + overlapping L4_DST_PORT - Add vector: service listed twice on same table - Add standalone two-call reload test: port deleted between reloads must not leak stale rules - Add vector: non-/32 non-VLAN interfaces block interface IP not network address - Add standalone test: empty management IP returns empty list without crash Signed-off-by: littlespace --- scripts/caclmgrd | 65 +- .../caclmgrd_external_client_acl_test.py | 180 +++- ...e_allow_internal_docker_ip_traffic_test.py | 66 ++ .../test_external_client_acl_vectors.py | 819 +++++++++++++++--- ...test_internal_docker_ip_traffic_vectors.py | 66 +- tests/caclmgrd/test_ip2me_vectors.py | 34 +- 6 files changed, 1085 insertions(+), 145 deletions(-) diff --git a/scripts/caclmgrd b/scripts/caclmgrd index 5c1137a6..b337f8f7 100755 --- a/scripts/caclmgrd +++ b/scripts/caclmgrd @@ -306,7 +306,9 @@ class ControlPlaneAclManager(logger.Logger): # For VLAN interfaces, the IP address we want to block is the default gateway (i.e., # the first available host IP address of the VLAN subnet) - ip_addr = next(ip_ntwrk.hosts()) if iface_table_name == "VLAN_INTERFACE" else ip_ntwrk.network_address + # Use ip_interface to get the actual interface IP (not the network address), + # which would be wrong for non-/32 non-VLAN interfaces (e.g. /24 gives 10.0.0.0). + ip_addr = next(ip_ntwrk.hosts()) if iface_table_name == "VLAN_INTERFACE" else ipaddress.ip_interface(iface_cidr).ip if isinstance(ip_ntwrk, ipaddress.IPv4Network): block_ip2me_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['iptables', '-A', 'INPUT', '-d', '{}/{}'.format(ip_addr, ip_ntwrk.max_prefixlen), '-j', 'DROP']) @@ -373,13 +375,19 @@ class ControlPlaneAclManager(logger.Logger): allow_internal_docker_ip_cmds = [] if namespace: - # For namespace docker allow local communication on docker management ip for all proto - allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['iptables', '-A', 'INPUT', '-s', self.namespace_docker_mgmt_ip[namespace], '-d', self.namespace_docker_mgmt_ip[namespace], '-j', 'ACCEPT']) - - allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['ip6tables', '-A', 'INPUT', '-s', self.namespace_docker_mgmt_ipv6[namespace], '-d', self.namespace_docker_mgmt_ipv6[namespace], '-j', 'ACCEPT']) - allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['iptables', '-A', 'INPUT', '-s', self.namespace_mgmt_ip, '-d', self.namespace_docker_mgmt_ip[namespace], '-j', 'ACCEPT']) - - allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['ip6tables', '-A', 'INPUT', '-s', self.namespace_mgmt_ipv6, '-d', self.namespace_docker_mgmt_ipv6[namespace], '-j', 'ACCEPT']) + # Guard each IP version independently: skip if either IP is unavailable + # to prevent empty strings being passed as iptables -s/-d arguments. + if self.namespace_docker_mgmt_ip.get(namespace) and self.namespace_mgmt_ip: + allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['iptables', '-A', 'INPUT', '-s', self.namespace_docker_mgmt_ip[namespace], '-d', self.namespace_docker_mgmt_ip[namespace], '-j', 'ACCEPT']) + allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['iptables', '-A', 'INPUT', '-s', self.namespace_mgmt_ip, '-d', self.namespace_docker_mgmt_ip[namespace], '-j', 'ACCEPT']) + else: + self.log_warning("Skipping IPv4 internal docker traffic rules for namespace '{}': IPv4 management IP unavailable".format(namespace)) + + if self.namespace_docker_mgmt_ipv6.get(namespace) and self.namespace_mgmt_ipv6: + allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['ip6tables', '-A', 'INPUT', '-s', self.namespace_docker_mgmt_ipv6[namespace], '-d', self.namespace_docker_mgmt_ipv6[namespace], '-j', 'ACCEPT']) + allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['ip6tables', '-A', 'INPUT', '-s', self.namespace_mgmt_ipv6, '-d', self.namespace_docker_mgmt_ipv6[namespace], '-j', 'ACCEPT']) + else: + self.log_warning("Skipping IPv6 internal docker traffic rules for namespace '{}': IPv6 management IP unavailable".format(namespace)) else: @@ -742,6 +750,11 @@ class ControlPlaneAclManager(logger.Logger): acl_services = table_data["services"] + # Shadow set for O(1) duplicate detection across all services in this table. + # Scoped per table (not per service) so cross-service duplicate commands are + # also caught. iptables_cmds stays a list to preserve rule ordering. + acl_cmds_seen = set() + for acl_service in acl_services: if acl_service not in self.ACL_SERVICES: self.log_warning("Ignoring control plane ACL '{}' with unrecognized service '{}'" @@ -782,18 +795,26 @@ class ControlPlaneAclManager(logger.Logger): elif self.is_rule_ipv4(rule_props): table_ip_version = 4 - # Read DST_PORT info from Config DB, insert it back to ACL_SERVICES + # Read DST_PORT info from Config DB into the local dst_ports list. + # Do NOT write back into ACL_SERVICES (class-level dict) — doing so + # would pollute subsequent reloads with stale ports from deleted rules. if acl_service == 'EXTERNAL_CLIENT' and "L4_DST_PORT" in rule_props: - dst_ports = [rule_props["L4_DST_PORT"]] - self.ACL_SERVICES[acl_service]["dst_ports"] = dst_ports + if rule_props["L4_DST_PORT"] not in dst_ports: + dst_ports.append(rule_props["L4_DST_PORT"]) elif acl_service == 'EXTERNAL_CLIENT' and "L4_DST_PORT_RANGE" in rule_props: - dst_ports = [] - port_ranges = rule_props["L4_DST_PORT_RANGE"].split("-") - port_start = int(port_ranges[0]) - port_end = int(port_ranges[1]) + try: + port_ranges = rule_props["L4_DST_PORT_RANGE"].split("-") + port_start = int(port_ranges[0]) + port_end = int(port_ranges[1]) + except (ValueError, IndexError): + self.log_error("Invalid L4_DST_PORT_RANGE value '{}' in ACL table '{}'. Skipping.".format( + rule_props["L4_DST_PORT_RANGE"], table_name)) + continue for port in range(port_start, port_end + 1): - dst_ports.append(port) - self.ACL_SERVICES[acl_service]["dst_ports"] = dst_ports + # Use str() to keep dst_ports type-consistent with L4_DST_PORT + port_str = str(port) + if port_str not in dst_ports: + dst_ports.append(port_str) if (self.is_rule_ipv6(rule_props) and (table_ip_version == 4)): self.log_error("CtrlPlane ACL table {} is a IPv4 based table and rule {} is a IPV6 rule! Ignoring rule." @@ -869,8 +890,14 @@ class ControlPlaneAclManager(logger.Logger): # Append the packet action as the jump target rule_cmd += ["-j", "{}".format(rule_props["PACKET_ACTION"])] - iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + rule_cmd) - num_ctrl_plane_acl_rules += 1 + # Deduplicate using a shadow set for O(1) lookup. + # iptables_cmds stays a list to preserve rule ordering. + full_cmd = self.iptables_cmd_ns_prefix[namespace] + rule_cmd + full_cmd_key = tuple(full_cmd) + if full_cmd_key not in acl_cmds_seen: + acl_cmds_seen.add(full_cmd_key) + iptables_cmds.append(full_cmd) + num_ctrl_plane_acl_rules += 1 service_to_source_ip_map.update({ acl_service:{ "ipv4":ipv4_src_ip_set, "ipv6":ipv6_src_ip_set } }) diff --git a/tests/caclmgrd/caclmgrd_external_client_acl_test.py b/tests/caclmgrd/caclmgrd_external_client_acl_test.py index bc5ce4f7..c9b11069 100644 --- a/tests/caclmgrd/caclmgrd_external_client_acl_test.py +++ b/tests/caclmgrd/caclmgrd_external_client_acl_test.py @@ -11,44 +11,188 @@ from tests.common.mock_configdb import MockConfigDb -DBCONFIG_PATH = '/var/run/redis/sonic-db/database_config.json' +DBCONFIG_PATH = "/var/run/redis/sonic-db/database_config.json" class TestCaclmgrdExternalClientAcl(TestCase): """ - Test caclmgrd EXTERNAL_CLIENT_ACL + Test caclmgrd EXTERNAL_CLIENT_ACL """ + def setUp(self): swsscommon.ConfigDBConnector = MockConfigDb test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) modules_path = os.path.dirname(test_path) scripts_path = os.path.join(modules_path, "scripts") sys.path.insert(0, modules_path) - caclmgrd_path = os.path.join(scripts_path, 'caclmgrd') - self.caclmgrd = load_module_from_source('caclmgrd', caclmgrd_path) + caclmgrd_path = os.path.join(scripts_path, "caclmgrd") + self.caclmgrd = load_module_from_source("caclmgrd", caclmgrd_path) @parameterized.expand(EXTERNAL_CLIENT_ACL_TEST_VECTOR) @patchfs def test_caclmgrd_external_client_acl(self, test_name, test_data, fs): if not os.path.exists(DBCONFIG_PATH): - fs.create_file(DBCONFIG_PATH) # fake database_config.json + fs.create_file(DBCONFIG_PATH) # fake database_config.json MockConfigDb.set_config_db(test_data["config_db"]) self.caclmgrd.ControlPlaneAclManager.get_namespace_mgmt_ip = mock.MagicMock() self.caclmgrd.ControlPlaneAclManager.get_namespace_mgmt_ipv6 = mock.MagicMock() - self.caclmgrd.ControlPlaneAclManager.generate_block_ip2me_traffic_iptables_commands = mock.MagicMock(return_value=[]) - self.caclmgrd.ControlPlaneAclManager.get_chain_list = mock.MagicMock(return_value=["INPUT", "FORWARD", "OUTPUT"]) - self.caclmgrd.ControlPlaneAclManager.get_chassis_midplane_interface_ip = mock.MagicMock(return_value='') + self.caclmgrd.ControlPlaneAclManager.generate_block_ip2me_traffic_iptables_commands = mock.MagicMock( + return_value=[] + ) + self.caclmgrd.ControlPlaneAclManager.get_chain_list = mock.MagicMock( + return_value=["INPUT", "FORWARD", "OUTPUT"] + ) + self.caclmgrd.ControlPlaneAclManager.get_chassis_midplane_interface_ip = ( + mock.MagicMock(return_value="") + ) caclmgrd_daemon = self.caclmgrd.ControlPlaneAclManager("caclmgrd") - iptables_rules_ret, _ = caclmgrd_daemon.get_acl_rules_and_translate_to_iptables_commands('', MockConfigDb()) - test_data['return'] = [tuple(i) for i in test_data['return']] + iptables_rules_ret, _ = ( + caclmgrd_daemon.get_acl_rules_and_translate_to_iptables_commands( + "", MockConfigDb() + ) + ) + test_data["return"] = [tuple(i) for i in test_data["return"]] iptables_rules_ret = [tuple(i) for i in iptables_rules_ret] - self.assertEqual(set(test_data["return"]).issubset(set(iptables_rules_ret)), True) - caclmgrd_daemon.iptables_cmd_ns_prefix['asic0'] = ['ip', 'netns', 'exec', 'asic0'] - caclmgrd_daemon.namespace_docker_mgmt_ip['asic0'] = '1.1.1.1' - caclmgrd_daemon.namespace_mgmt_ip = '2.2.2.2' - caclmgrd_daemon.namespace_docker_mgmt_ipv6['asic0'] = 'fd::01' - caclmgrd_daemon.namespace_mgmt_ipv6 = 'fd::02' - - _ = caclmgrd_daemon.generate_fwd_traffic_from_namespace_to_host_commands('asic0', None) + self.assertEqual( + set(test_data["return"]).issubset(set(iptables_rules_ret)), True + ) + # Assert no duplicate iptables rules are emitted (SONIC-103) + self.assertEqual(len(iptables_rules_ret), len(set(iptables_rules_ret))) + caclmgrd_daemon.iptables_cmd_ns_prefix["asic0"] = [ + "ip", + "netns", + "exec", + "asic0", + ] + caclmgrd_daemon.namespace_docker_mgmt_ip["asic0"] = "1.1.1.1" + caclmgrd_daemon.namespace_mgmt_ip = "2.2.2.2" + caclmgrd_daemon.namespace_docker_mgmt_ipv6["asic0"] = "fd::01" + caclmgrd_daemon.namespace_mgmt_ipv6 = "fd::02" + + _ = caclmgrd_daemon.generate_fwd_traffic_from_namespace_to_host_commands( + "asic0", None + ) + + @patchfs + def test_acl_services_not_polluted_across_reloads(self, fs): + """ + BUG 2 regression test: ACL_SERVICES class-level dict must not be mutated + with dst_ports from a previous call. If it were, a port deleted from + Config DB between two reloads would still appear in the second call's + iptables rules (stale port leak). + + Scenario: + - First call: EXTERNAL_CLIENT table has rules for ports 8081 and 8082. + - Second call (simulated reload): only port 8081 remains. + - Expected: second call produces no rules for port 8082. + """ + if not os.path.exists(DBCONFIG_PATH): + fs.create_file(DBCONFIG_PATH) + + self.caclmgrd.ControlPlaneAclManager.get_namespace_mgmt_ip = mock.MagicMock() + self.caclmgrd.ControlPlaneAclManager.get_namespace_mgmt_ipv6 = mock.MagicMock() + self.caclmgrd.ControlPlaneAclManager.generate_block_ip2me_traffic_iptables_commands = mock.MagicMock( + return_value=[] + ) + self.caclmgrd.ControlPlaneAclManager.get_chain_list = mock.MagicMock( + return_value=["INPUT", "FORWARD", "OUTPUT"] + ) + self.caclmgrd.ControlPlaneAclManager.get_chassis_midplane_interface_ip = ( + mock.MagicMock(return_value="") + ) + caclmgrd_daemon = self.caclmgrd.ControlPlaneAclManager("caclmgrd") + + # First reload: ports 8081 and 8082 both present + config_first = { + "ACL_TABLE": { + "EXTERNAL_CLIENT_ACL": { + "stage": "INGRESS", + "type": "CTRLPLANE", + "services": ["EXTERNAL_CLIENT"], + } + }, + "ACL_RULE": { + "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { + "ETHER_TYPE": "2048", + "PACKET_ACTION": "DROP", + "PRIORITY": "1", + }, + "EXTERNAL_CLIENT_ACL|RULE_1": { + "L4_DST_PORT": "8081", + "PACKET_ACTION": "ACCEPT", + "PRIORITY": "9998", + "SRC_IP": "10.0.0.1/32", + }, + "EXTERNAL_CLIENT_ACL|RULE_2": { + "L4_DST_PORT": "8082", + "PACKET_ACTION": "ACCEPT", + "PRIORITY": "9997", + "SRC_IP": "10.0.0.1/32", + }, + }, + "DEVICE_METADATA": {"localhost": {}}, + "FEATURE": {}, + } + MockConfigDb.set_config_db(config_first) + rules_first, _ = ( + caclmgrd_daemon.get_acl_rules_and_translate_to_iptables_commands( + "", MockConfigDb() + ) + ) + rules_first = [tuple(r) for r in rules_first] + self.assertIn( + ("iptables", "-A", "INPUT", "-p", "tcp", "--dport", "8081", "-j", "DROP"), + rules_first, + ) + self.assertIn( + ("iptables", "-A", "INPUT", "-p", "tcp", "--dport", "8082", "-j", "DROP"), + rules_first, + ) + + # Second reload: RULE_2 (port 8082) deleted from Config DB + config_second = { + "ACL_TABLE": { + "EXTERNAL_CLIENT_ACL": { + "stage": "INGRESS", + "type": "CTRLPLANE", + "services": ["EXTERNAL_CLIENT"], + } + }, + "ACL_RULE": { + "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { + "ETHER_TYPE": "2048", + "PACKET_ACTION": "DROP", + "PRIORITY": "1", + }, + "EXTERNAL_CLIENT_ACL|RULE_1": { + "L4_DST_PORT": "8081", + "PACKET_ACTION": "ACCEPT", + "PRIORITY": "9998", + "SRC_IP": "10.0.0.1/32", + }, + }, + "DEVICE_METADATA": {"localhost": {}}, + "FEATURE": {}, + } + MockConfigDb.set_config_db(config_second) + rules_second, _ = ( + caclmgrd_daemon.get_acl_rules_and_translate_to_iptables_commands( + "", MockConfigDb() + ) + ) + rules_second = [tuple(r) for r in rules_second] + + # Port 8081 must still be present + self.assertIn( + ("iptables", "-A", "INPUT", "-p", "tcp", "--dport", "8081", "-j", "DROP"), + rules_second, + ) + # Port 8082 must NOT appear — it was deleted and ACL_SERVICES must not carry stale state + port_8082_rules = [r for r in rules_second if "--dport" in r and "8082" in r] + self.assertEqual( + port_8082_rules, + [], + "Stale port 8082 rules found after reload — ACL_SERVICES class dict was polluted", + ) diff --git a/tests/caclmgrd/caclmgrd_generate_allow_internal_docker_ip_traffic_test.py b/tests/caclmgrd/caclmgrd_generate_allow_internal_docker_ip_traffic_test.py index 474dd52a..b6495fc1 100644 --- a/tests/caclmgrd/caclmgrd_generate_allow_internal_docker_ip_traffic_test.py +++ b/tests/caclmgrd/caclmgrd_generate_allow_internal_docker_ip_traffic_test.py @@ -36,3 +36,69 @@ def test_caclmgrd_internal_docker_ip_traffic(self, test_name, test_data, fs): ret = caclmgrd_daemon.generate_allow_internal_docker_ip_traffic_commands('asic0') self.assertListEqual(test_data["return"], ret) + + @patchfs + def test_empty_mgmt_ip_returns_empty_list(self, fs): + """ + BUG 7 regression test: when both IPv4 and IPv6 management IPs are empty, + generate_allow_internal_docker_ip_traffic_commands must return an empty + list without raising an exception or passing empty strings to iptables. + """ + self.caclmgrd.ControlPlaneAclManager.get_namespace_mgmt_ip = mock.MagicMock() + self.caclmgrd.ControlPlaneAclManager.get_namespace_mgmt_ipv6 = mock.MagicMock() + caclmgrd_daemon = self.caclmgrd.ControlPlaneAclManager("caclmgrd") + caclmgrd_daemon.iptables_cmd_ns_prefix['asic0'] = ['ip', 'netns', 'exec', 'asic0'] + caclmgrd_daemon.namespace_docker_mgmt_ip['asic0'] = '' + caclmgrd_daemon.namespace_mgmt_ip = '' + caclmgrd_daemon.namespace_docker_mgmt_ipv6['asic0'] = '' + caclmgrd_daemon.namespace_mgmt_ipv6 = '' + + ret = caclmgrd_daemon.generate_allow_internal_docker_ip_traffic_commands('asic0') + self.assertListEqual([], ret, + "Expected empty list when all management IPs are unavailable") + + @patchfs + def test_ipv4_valid_ipv6_empty_skips_only_ipv6_rules(self, fs): + """ + BUG 7 partial-failure regression test: when IPv4 management IPs are valid + but IPv6 management IPs are empty, only IPv6 rules must be skipped. + IPv4 rules must still be generated correctly. + """ + self.caclmgrd.ControlPlaneAclManager.get_namespace_mgmt_ip = mock.MagicMock() + self.caclmgrd.ControlPlaneAclManager.get_namespace_mgmt_ipv6 = mock.MagicMock() + caclmgrd_daemon = self.caclmgrd.ControlPlaneAclManager("caclmgrd") + caclmgrd_daemon.iptables_cmd_ns_prefix['asic0'] = ['ip', 'netns', 'exec', 'asic0'] + caclmgrd_daemon.namespace_docker_mgmt_ip['asic0'] = '1.1.1.1/32' + caclmgrd_daemon.namespace_mgmt_ip = '2.2.2.2/32' + caclmgrd_daemon.namespace_docker_mgmt_ipv6['asic0'] = '' + caclmgrd_daemon.namespace_mgmt_ipv6 = '' + + ret = caclmgrd_daemon.generate_allow_internal_docker_ip_traffic_commands('asic0') + ret_cmds = [' '.join(r) for r in ret] + self.assertTrue(any('iptables' in c and '1.1.1.1' in c for c in ret_cmds), + "Expected IPv4 rules to be generated when IPv4 mgmt IP is valid") + self.assertFalse(any('ip6tables' in c for c in ret_cmds), + "Expected no ip6tables rules when IPv6 mgmt IP is empty") + + @patchfs + def test_ipv6_valid_ipv4_empty_skips_only_ipv4_rules(self, fs): + """ + BUG 7 partial-failure regression test: when IPv6 management IPs are valid + but IPv4 management IPs are empty, only IPv4 rules must be skipped. + IPv6 rules must still be generated correctly. + """ + self.caclmgrd.ControlPlaneAclManager.get_namespace_mgmt_ip = mock.MagicMock() + self.caclmgrd.ControlPlaneAclManager.get_namespace_mgmt_ipv6 = mock.MagicMock() + caclmgrd_daemon = self.caclmgrd.ControlPlaneAclManager("caclmgrd") + caclmgrd_daemon.iptables_cmd_ns_prefix['asic0'] = ['ip', 'netns', 'exec', 'asic0'] + caclmgrd_daemon.namespace_docker_mgmt_ip['asic0'] = '' + caclmgrd_daemon.namespace_mgmt_ip = '' + caclmgrd_daemon.namespace_docker_mgmt_ipv6['asic0'] = 'fd::01/128' + caclmgrd_daemon.namespace_mgmt_ipv6 = 'fd::02/128' + + ret = caclmgrd_daemon.generate_allow_internal_docker_ip_traffic_commands('asic0') + ret_cmds = [' '.join(r) for r in ret] + self.assertTrue(any('ip6tables' in c and 'fd::01' in c for c in ret_cmds), + "Expected ip6tables rules to be generated when IPv6 mgmt IP is valid") + self.assertFalse(any(c.split()[4] == 'iptables' for c in ret_cmds), + "Expected no iptables rules when IPv4 mgmt IP is empty") diff --git a/tests/caclmgrd/test_external_client_acl_vectors.py b/tests/caclmgrd/test_external_client_acl_vectors.py index 74e10cc3..7942b4ad 100644 --- a/tests/caclmgrd/test_external_client_acl_vectors.py +++ b/tests/caclmgrd/test_external_client_acl_vectors.py @@ -12,32 +12,26 @@ "EXTERNAL_CLIENT_ACL": { "stage": "INGRESS", "type": "CTRLPLANE", - "services": [ - "EXTERNAL_CLIENT" - ] + "services": ["EXTERNAL_CLIENT"], } }, "ACL_RULE": { "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { "ETHER_TYPE": "2048", "PACKET_ACTION": "DROP", - "PRIORITY": "1" + "PRIORITY": "1", }, "EXTERNAL_CLIENT_ACL|RULE_1": { "PACKET_ACTION": "ACCEPT", "PRIORITY": "9998", - "SRC_IP": "20.0.0.55/32" + "SRC_IP": "20.0.0.55/32", }, }, - "DEVICE_METADATA": { - "localhost": { - } - }, + "DEVICE_METADATA": {"localhost": {}}, "FEATURE": {}, }, - "return": [ - ], - } + "return": [], + }, ], [ "Test single IPv4 dst port + src ip for EXTERNAL_CLIENT_ACL", @@ -47,35 +41,52 @@ "EXTERNAL_CLIENT_ACL": { "stage": "INGRESS", "type": "CTRLPLANE", - "services": [ - "EXTERNAL_CLIENT" - ] + "services": ["EXTERNAL_CLIENT"], } }, "ACL_RULE": { "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { "ETHER_TYPE": "2048", "PACKET_ACTION": "DROP", - "PRIORITY": "1" + "PRIORITY": "1", }, "EXTERNAL_CLIENT_ACL|RULE_1": { "L4_DST_PORT": "8081", "PACKET_ACTION": "ACCEPT", "PRIORITY": "9998", - "SRC_IP": "20.0.0.55/32" + "SRC_IP": "20.0.0.55/32", }, }, - "DEVICE_METADATA": { - "localhost": { - } - }, + "DEVICE_METADATA": {"localhost": {}}, "FEATURE": {}, }, "return": [ - ['iptables', '-A', 'INPUT', '-p', 'tcp', '-s', '20.0.0.55/32', '--dport', '8081', '-j', 'ACCEPT'], - ['iptables', '-A', 'INPUT', '-p', 'tcp', '--dport', '8081', '-j', 'DROP'] + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "20.0.0.55/32", + "--dport", + "8081", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8081", + "-j", + "DROP", + ], ], - } + }, ], [ "Test single IPv4 dst port + dst ip for EXTERNAL_CLIENT_ACL", @@ -85,35 +96,52 @@ "EXTERNAL_CLIENT_ACL": { "stage": "INGRESS", "type": "CTRLPLANE", - "services": [ - "EXTERNAL_CLIENT" - ] + "services": ["EXTERNAL_CLIENT"], } }, "ACL_RULE": { "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { "ETHER_TYPE": "2048", "PACKET_ACTION": "DROP", - "PRIORITY": "1" + "PRIORITY": "1", }, "EXTERNAL_CLIENT_ACL|RULE_1": { "L4_DST_PORT": "8081", "PACKET_ACTION": "ACCEPT", "PRIORITY": "9998", - "DST_IP": "20.0.0.66/32" + "DST_IP": "20.0.0.66/32", }, }, - "DEVICE_METADATA": { - "localhost": { - } - }, + "DEVICE_METADATA": {"localhost": {}}, "FEATURE": {}, }, "return": [ - ['iptables', '-A', 'INPUT', '-p', 'tcp', '-d', '20.0.0.66/32', '--dport', '8081', '-j', 'ACCEPT'], - ['iptables', '-A', 'INPUT', '-p', 'tcp', '--dport', '8081', '-j', 'DROP'] + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-d", + "20.0.0.66/32", + "--dport", + "8081", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8081", + "-j", + "DROP", + ], ], - } + }, ], [ "Test single IPv4 dst port + incoming interface for EXTERNAL_CLIENT_ACL", @@ -123,36 +151,55 @@ "EXTERNAL_CLIENT_ACL": { "stage": "INGRESS", "type": "CTRLPLANE", - "services": [ - "EXTERNAL_CLIENT" - ] + "services": ["EXTERNAL_CLIENT"], } }, "ACL_RULE": { "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { "ETHER_TYPE": "2048", "PACKET_ACTION": "DROP", - "PRIORITY": "1" + "PRIORITY": "1", }, "EXTERNAL_CLIENT_ACL|RULE_1": { "L4_DST_PORT": "8081", "PACKET_ACTION": "ACCEPT", "PRIORITY": "9998", "DST_IP": "0.0.0.0/0", - "IN_PORTS": "mgmt" + "IN_PORTS": "mgmt", }, }, - "DEVICE_METADATA": { - "localhost": { - } - }, + "DEVICE_METADATA": {"localhost": {}}, "FEATURE": {}, }, "return": [ - ['iptables', '-A', 'INPUT', '-p', 'tcp', '-d', '0.0.0.0/0', '-i', 'mgmt', '--dport', '8081', '-j', 'ACCEPT'], - ['iptables', '-A', 'INPUT', '-p', 'tcp', '--dport', '8081', '-j', 'DROP'] + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-d", + "0.0.0.0/0", + "-i", + "mgmt", + "--dport", + "8081", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8081", + "-j", + "DROP", + ], ], - } + }, ], [ "Test IPv4 dst port range + src ip forEXTERNAL_CLIENT_ACL", @@ -162,39 +209,100 @@ "EXTERNAL_CLIENT_ACL": { "stage": "INGRESS", "type": "CTRLPLANE", - "services": [ - "EXTERNAL_CLIENT" - ] + "services": ["EXTERNAL_CLIENT"], } }, "ACL_RULE": { "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { "ETHER_TYPE": "2048", "PACKET_ACTION": "DROP", - "PRIORITY": "1" + "PRIORITY": "1", }, "EXTERNAL_CLIENT_ACL|RULE_1": { "L4_DST_PORT_RANGE": "8081-8083", "PACKET_ACTION": "ACCEPT", "PRIORITY": "9998", - "SRC_IP": "20.0.0.55/32" + "SRC_IP": "20.0.0.55/32", }, }, - "DEVICE_METADATA": { - "localhost": { - } - }, + "DEVICE_METADATA": {"localhost": {}}, "FEATURE": {}, }, "return": [ - ['iptables', '-A', 'INPUT', '-p', 'tcp', '-s', '20.0.0.55/32', '--dport', '8081', '-j', 'ACCEPT'], - ['iptables', '-A', 'INPUT', '-p', 'tcp', '-s', '20.0.0.55/32', '--dport', '8082', '-j', 'ACCEPT'], - ['iptables', '-A', 'INPUT', '-p', 'tcp', '-s', '20.0.0.55/32', '--dport', '8083', '-j', 'ACCEPT'], - ['iptables', '-A', 'INPUT', '-p', 'tcp', '--dport', '8081', '-j', 'DROP'], - ['iptables', '-A', 'INPUT', '-p', 'tcp', '--dport', '8082', '-j', 'DROP'], - ['iptables', '-A', 'INPUT', '-p', 'tcp', '--dport', '8083', '-j', 'DROP'], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "20.0.0.55/32", + "--dport", + "8081", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "20.0.0.55/32", + "--dport", + "8082", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "20.0.0.55/32", + "--dport", + "8083", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8081", + "-j", + "DROP", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8082", + "-j", + "DROP", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8083", + "-j", + "DROP", + ], ], - } + }, ], [ "Test IPv6 single dst port range + src ip forEXTERNAL_CLIENT_ACL", @@ -204,35 +312,52 @@ "EXTERNAL_CLIENT_ACL": { "stage": "INGRESS", "type": "CTRLPLANE", - "services": [ - "EXTERNAL_CLIENT" - ] + "services": ["EXTERNAL_CLIENT"], } }, "ACL_RULE": { "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { "ETHER_TYPE": "2048", "PACKET_ACTION": "DROP", - "PRIORITY": "1" + "PRIORITY": "1", }, "EXTERNAL_CLIENT_ACL|RULE_1": { "L4_DST_PORT": "8081", "PACKET_ACTION": "ACCEPT", "PRIORITY": "9998", - "SRC_IP": "2001::2/128" + "SRC_IP": "2001::2/128", }, }, - "DEVICE_METADATA": { - "localhost": { - } - }, + "DEVICE_METADATA": {"localhost": {}}, "FEATURE": {}, }, "return": [ - ['iptables', '-A', 'INPUT', '-p', 'tcp', '-s', '2001::2/128', '--dport', '8081', '-j', 'ACCEPT'], - ['iptables', '-A', 'INPUT', '-p', 'tcp', '--dport', '8081', '-j', 'DROP'] + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "2001::2/128", + "--dport", + "8081", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8081", + "-j", + "DROP", + ], ], - } + }, ], [ "Test IPv6 single dst port range + dst ip forEXTERNAL_CLIENT_ACL", @@ -242,76 +367,566 @@ "EXTERNAL_CLIENT_ACL": { "stage": "INGRESS", "type": "CTRLPLANE", - "services": [ - "EXTERNAL_CLIENT" - ] + "services": ["EXTERNAL_CLIENT"], } }, "ACL_RULE": { "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { "ETHER_TYPE": "2048", "PACKET_ACTION": "DROP", - "PRIORITY": "1" + "PRIORITY": "1", }, "EXTERNAL_CLIENT_ACL|RULE_1": { "L4_DST_PORT": "8081", "PACKET_ACTION": "ACCEPT", "PRIORITY": "9998", - "DST_IP": "2001::6/128" + "DST_IP": "2001::6/128", }, }, - "DEVICE_METADATA": { - "localhost": { + "DEVICE_METADATA": {"localhost": {}}, + "FEATURE": {}, + }, + "return": [ + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-d", + "2001::6/128", + "--dport", + "8081", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8081", + "-j", + "DROP", + ], + ], + }, + ], + [ + "Test IPv6 dst port range + src ip forEXTERNAL_CLIENT_ACL", + { + "config_db": { + "ACL_TABLE": { + "EXTERNAL_CLIENT_ACL": { + "stage": "INGRESS", + "type": "CTRLPLANE", + "services": ["EXTERNAL_CLIENT"], } }, + "ACL_RULE": { + "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { + "ETHER_TYPE": "2048", + "PACKET_ACTION": "DROP", + "PRIORITY": "1", + }, + "EXTERNAL_CLIENT_ACL|RULE_1": { + "L4_DST_PORT_RANGE": "8081-8083", + "PACKET_ACTION": "ACCEPT", + "PRIORITY": "9998", + "SRC_IP": "2001::2/128", + }, + }, + "DEVICE_METADATA": {"localhost": {}}, "FEATURE": {}, }, "return": [ - ['iptables', '-A', 'INPUT', '-p', 'tcp', '-d', '2001::6/128', '--dport', '8081', '-j', 'ACCEPT'], - ['iptables', '-A', 'INPUT', '-p', 'tcp', '--dport', '8081', '-j', 'DROP'] + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "2001::2/128", + "--dport", + "8081", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "2001::2/128", + "--dport", + "8082", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "2001::2/128", + "--dport", + "8083", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8081", + "-j", + "DROP", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8082", + "-j", + "DROP", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8083", + "-j", + "DROP", + ], ], - } + }, ], [ - "Test IPv6 dst port range + src ip forEXTERNAL_CLIENT_ACL", + "Test IPv4 multiple dst port + src ip for EXTERNAL_CLIENT_ACL", + { + "config_db": { + "ACL_TABLE": { + "EXTERNAL_CLIENT_ACL": { + "stage": "INGRESS", + "type": "CTRLPLANE", + "services": ["EXTERNAL_CLIENT"], + } + }, + "ACL_RULE": { + "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { + "ETHER_TYPE": "2048", + "PACKET_ACTION": "DROP", + "PRIORITY": "1", + }, + "EXTERNAL_CLIENT_ACL|RULE_1": { + "L4_DST_PORT": "8081", + "PACKET_ACTION": "ACCEPT", + "PRIORITY": "9998", + "SRC_IP": "20.0.0.55/32", + }, + "EXTERNAL_CLIENT_ACL|RULE_2": { + "L4_DST_PORT": "8082", + "PACKET_ACTION": "ACCEPT", + "PRIORITY": "9997", + "SRC_IP": "20.0.0.55/32", + }, + "EXTERNAL_CLIENT_ACL|RULE_3": { + "L4_DST_PORT": "8083", + "PACKET_ACTION": "ACCEPT", + "PRIORITY": "9996", + "SRC_IP": "20.0.0.55/32", + }, + }, + "DEVICE_METADATA": {"localhost": {}}, + "FEATURE": {}, + }, + "return": [ + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "20.0.0.55/32", + "--dport", + "8081", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "20.0.0.55/32", + "--dport", + "8082", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "20.0.0.55/32", + "--dport", + "8083", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8081", + "-j", + "DROP", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8082", + "-j", + "DROP", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8083", + "-j", + "DROP", + ], + ], + }, + ], + [ + # BUG 4: L4_DST_PORT_RANGE and L4_DST_PORT in the same table where one port + # overlaps. Before the fix, L4_DST_PORT_RANGE appended ints and L4_DST_PORT + # appended strs, so "8083" not in [8081, 8082, 8083] (int) was True and the + # port was added twice, producing duplicate --dport 8083 rules. + "Test EXTERNAL_CLIENT_ACL L4_DST_PORT_RANGE processed before L4_DST_PORT - no duplicate rules", { "config_db": { "ACL_TABLE": { "EXTERNAL_CLIENT_ACL": { "stage": "INGRESS", "type": "CTRLPLANE", - "services": [ - "EXTERNAL_CLIENT" - ] + "services": ["EXTERNAL_CLIENT"], } }, "ACL_RULE": { "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { "ETHER_TYPE": "2048", "PACKET_ACTION": "DROP", - "PRIORITY": "1" + "PRIORITY": "1", }, "EXTERNAL_CLIENT_ACL|RULE_1": { "L4_DST_PORT_RANGE": "8081-8083", "PACKET_ACTION": "ACCEPT", "PRIORITY": "9998", - "SRC_IP": "2001::2/128" + "SRC_IP": "20.0.0.55/32", + }, + "EXTERNAL_CLIENT_ACL|RULE_2": { + "L4_DST_PORT": "8083", + "PACKET_ACTION": "ACCEPT", + "PRIORITY": "9997", + "SRC_IP": "20.0.0.66/32", + }, + }, + "DEVICE_METADATA": {"localhost": {}}, + "FEATURE": {}, + }, + "return": [ + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "20.0.0.55/32", + "--dport", + "8081", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "20.0.0.55/32", + "--dport", + "8082", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "20.0.0.55/32", + "--dport", + "8083", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "20.0.0.66/32", + "--dport", + "8083", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8081", + "-j", + "DROP", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8082", + "-j", + "DROP", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8083", + "-j", + "DROP", + ], + ], + }, + ], + [ + # BUG 3: A service listed twice in the same ACL table's services list causes + # every iptables rule to be emitted twice. Before the fix, acl_cmds_seen was + # reset per service so cross-service duplicates were not caught. + "Test NTP ACL table with service listed twice - no duplicate rules emitted", + { + "config_db": { + "ACL_TABLE": { + "NTP_ACL": { + "stage": "INGRESS", + "type": "CTRLPLANE", + "services": ["NTP", "NTP"], + } + }, + "ACL_RULE": { + "NTP_ACL|DEFAULT_RULE": { + "ETHER_TYPE": "2048", + "PACKET_ACTION": "DROP", + "PRIORITY": "1", + }, + "NTP_ACL|RULE_1": { + "PACKET_ACTION": "ACCEPT", + "PRIORITY": "9998", + "SRC_IP": "10.0.0.1/32", }, }, - "DEVICE_METADATA": { - "localhost": { + "DEVICE_METADATA": {"localhost": {}}, + "FEATURE": {}, + }, + "return": [ + [ + "iptables", + "-A", + "INPUT", + "-p", + "udp", + "-s", + "10.0.0.1/32", + "--dport", + "123", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "udp", + "--dport", + "123", + "-j", + "DROP", + ], + ], + }, + ], + [ + # Critical regression: if L4_DST_PORT rules are processed before the + # L4_DST_PORT_RANGE rule (higher priority = processed first when sorted + # descending), the range branch must accumulate rather than reset dst_ports. + # Before the fix, dst_ports = [] silently discarded ports already accumulated + # by prior L4_DST_PORT rules. + "Test EXTERNAL_CLIENT_ACL L4_DST_PORT processed before L4_DST_PORT_RANGE - range must not reset", + { + "config_db": { + "ACL_TABLE": { + "EXTERNAL_CLIENT_ACL": { + "stage": "INGRESS", + "type": "CTRLPLANE", + "services": ["EXTERNAL_CLIENT"], } }, + "ACL_RULE": { + "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { + "ETHER_TYPE": "2048", + "PACKET_ACTION": "DROP", + "PRIORITY": "1", + }, + "EXTERNAL_CLIENT_ACL|RULE_1": { + "L4_DST_PORT": "9000", + "PACKET_ACTION": "ACCEPT", + "PRIORITY": "9998", + "SRC_IP": "10.0.0.1/32", + }, + "EXTERNAL_CLIENT_ACL|RULE_2": { + "L4_DST_PORT_RANGE": "8081-8082", + "PACKET_ACTION": "ACCEPT", + "PRIORITY": "9997", + "SRC_IP": "10.0.0.1/32", + }, + }, + "DEVICE_METADATA": {"localhost": {}}, "FEATURE": {}, }, "return": [ - ['iptables', '-A', 'INPUT', '-p', 'tcp', '-s', '2001::2/128', '--dport', '8081', '-j', 'ACCEPT'], - ['iptables', '-A', 'INPUT', '-p', 'tcp', '-s', '2001::2/128', '--dport', '8082', '-j', 'ACCEPT'], - ['iptables', '-A', 'INPUT', '-p', 'tcp', '-s', '2001::2/128', '--dport', '8083', '-j', 'ACCEPT'], - ['iptables', '-A', 'INPUT', '-p', 'tcp', '--dport', '8081', '-j', 'DROP'], - ['iptables', '-A', 'INPUT', '-p', 'tcp', '--dport', '8082', '-j', 'DROP'], - ['iptables', '-A', 'INPUT', '-p', 'tcp', '--dport', '8083', '-j', 'DROP'], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "10.0.0.1/32", + "--dport", + "9000", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "10.0.0.1/32", + "--dport", + "8081", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "-s", + "10.0.0.1/32", + "--dport", + "8082", + "-j", + "ACCEPT", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "9000", + "-j", + "DROP", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8081", + "-j", + "DROP", + ], + [ + "iptables", + "-A", + "INPUT", + "-p", + "tcp", + "--dport", + "8082", + "-j", + "DROP", + ], ], - } - ] + }, + ], ] diff --git a/tests/caclmgrd/test_internal_docker_ip_traffic_vectors.py b/tests/caclmgrd/test_internal_docker_ip_traffic_vectors.py index 2f584bb0..08f9be36 100644 --- a/tests/caclmgrd/test_internal_docker_ip_traffic_vectors.py +++ b/tests/caclmgrd/test_internal_docker_ip_traffic_vectors.py @@ -8,11 +8,67 @@ "Allow internal docker traffic", { "return": [ - ['ip', 'netns', 'exec', 'asic0', 'iptables', '-A', 'INPUT', '-s', '1.1.1.1/32', '-d', '1.1.1.1/32', '-j', 'ACCEPT'], - ['ip', 'netns', 'exec', 'asic0', 'ip6tables', '-A', 'INPUT', '-s', 'fd::01/128', '-d', 'fd::01/128', '-j', 'ACCEPT'], - ['ip', 'netns', 'exec', 'asic0', 'iptables', '-A', 'INPUT', '-s', '2.2.2.2/32', '-d', '1.1.1.1/32', '-j', 'ACCEPT'], - ['ip', 'netns', 'exec', 'asic0', 'ip6tables', '-A', 'INPUT', '-s', 'fd::02/128', '-d', 'fd::01/128', '-j', 'ACCEPT'] + [ + "ip", + "netns", + "exec", + "asic0", + "iptables", + "-A", + "INPUT", + "-s", + "1.1.1.1/32", + "-d", + "1.1.1.1/32", + "-j", + "ACCEPT", + ], + [ + "ip", + "netns", + "exec", + "asic0", + "iptables", + "-A", + "INPUT", + "-s", + "2.2.2.2/32", + "-d", + "1.1.1.1/32", + "-j", + "ACCEPT", + ], + [ + "ip", + "netns", + "exec", + "asic0", + "ip6tables", + "-A", + "INPUT", + "-s", + "fd::01/128", + "-d", + "fd::01/128", + "-j", + "ACCEPT", + ], + [ + "ip", + "netns", + "exec", + "asic0", + "ip6tables", + "-A", + "INPUT", + "-s", + "fd::02/128", + "-d", + "fd::01/128", + "-j", + "ACCEPT", + ], ] - } + }, ] ] diff --git a/tests/caclmgrd/test_ip2me_vectors.py b/tests/caclmgrd/test_ip2me_vectors.py index 4cb75a32..f4b59b12 100644 --- a/tests/caclmgrd/test_ip2me_vectors.py +++ b/tests/caclmgrd/test_ip2me_vectors.py @@ -120,4 +120,36 @@ ], }, ] -] + , + [ + # BUG 9 regression: non-/32 non-VLAN interface IP must be the interface's own + # IP, not the network address. Before the fix, Ethernet0|10.0.1.2/24 would + # produce -d 10.0.1.0/32 (network address) instead of -d 10.0.1.2/32. + "Non-/32 INTERFACE and PORTCHANNEL entries block interface IP not network address", + { + "config_db": { + "LOOPBACK_INTERFACE": {}, + "VLAN_INTERFACE": {}, + "PORTCHANNEL_INTERFACE": { + "PortChannel0001|10.0.1.2/24": {}, + }, + "INTERFACE": { + "Ethernet0|10.0.2.5/30": {} + }, + "MGMT_INTERFACE": { + "eth0|172.18.0.100/24": { + "gwaddr": "172.18.0.1" + } + }, + "DEVICE_METADATA": { + "localhost": {} + }, + "FEATURE": {}, + }, + "return": [ + ['iptables', '-A', 'INPUT', '-d', '10.0.1.2/32', '-j', 'DROP'], + ['iptables', '-A', 'INPUT', '-d', '10.0.2.5/32', '-j', 'DROP'], + ], + }, + ] +] \ No newline at end of file