From 7ff74c11a620b8ba0eb7e3a37bed2501acffadc6 Mon Sep 17 00:00:00 2001 From: Tianrong Zhang Date: Sat, 17 Jul 2021 16:52:10 -0700 Subject: [PATCH 1/4] dhcp acl --- src/sonic-host-services/scripts/caclmgrd | 132 +++++++++++++++++++++-- 1 file changed, 122 insertions(+), 10 deletions(-) diff --git a/src/sonic-host-services/scripts/caclmgrd b/src/sonic-host-services/scripts/caclmgrd index 62783eb98df0..36847861451e 100755 --- a/src/sonic-host-services/scripts/caclmgrd +++ b/src/sonic-host-services/scripts/caclmgrd @@ -54,6 +54,8 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): """ ACL_TABLE = "ACL_TABLE" ACL_RULE = "ACL_RULE" + DEVICE_METADATA_TABLE = "DEVICE_METADATA" + MUX_CABLE_TABLE = "MUX_CABLE_TABLE" ACL_TABLE_TYPE_CTRLPLANE = "CTRLPLANE" @@ -84,6 +86,8 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): UPDATE_DELAY_SECS = 0.5 + DualToR = 0 + def __init__(self, log_identifier): super(ControlPlaneAclManager, self).__init__(log_identifier) @@ -108,6 +112,10 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): self.namespace_docker_mgmt_ip = {} self.namespace_docker_mgmt_ipv6 = {} + metadata = self.config_db_map[DEFAULT_NAMESPACE].get_table(self.DEVICE_METADATA_TABLE) + if 'subtype' in metadata['localhost'] and metadata['localhost']['subtype'] == 'DualToR': + self.DualToR = 1 + namespaces = device_info.get_all_namespaces() for front_asic_namespace in namespaces['front_ns']: self.update_thread[front_asic_namespace] = None @@ -319,6 +327,77 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): else: return False + def setup_dhcp_chain(self, namespace): + all_chains = self.get_chain_list(self.iptables_cmd_ns_prefix[namespace], [""]) + dhcp_chain_exist = 1 if "DHCP" in all_chains else 0 + + iptables_cmds = [] + if dhcp_chain_exist: + iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -F DHCP") + self.log_info("DHCP chain exists, flush") + else: + iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -N DHCP") + self.log_info("DHCP chain does not exist, create") + iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A DHCP -j RETURN") + + self.log_info("Issuing the following iptables commands for DHCP chain:") + for cmd in iptables_cmds: + self.log_info(" " + cmd) + + self.run_commands(iptables_cmds) + + def get_chain_list(self, iptable_ns_cmd_prefix, exclude_list): + command = iptable_ns_cmd_prefix + "iptables -L -v -n | grep Chain | awk '{print $2}'" + chain_list = self.run_commands([command]).splitlines() + + for chain in exclude_list: + if chain in chain_list: + chain_list.remove(chain) + + return chain_list + + def dhcp_acl_rule(self, iptable_ns_cmd_prefix, op, intf): + ''' + sample: iptables --insert/delete/check DHCP -m physdev --physdev-in Ethernet4 -j DROP + ''' + return iptable_ns_cmd_prefix + 'iptables --{} DHCP -m physdev --physdev-in {} -j DROP'.format(op, intf) + + def update_dhcp_chain(self, op, intf): + for namespace in list(self.config_db_map.keys()): + check_cmd = self.dhcp_acl_rule(self.iptables_cmd_ns_prefix[namespace], "check", intf) + update_cmd = self.dhcp_acl_rule(self.iptables_cmd_ns_prefix[namespace], op, intf) + + execute = 0 + ret = subprocess.call(check_cmd, shell=True) # ret==0 indicates the rule exists + + if op == "insert" and ret == 1: + execute = 1 + if op == "delete" and ret == 0: + execute = 1 + + if execute == 1: + self.run_commands([update_cmd]) + self.log_info("Update DHCP chain: {}".format(update_cmd)) + + def update_dhcp_acl(self, key, op, data): + if "status" not in data: + self.log_warning("Unexpected update in MUX_CABLE_TABLE") + return + + intf = key + status = data["status"] + + if status == "active": + self.update_dhcp_chain("delete", intf) + elif status == "standby": + self.update_dhcp_chain("insert", intf) + elif status == "unknown": + self.update_dhcp_chain("delete", intf) + elif status == "error": + self.log_warning("Cable status shows error") + else: + self.log_warning("Unexpected cable status") + def get_acl_rules_and_translate_to_iptables_commands(self, namespace): """ Retrieves current ACL tables and rules from Config DB, translates @@ -337,11 +416,12 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -P FORWARD ACCEPT") iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -P OUTPUT ACCEPT") - # Add iptables command to flush the current rules - iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -F") - - # Add iptables command to delete all non-default chains - iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -X") + # Add iptables command to flush the current rules and delete all non-default chains + chain_list = self.get_chain_list(self.iptables_cmd_ns_prefix[namespace], ["DHCP"] if self.DualToR else [""]) + for chain in chain_list: + iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -F " + chain) + if chain not in ["INPUT", "FORWARD", "OUTPUT"]: + iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -X " + chain) # Add same set of commands for ip6tables iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -P INPUT ACCEPT") @@ -383,6 +463,10 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -A INPUT -p icmpv6 --icmpv6-type router-solicitation -j ACCEPT") iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -A INPUT -p icmpv6 --icmpv6-type router-advertisement -j ACCEPT") + # Add iptables commands to link the DCHP chain to block dhcp packets based on ingress interfaces + if self.DualToR: + iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -p udp --dport 67 -j DHCP") + # Add iptables/ip6tables commands to allow all incoming IPv4 DHCP packets iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -p udp --dport 67:68 -j ACCEPT") iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -A INPUT -p udp --dport 67:68 -j ACCEPT") @@ -617,9 +701,26 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): # Create the Select object sel = swsscommon.Select() + + # Set up STATE_DB connector to monitor the change in MUX_CABLE_TABLE + state_db_connector = None + subscribe_mux_cable = None + if self.DualToR: + self.log_info("Dual ToR mode") + + # set up state_db connector + state_db_id = swsscommon.SonicDBConfig.getDbId("STATE_DB") + state_db_connector = swsscommon.DBConnector("STATE_DB", 0) + subscribe_mux_cable = swsscommon.SubscriberStateTable(state_db_connector, self.MUX_CABLE_TABLE) + sel.addSelectable(subscribe_mux_cable) + + # create DHCP chain + for namespace in list(self.config_db_map.keys()): + self.setup_dhcp_chain(namespace) + # Map of Namespace <--> susbcriber table's object config_db_subscriber_table_map = {} - + # Loop through all asic namespaces (if present) and host namespace (DEFAULT_NAMESPACE) for namespace in list(self.config_db_map.keys()): # Unconditionally update control plane ACLs once at start on given namespace @@ -641,10 +742,8 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): # Get the ACL rule table seprator acl_rule_table_seprator = subscribe_acl_rule_table.getTableNameSeparator() - # Loop on select to see if any event happen on config db of any namespace + # Loop on select to see if any event happen on state db or config db of any namespace while True: - ctrl_plane_acl_notification = set() - (state, selectableObj) = sel.select(SELECT_TIMEOUT_MS) # Continue if select is timeout or selectable object is not return if state != swsscommon.Select.OBJECT: @@ -653,8 +752,21 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): # Get the redisselect object from selectable object redisSelectObj = swsscommon.CastSelectableToRedisSelectObj(selectableObj) - # Get the corresponding namespace from redisselect db connector object + # Get the corresponding namespace and db_id from redisselect namespace = redisSelectObj.getDbConnector().getNamespace() + db_id = redisSelectObj.getDbConnector().getDbId() + + if db_id == state_db_id: + if self.DualToR: + while True: + key, op, fvs = subscribe_mux_cable.pop() + if not key: + break + self.log_info("mux cable update : '%s'" % str((key, op, fvs))) + self.update_dhcp_acl(key, op, dict(fvs)) + continue + + ctrl_plane_acl_notification = set() # Pop data of both Subscriber Table object of namespace that got config db acl table event for table in config_db_subscriber_table_map[namespace]: From 0ae67a0b38f14439ee7a489a77d05c30f4a04928 Mon Sep 17 00:00:00 2001 From: Tianrong Zhang Date: Sun, 1 Aug 2021 13:28:32 -0700 Subject: [PATCH 2/4] update --- src/sonic-host-services/scripts/caclmgrd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sonic-host-services/scripts/caclmgrd b/src/sonic-host-services/scripts/caclmgrd index 36847861451e..6427f55a8bc8 100755 --- a/src/sonic-host-services/scripts/caclmgrd +++ b/src/sonic-host-services/scripts/caclmgrd @@ -86,7 +86,7 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): UPDATE_DELAY_SECS = 0.5 - DualToR = 0 + DualToR = False def __init__(self, log_identifier): super(ControlPlaneAclManager, self).__init__(log_identifier) @@ -114,7 +114,7 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): metadata = self.config_db_map[DEFAULT_NAMESPACE].get_table(self.DEVICE_METADATA_TABLE) if 'subtype' in metadata['localhost'] and metadata['localhost']['subtype'] == 'DualToR': - self.DualToR = 1 + self.DualToR = True namespaces = device_info.get_all_namespaces() for front_asic_namespace in namespaces['front_ns']: @@ -329,7 +329,7 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): def setup_dhcp_chain(self, namespace): all_chains = self.get_chain_list(self.iptables_cmd_ns_prefix[namespace], [""]) - dhcp_chain_exist = 1 if "DHCP" in all_chains else 0 + dhcp_chain_exist = "DHCP" in all_chains iptables_cmds = [] if dhcp_chain_exist: From b629ff8a1e8d253771bd79354149a526bf9cf1b6 Mon Sep 17 00:00:00 2001 From: Tianrong Zhang Date: Mon, 2 Aug 2021 18:26:53 -0700 Subject: [PATCH 3/4] use state as key --- src/sonic-host-services/scripts/caclmgrd | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sonic-host-services/scripts/caclmgrd b/src/sonic-host-services/scripts/caclmgrd index 6427f55a8bc8..84ecb422dc30 100755 --- a/src/sonic-host-services/scripts/caclmgrd +++ b/src/sonic-host-services/scripts/caclmgrd @@ -380,23 +380,23 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): self.log_info("Update DHCP chain: {}".format(update_cmd)) def update_dhcp_acl(self, key, op, data): - if "status" not in data: + if "state" not in data: self.log_warning("Unexpected update in MUX_CABLE_TABLE") return intf = key - status = data["status"] + state = data["state"] - if status == "active": + if state == "active": self.update_dhcp_chain("delete", intf) - elif status == "standby": + elif state == "standby": self.update_dhcp_chain("insert", intf) - elif status == "unknown": + elif state == "unknown": self.update_dhcp_chain("delete", intf) - elif status == "error": - self.log_warning("Cable status shows error") + elif state == "error": + self.log_warning("Cable state shows error") else: - self.log_warning("Unexpected cable status") + self.log_warning("Unexpected cable state") def get_acl_rules_and_translate_to_iptables_commands(self, namespace): """ From 1078f75635c103fcd9d34e853467cd9d6eedcb4b Mon Sep 17 00:00:00 2001 From: Tianrong Zhang Date: Mon, 2 Aug 2021 18:59:20 -0700 Subject: [PATCH 4/4] update subprocess call --- src/sonic-host-services/scripts/caclmgrd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sonic-host-services/scripts/caclmgrd b/src/sonic-host-services/scripts/caclmgrd index 84ecb422dc30..8babd666d593 100755 --- a/src/sonic-host-services/scripts/caclmgrd +++ b/src/sonic-host-services/scripts/caclmgrd @@ -376,7 +376,7 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): execute = 1 if execute == 1: - self.run_commands([update_cmd]) + subprocess.call(update_cmd, shell=True) self.log_info("Update DHCP chain: {}".format(update_cmd)) def update_dhcp_acl(self, key, op, data):