From 22402e54b2f67fa9e8dc61cb87412937b693897b Mon Sep 17 00:00:00 2001 From: Shuotian Cheng Date: Fri, 25 Oct 2019 17:24:47 -0700 Subject: [PATCH 01/63] [mirrororch]: Add retry logic when deleting referenced mirror session (#1104) When a mirror session is referenced by an ACL rule, we cannot remove the mirror session. However, once the ACL rule gets removed, a retry would help clean up the to-be-removed mirror session. Signed-off-by: Shu0T1an ChenG --- orchagent/mirrororch.cpp | 28 +++++++++++++++++++++------- orchagent/mirrororch.h | 2 +- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/orchagent/mirrororch.cpp b/orchagent/mirrororch.cpp index 23e1a881bf..0de90a424d 100644 --- a/orchagent/mirrororch.cpp +++ b/orchagent/mirrororch.cpp @@ -352,29 +352,35 @@ void MirrorOrch::createEntry(const string& key, const vector& d m_routeOrch->attach(this, entry.dstIp); } -void MirrorOrch::deleteEntry(const string& name) +task_process_status MirrorOrch::deleteEntry(const string& name) { SWSS_LOG_ENTER(); auto sessionIter = m_syncdMirrors.find(name); if (sessionIter == m_syncdMirrors.end()) { - SWSS_LOG_ERROR("Failed to delete session. Session %s doesn't exist.\n", name.c_str()); - return; + SWSS_LOG_ERROR("Failed to remove non-existent mirror session %s", + name.c_str()); + return task_process_status::task_invalid_entry; } auto& session = sessionIter->second; if (session.refCount) { - SWSS_LOG_ERROR("Failed to delete session. Session %s in use.\n", name.c_str()); - return; + SWSS_LOG_WARN("Failed to remove still referenced mirror session %s, retry...", + name.c_str()); + return task_process_status::task_need_retry; } if (session.status) { m_routeOrch->detach(this, session.dstIp); - deactivateSession(name, session); + if (!deactivateSession(name, session)) + { + SWSS_LOG_ERROR("Failed to remove mirror session %s", name.c_str()); + return task_process_status::task_failed; + } } if (!session.policer.empty()) @@ -387,6 +393,8 @@ void MirrorOrch::deleteEntry(const string& name) m_syncdMirrors.erase(sessionIter); SWSS_LOG_NOTICE("Removed mirror session %s", name.c_str()); + + return task_process_status::task_success; } void MirrorOrch::setSessionState(const string& name, const MirrorEntry& session, const string& attr) @@ -1144,7 +1152,13 @@ void MirrorOrch::doTask(Consumer& consumer) } else if (op == DEL_COMMAND) { - deleteEntry(key); + auto task_status = deleteEntry(key); + // Specifically retry the task when asked + if (task_status == task_process_status::task_need_retry) + { + it++; + continue; + } } else { diff --git a/orchagent/mirrororch.h b/orchagent/mirrororch.h index b367e4fba8..798eb4583a 100644 --- a/orchagent/mirrororch.h +++ b/orchagent/mirrororch.h @@ -94,7 +94,7 @@ class MirrorOrch : public Orch, public Observer, public Subject bool m_freeze = false; void createEntry(const string&, const vector&); - void deleteEntry(const string&); + task_process_status deleteEntry(const string&); bool activateSession(const string&, MirrorEntry&); bool deactivateSession(const string&, MirrorEntry&); From 2a1ffe8135c618d40dce9e4462a0a5ff604751d8 Mon Sep 17 00:00:00 2001 From: zhenggen-xu Date: Sat, 26 Oct 2019 21:26:32 -0700 Subject: [PATCH 02/63] [portsyncd] Remove the port_config.ini dependency from portsyncd (#1107) * Remove the port_config.ini dependency from portsyncd portsyncd will rely on configDB port table, not from port_config.ini port_config.ini is used when we convert minigraph to configDB, then everything should come from configDB. Also for the DPB feature, we are going to deprecate port_config.ini * Modify the vs test cases --- portsyncd/portsyncd.cpp | 111 +++----------------------------------- tests/test_port_config.py | 101 ++++++++++++++++++++++++---------- 2 files changed, 80 insertions(+), 132 deletions(-) diff --git a/portsyncd/portsyncd.cpp b/portsyncd/portsyncd.cpp index 2b72d29ac7..132e1cbf83 100644 --- a/portsyncd/portsyncd.cpp +++ b/portsyncd/portsyncd.cpp @@ -35,9 +35,9 @@ bool g_init = false; void usage() { - cout << "Usage: portsyncd [-p port_config.ini]" << endl; - cout << " -p port_config.ini: import port lane mapping" << endl; - cout << " use configDB data if not specified" << endl; + cout << "Usage: portsyncd" << endl; + cout << " port lane mapping is from configDB" << endl; + cout << " this program will exit if configDB does not contain that info" << endl; } void handlePortConfigFile(ProducerStateTable &p, string file, bool warm); @@ -50,16 +50,12 @@ int main(int argc, char **argv) { Logger::linkToDbNative("portsyncd"); int opt; - string port_config_file; map port_cfg_map; - while ((opt = getopt(argc, argv, "p:v:h")) != -1 ) + while ((opt = getopt(argc, argv, "v:h")) != -1 ) { switch (opt) { - case 'p': - port_config_file.assign(optarg); - break; case 'h': usage(); return 1; @@ -91,11 +87,9 @@ int main(int argc, char **argv) if (!handlePortConfigFromConfigDB(p, cfgDb, warm)) { // if port config is missing in ConfigDB - // attempt to use port_config.ini - if (!port_config_file.empty()) - { - handlePortConfigFile(p, port_config_file, warm); - } + // program will exit with failure + SWSS_LOG_THROW("ConfigDB does not have port information, exiting..."); + return EXIT_FAILURE; } LinkSync sync(&appl_db, &state_db); @@ -222,97 +216,6 @@ bool handlePortConfigFromConfigDB(ProducerStateTable &p, DBConnector &cfgDb, boo return true; } -void handlePortConfigFile(ProducerStateTable &p, string file, bool warm) -{ - cout << "Read port configuration file..." << endl; - - ifstream infile(file); - if (!infile.is_open()) - { - usage(); - throw runtime_error("Port configuration file not found!"); - } - - list header = {"name", "lanes", "alias", "speed", "autoneg", "fec"}; - string line; - while (getline(infile, line)) - { - if (line.at(0) == '#') - { - // Take this line as column header line - istringstream iss_hdr(line.substr(1)); - string hdr; - - header.clear(); - while (! iss_hdr.eof()) { - iss_hdr >> hdr; - cout << "Adding column header '" << hdr << "'" << endl; - header.push_back(hdr); - } - - continue; - } - - istringstream iss(line); - map entry; - - /* Read port configuration entry */ - for (auto column : header) - { - iss >> entry[column]; - } - - if (!warm) - { - /* If port has no alias, then use its name as alias */ - string alias; - if ((entry.find("alias") != entry.end()) && (entry["alias"] != "")) - { - alias = entry["alias"]; - } - else - { - alias = entry["name"]; - } - - FieldValueTuple lanes_attr("lanes", entry["lanes"]); - FieldValueTuple alias_attr("alias", alias); - - vector attrs; - attrs.push_back(lanes_attr); - attrs.push_back(alias_attr); - - if ((entry.find("speed") != entry.end()) && (entry["speed"] != "")) - { - FieldValueTuple speed_attr("speed", entry["speed"]); - attrs.push_back(speed_attr); - } - - if ((entry.find("autoneg") != entry.end()) && (entry["autoneg"] != "")) - { - FieldValueTuple autoneg_attr("autoneg", entry["autoneg"]); - attrs.push_back(autoneg_attr); - } - - if ((entry.find("fec") != entry.end()) && (entry["fec"] != "")) - { - FieldValueTuple fec_attr("fec", entry["fec"]); - attrs.push_back(fec_attr); - } - - p.set(entry["name"], attrs); - } - - g_portSet.insert(entry["name"]); - } - - infile.close(); - if (!warm) - { - notifyPortConfigDone(p); - } -} - void handlePortConfig(ProducerStateTable &p, map &port_cfg_map) { diff --git a/tests/test_port_config.py b/tests/test_port_config.py index 656b96565e..6fa2d82a92 100644 --- a/tests/test_port_config.py +++ b/tests/test_port_config.py @@ -55,44 +55,89 @@ def test_port_hw_lane(self, dvs): def test_port_breakout(self, dvs, port_config): - # check port_config.ini - (exitcode, output) = dvs.runcmd(['sh', '-c', "cat %s | tail -n 1" % (port_config)]) - try: - name_str, lanes_str, alias_str, index_str, speed_str = list(output.split()) - except: - print "parse port_config.ini fail" - - LANES_L = list(lanes_str.split(",")) - assert len(LANES_L) == 4 - assert int(speed_str) == 40000 - - # modify port_config.ini - eth = int(name_str.replace("Ethernet", "")) - index = int(index_str) - speed_str = "tenGigE0" - speed = 10000 - dvs.runcmd("sed -i '$d' %s" % (port_config)) == 0 - for i in range(0,4): - dvs.runcmd("sed -i '$a Ethernet%-7d %-17d %s/%-8d %-11d %d' %s" % - (eth+i, int(LANES_L[i]), speed_str, eth+i, index+i, speed, port_config)) == 0 - - # delete port config + # Breakout the port from 1 to 4 + ''' + "Ethernet0": { + "alias": "fortyGigE0/0", + "index": "0", + "lanes": "25,26,27,28", + "speed": "40000" + }, + + to: + "Ethernet0": { + "alias": "tenGigE0", + "index": "0", + "lanes": "25", + "speed": "10000" + }, + + "Ethernet1": { + "alias": "tenGigE1", + "index": "0", + "lanes": "26", + "speed": "10000" + }, + + "Ethernet2": { + "alias": "tenGigE2", + "index": "0", + "lanes": "27", + "speed": "10000" + }, + + "Ethernet3": { + "alias": "tenGigE3", + "index": "0", + "lanes": "28", + "speed": "10000" + }, + ''' + # Get port config from configDB conf_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) portTbl = swsscommon.Table(conf_db, swsscommon.CFG_PORT_TABLE_NAME) + + chg_port = "Ethernet0" + keys = portTbl.getKeys() - assert len(keys) > 0 - for key in keys: - portTbl._del(key) + assert chg_port in keys + + (status, fvs) = portTbl.get(chg_port) + assert(status == True) + + for fv in fvs: + if fv[0] == "index": + new_index = fv[1] + if fv[0] == "lanes": + new_lanes = fv[1].split(",") - # restart to apply new port_config.ini + # Stop swss before modifing the configDB dvs.stop_swss() + time.sleep(1) + + # breakout the port in configDB + portTbl._del(chg_port) + + new_ports = ["Ethernet0","Ethernet1","Ethernet2","Ethernet3"] + new_speed = "10000" + new_alias = ["tenGigE0", "tenGigE1", "tenGigE2", "tenGigE3"] + + for i in range (0 ,4): + fvs = swsscommon.FieldValuePairs([("alias", new_alias[i]), + ("lanes", new_lanes[i]), + ("speed", new_speed), + ("index", new_index)]) + + portTbl.set(new_ports[i], fvs) + + # start to apply new port_config.ini dvs.start_swss() time.sleep(5) asic_db = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) for i in range(0,4): - port_name = 'Ethernet{0}'.format(eth+i) + port_name = 'Ethernet{0}'.format(i) port_oid = self.getPortOid(dvs, port_name) port_tbl = swsscommon.Table(asic_db, 'ASIC_STATE:SAI_OBJECT_TYPE_PORT:{0}'.format(port_oid)) hw_lane_value = None @@ -102,6 +147,6 @@ def test_port_breakout(self, dvs, port_config): hw_lane_value = k[1] assert hw_lane_value, "Can't get hw_lane list" - assert hw_lane_value == "1:%s" % (LANES_L[i]) + assert hw_lane_value == "1:%s" % (new_lanes[i]) From 09171575324ea56f42f18e5203f501f9ccb92309 Mon Sep 17 00:00:00 2001 From: Stepan Blyshchak <38952541+stepanblyschak@users.noreply.github.com> Date: Mon, 28 Oct 2019 21:03:36 +0200 Subject: [PATCH 03/63] =?UTF-8?q?[aclorch]=20if=20vendor=20does=20not=20im?= =?UTF-8?q?plement=20ACL=20action=20capability=20quieries=E2=80=A6=20(#110?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [aclorch] if vendor does not implement ACL action capability quieries - use default capabilities (do not fail) --- orchagent/aclorch.cpp | 165 ++++++++++++++++++++++++++++-------------- orchagent/aclorch.h | 2 + 2 files changed, 113 insertions(+), 54 deletions(-) diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index 2ce0ad9bf6..c3d5fd49a8 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -115,6 +115,20 @@ static acl_stage_type_lookup_t aclStageLookUp = {STAGE_EGRESS, ACL_STAGE_EGRESS } }; +static const acl_capabilities_t defaultAclActionsSupported = +{ + { + ACL_STAGE_INGRESS, + { + SAI_ACL_ACTION_TYPE_PACKET_ACTION, + SAI_ACL_ACTION_TYPE_MIRROR_INGRESS + } + }, + { + ACL_STAGE_EGRESS, {} + } +}; + static acl_ip_type_lookup_t aclIpTypeLookup = { { IP_TYPE_ANY, SAI_ACL_IP_TYPE_ANY }, @@ -2129,68 +2143,59 @@ void AclOrch::queryAclActionCapability() sai_status_t status {SAI_STATUS_FAILURE}; sai_attribute_t attr; vector action_list; - vector fvVector; attr.id = SAI_SWITCH_ATTR_MAX_ACL_ACTION_COUNT; status = sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr); - if (status != SAI_STATUS_SUCCESS) - { - SWSS_LOG_THROW("AclOrch initialization failed: " - "failed to query maximum ACL action count"); - } - - const auto max_action_count = attr.value.u32; - - for (auto stage_attr: {SAI_SWITCH_ATTR_ACL_STAGE_INGRESS, SAI_SWITCH_ATTR_ACL_STAGE_EGRESS}) + if (status == SAI_STATUS_SUCCESS) { - auto stage = (stage_attr == SAI_SWITCH_ATTR_ACL_STAGE_INGRESS ? ACL_STAGE_INGRESS : ACL_STAGE_EGRESS); - auto stage_str = (stage_attr == SAI_SWITCH_ATTR_ACL_STAGE_INGRESS ? STAGE_INGRESS : STAGE_EGRESS); - action_list.resize(static_cast(max_action_count)); - - attr.id = stage_attr; - attr.value.aclcapability.action_list.list = action_list.data(); - attr.value.aclcapability.action_list.count = max_action_count; + const auto max_action_count = attr.value.u32; - status = sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr); - if (status != SAI_STATUS_SUCCESS) + for (auto stage_attr: {SAI_SWITCH_ATTR_ACL_STAGE_INGRESS, SAI_SWITCH_ATTR_ACL_STAGE_EGRESS}) { - SWSS_LOG_THROW("AclOrch initialization failed: " - "failed to query supported %s ACL actions", stage_str); - } + auto stage = (stage_attr == SAI_SWITCH_ATTR_ACL_STAGE_INGRESS ? ACL_STAGE_INGRESS : ACL_STAGE_EGRESS); + auto stage_str = (stage_attr == SAI_SWITCH_ATTR_ACL_STAGE_INGRESS ? STAGE_INGRESS : STAGE_EGRESS); + action_list.resize(static_cast(max_action_count)); - SWSS_LOG_INFO("Supported %s action count %d:", stage_str, - attr.value.aclcapability.action_list.count); + attr.id = stage_attr; + attr.value.aclcapability.action_list.list = action_list.data(); + attr.value.aclcapability.action_list.count = max_action_count; - for (size_t i = 0; i < static_cast(attr.value.aclcapability.action_list.count); i++) - { - auto action = static_cast(action_list[i]); - m_aclCapabilities[stage].insert(action); - SWSS_LOG_INFO(" %s", sai_serialize_enum(action, &sai_metadata_enum_sai_acl_action_type_t).c_str()); - } - - // put capabilities in state DB + status = sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr); + if (status == SAI_STATUS_SUCCESS) + { - auto field = std::string("ACL_ACTIONS") + '|' + stage_str; - auto& acl_action_set = m_aclCapabilities[stage]; + SWSS_LOG_INFO("Supported %s action count %d:", stage_str, + attr.value.aclcapability.action_list.count); - string delimiter; - ostringstream acl_action_value_stream; - - for (const auto& action_map: {aclL3ActionLookup, aclMirrorStageLookup, aclDTelActionLookup}) - { - for (const auto& it: action_map) - { - auto saiAction = getAclActionFromAclEntry(it.second); - if (acl_action_set.find(saiAction) != acl_action_set.cend()) + for (size_t i = 0; i < static_cast(attr.value.aclcapability.action_list.count); i++) { - acl_action_value_stream << delimiter << it.first; - delimiter = comma; + auto action = static_cast(action_list[i]); + m_aclCapabilities[stage].insert(action); + SWSS_LOG_INFO(" %s", sai_serialize_enum(action, &sai_metadata_enum_sai_acl_action_type_t).c_str()); } } - } + else + { + SWSS_LOG_WARN("Failed to query ACL %s action capabilities - " + "API assumed to be not implemented, using defaults", + stage_str); + initDefaultAclActionCapabilities(stage); + } - fvVector.emplace_back(field, acl_action_value_stream.str()); - m_switchTable.set("switch", fvVector); + // put capabilities in state DB + putAclActionCapabilityInDB(stage); + } + } + else + { + SWSS_LOG_WARN("Failed to query maximum ACL action count - " + "API assumed to be not implemented, using defaults capabilities for both %s and %s", + STAGE_INGRESS, STAGE_EGRESS); + for (auto stage: {ACL_STAGE_INGRESS, ACL_STAGE_EGRESS}) + { + initDefaultAclActionCapabilities(stage); + putAclActionCapabilityInDB(stage); + } } /* For those ACL action entry attributes for which acl parameter is enumeration (metadata->isenum == true) @@ -2209,6 +2214,50 @@ void AclOrch::queryAclActionCapability() aclDTelFlowOpTypeLookup); } +void AclOrch::putAclActionCapabilityInDB(acl_stage_type_t stage) +{ + vector fvVector; + auto stage_str = (stage == ACL_STAGE_INGRESS ? STAGE_INGRESS : STAGE_EGRESS); + + auto field = std::string("ACL_ACTIONS") + '|' + stage_str; + auto& acl_action_set = m_aclCapabilities[stage]; + + string delimiter; + ostringstream acl_action_value_stream; + + for (const auto& action_map: {aclL3ActionLookup, aclMirrorStageLookup, aclDTelActionLookup}) + { + for (const auto& it: action_map) + { + auto saiAction = getAclActionFromAclEntry(it.second); + if (acl_action_set.find(saiAction) != acl_action_set.cend()) + { + acl_action_value_stream << delimiter << it.first; + delimiter = comma; + } + } + } + + fvVector.emplace_back(field, acl_action_value_stream.str()); + m_switchTable.set("switch", fvVector); +} + +void AclOrch::initDefaultAclActionCapabilities(acl_stage_type_t stage) +{ + m_aclCapabilities[stage] = defaultAclActionsSupported.at(stage); + + SWSS_LOG_INFO("Assumed %s %zu actions to be supported:", + stage == ACL_STAGE_INGRESS ? STAGE_INGRESS : STAGE_EGRESS, + m_aclCapabilities[stage].size()); + + for (auto action: m_aclCapabilities[stage]) + { + SWSS_LOG_INFO(" %s", sai_serialize_enum(action, &sai_metadata_enum_sai_acl_action_type_t).c_str()); + } + // put capabilities in state DB + putAclActionCapabilityInDB(stage); +} + template void AclOrch::queryAclActionAttrEnumValues(const string &action_name, const acl_rule_attr_lookup_t& ruleAttrLookupMap, @@ -2251,15 +2300,23 @@ void AclOrch::queryAclActionAttrEnumValues(const string &action_name, SAI_OBJECT_TYPE_ACL_ENTRY, acl_attr, &values); - if (status != SAI_STATUS_SUCCESS) + if (status == SAI_STATUS_SUCCESS) { - SWSS_LOG_THROW("sai_query_attribute_enum_values_capability failed for %s", - action_name.c_str()); + for (size_t i = 0; i < values.count; i++) + { + m_aclEnumActionCapabilities[acl_action].insert(values.list[i]); + } } - - for (size_t i = 0; i < values.count; i++) + else { - m_aclEnumActionCapabilities[acl_action].insert(values.list[i]); + SWSS_LOG_WARN("Failed to query enum values supported for ACL action %s - ", + "API is not implemented, assuming all values are supported for this action", + action_name.c_str()); + /* assume all enum values are supported */ + for (size_t i = 0; i < meta->enummetadata->valuescount; i++) + { + m_aclEnumActionCapabilities[acl_action].insert(meta->enummetadata->values[i]); + } } #else /* assume all enum values are supported untill sai object api is available */ diff --git a/orchagent/aclorch.h b/orchagent/aclorch.h index 004dfa58ba..5f09683f5a 100644 --- a/orchagent/aclorch.h +++ b/orchagent/aclorch.h @@ -419,6 +419,8 @@ class AclOrch : public Orch, public Observer void queryMirrorTableCapability(); void queryAclActionCapability(); + void initDefaultAclActionCapabilities(acl_stage_type_t); + void putAclActionCapabilityInDB(acl_stage_type_t); template void queryAclActionAttrEnumValues(const string& action_name, From b767ca201845eb1543b19300362f3f4225eb14ba Mon Sep 17 00:00:00 2001 From: "Sudharsan D.G" Date: Mon, 28 Oct 2019 18:08:24 -0500 Subject: [PATCH 04/63] Copp sflow changes (#1011) * Copp sflow changes --- orchagent/copporch.cpp | 226 ++++++++++++++++++++++++++++++++++++++- orchagent/copporch.h | 31 +++++- orchagent/orchdaemon.cpp | 10 +- 3 files changed, 257 insertions(+), 10 deletions(-) diff --git a/orchagent/copporch.cpp b/orchagent/copporch.cpp index 90fe900f45..fbe3f1080a 100644 --- a/orchagent/copporch.cpp +++ b/orchagent/copporch.cpp @@ -7,6 +7,7 @@ #include #include #include +#include using namespace swss; using namespace std; @@ -90,14 +91,15 @@ const vector default_trap_ids = { SAI_HOSTIF_TRAP_TYPE_TTL_ERROR }; -CoppOrch::CoppOrch(DBConnector *db, string tableName) : - Orch(db, tableName) +CoppOrch::CoppOrch(vector &tableConnectors) : + Orch( tableConnectors) { SWSS_LOG_ENTER(); initDefaultHostIntfTable(); initDefaultTrapGroup(); initDefaultTrapIds(); + enable_sflow_trap = false; }; void CoppOrch::initDefaultHostIntfTable() @@ -191,6 +193,60 @@ void CoppOrch::getTrapIdList(vector &trap_id_name_list, vector &trap_id_name_list) +{ + SWSS_LOG_ENTER(); + + vector trap_id_list; + + getTrapIdList(trap_id_name_list, trap_id_list); + + for (auto trap_id : trap_id_list) + { + auto host_tbl_entry = m_trapid_hostif_table_map.find(trap_id); + + if (host_tbl_entry == m_trapid_hostif_table_map.end()) + { + sai_object_id_t trap_group_id = m_syncdTrapIds[trap_id].trap_group_obj; + auto hostif_map = m_trap_group_hostif_map.find(trap_group_id); + if (hostif_map != m_trap_group_hostif_map.end()) + { + sai_object_id_t hostif_table_entry = SAI_NULL_OBJECT_ID; + sai_attribute_t attr; + vector sai_host_table_attr; + + attr.id = SAI_HOSTIF_TABLE_ENTRY_ATTR_TYPE; + attr.value.s32 = SAI_HOSTIF_TABLE_ENTRY_TYPE_TRAP_ID; + sai_host_table_attr.push_back(attr); + + attr.id = SAI_HOSTIF_TABLE_ENTRY_ATTR_TRAP_ID; + attr.value.oid = m_syncdTrapIds[trap_id].trap_obj; + sai_host_table_attr.push_back(attr); + + attr.id = SAI_HOSTIF_TABLE_ENTRY_ATTR_CHANNEL_TYPE; + attr.value.s32 = SAI_HOSTIF_TABLE_ENTRY_CHANNEL_TYPE_GENETLINK; + sai_host_table_attr.push_back(attr); + + attr.id = SAI_HOSTIF_TABLE_ENTRY_ATTR_HOST_IF; + attr.value.oid = hostif_map->second; + sai_host_table_attr.push_back(attr); + + sai_status_t status = sai_hostif_api->create_hostif_table_entry(&hostif_table_entry, + gSwitchId, + (uint32_t)sai_host_table_attr.size(), + sai_host_table_attr.data()); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create hostif table entry failed, rv %d", status); + return false; + } + m_trapid_hostif_table_map[trap_id] = hostif_table_entry; + } + } + } + return true; +} + bool CoppOrch::applyAttributesToTrapIds(sai_object_id_t trap_group_id, const vector &trap_id_list, vector &trap_id_attribs) @@ -213,9 +269,9 @@ bool CoppOrch::applyAttributesToTrapIds(sai_object_id_t trap_group_id, SWSS_LOG_ERROR("Failed to create trap %d, rv:%d", trap_id, status); return false; } - m_syncdTrapIds[trap_id] = trap_group_id; + m_syncdTrapIds[trap_id].trap_group_obj = trap_group_id; + m_syncdTrapIds[trap_id].trap_obj = hostif_trap_id; } - return true; } @@ -235,6 +291,7 @@ bool CoppOrch::applyTrapIds(sai_object_id_t trap_group, vector &trap_id_ return applyAttributesToTrapIds(trap_group, trap_id_list, trap_id_attribs); } + bool CoppOrch::removePolicer(string trap_group_name) { SWSS_LOG_ENTER(); @@ -321,6 +378,69 @@ bool CoppOrch::createPolicer(string trap_group_name, vector &po return true; } +bool CoppOrch::createGenetlinkHostIf(string trap_group_name, vector &genetlink_attribs) +{ + SWSS_LOG_ENTER(); + + sai_object_id_t hostif_id; + sai_status_t sai_status; + + sai_status = sai_hostif_api->create_hostif(&hostif_id, gSwitchId, + (uint32_t)genetlink_attribs.size(), + genetlink_attribs.data()); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create genetlink hostif for trap group %s, rc=%d", + trap_group_name.c_str(), sai_status); + return false; + } + + m_trap_group_hostif_map[m_trap_group_map[trap_group_name]] = hostif_id; + return true; +} + +bool CoppOrch::removeGenetlinkHostIf(string trap_group_name) +{ + SWSS_LOG_ENTER(); + + sai_status_t sai_status; + + for (auto it : m_syncdTrapIds) + { + if (it.second.trap_group_obj == m_trap_group_map[trap_group_name]) + { + auto hostTableEntry = m_trapid_hostif_table_map.find(it.first); + if (hostTableEntry != m_trapid_hostif_table_map.end()) + { + sai_status = sai_hostif_api->remove_hostif_table_entry(hostTableEntry->second); + if(sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to delete hostif table entry %ld \ + on trap group %s. rc=%d", hostTableEntry->second, + trap_group_name.c_str(), sai_status); + return false; + } + m_trapid_hostif_table_map.erase(it.first); + } + } + } + + auto hostInfo = m_trap_group_hostif_map.find(m_trap_group_map[trap_group_name]); + if(hostInfo != m_trap_group_hostif_map.end()) + { + sai_status = sai_hostif_api->remove_hostif(hostInfo->second); + if(sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to delete host info %ld on trap group %s. rc=%d", + hostInfo->second, trap_group_name.c_str(), sai_status); + return false; + } + m_trap_group_hostif_map.erase(m_trap_group_map[trap_group_name]); + } + + return true; +} + task_process_status CoppOrch::processCoppRule(Consumer& consumer) { SWSS_LOG_ENTER(); @@ -336,6 +456,7 @@ task_process_status CoppOrch::processCoppRule(Consumer& consumer) vector trap_gr_attribs; vector trap_id_attribs; vector policer_attribs; + vector genetlink_attribs; if (op == SET_COMMAND) { @@ -346,6 +467,14 @@ task_process_status CoppOrch::processCoppRule(Consumer& consumer) if (fvField(*i) == copp_trap_id_list) { trap_id_list = tokenize(fvValue(*i), list_item_delimiter); + auto it = std::find(trap_id_list.begin(), trap_id_list.end(), "sample_packet"); + if (it != trap_id_list.end()) + { + if (!enable_sflow_trap) + { + return task_process_status::task_need_retry; + } + } } else if (fvField(*i) == copp_queue_field) { @@ -443,6 +572,25 @@ task_process_status CoppOrch::processCoppRule(Consumer& consumer) attr.value.s32 = policer_action; policer_attribs.push_back(attr); } + else if (fvField(*i) == copp_genetlink_name) + { + attr.id = SAI_HOSTIF_ATTR_TYPE; + attr.value.s32 = SAI_HOSTIF_TYPE_GENETLINK; + genetlink_attribs.push_back(attr); + + attr.id = SAI_HOSTIF_ATTR_NAME; + strncpy(attr.value.chardata, fvValue(*i).c_str(), + sizeof(attr.value.chardata)); + genetlink_attribs.push_back(attr); + + } + else if (fvField(*i) == copp_genetlink_mcgrp_name) + { + attr.id = SAI_HOSTIF_ATTR_GENETLINK_MCGRP_NAME; + strncpy(attr.value.chardata, fvValue(*i).c_str(), + sizeof(attr.value.chardata)); + genetlink_attribs.push_back(attr); + } else { SWSS_LOG_ERROR("Unknown copp field specified:%s\n", fvField(*i).c_str()); @@ -519,6 +667,14 @@ task_process_status CoppOrch::processCoppRule(Consumer& consumer) return task_process_status::task_failed; } } + + if (!genetlink_attribs.empty()) + { + if (!createGenetlinkHostIf(trap_group_name, genetlink_attribs)) + { + return task_process_status::task_failed; + } + } } /* Apply traps to trap group */ @@ -526,6 +682,14 @@ task_process_status CoppOrch::processCoppRule(Consumer& consumer) { return task_process_status::task_failed; } + + if (!genetlink_attribs.empty()) + { + if (!createGenetlinkHostIfTable(trap_id_list)) + { + return task_process_status::task_failed; + } + } } else if (op == DEL_COMMAND) { @@ -536,6 +700,12 @@ task_process_status CoppOrch::processCoppRule(Consumer& consumer) return task_process_status::task_failed; } + if (!removeGenetlinkHostIf(trap_group_name)) + { + SWSS_LOG_ERROR("Failed to remove hostif from trap group %s", trap_group_name.c_str()); + return task_process_status::task_failed; + } + /* Do not remove default trap group */ if (trap_group_name == default_trap_group) { @@ -547,10 +717,16 @@ task_process_status CoppOrch::processCoppRule(Consumer& consumer) vector trap_ids_to_reset; for (auto it : m_syncdTrapIds) { - if (it.second == m_trap_group_map[trap_group_name]) + if (it.second.trap_group_obj == m_trap_group_map[trap_group_name]) { trap_ids_to_reset.push_back(it.first); } + sai_status = sai_hostif_api->remove_hostif_trap(it.second.trap_obj); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove trap object %ld", it.second.trap_obj); + return task_process_status::task_failed; + } } sai_attribute_t attr; @@ -588,9 +764,49 @@ task_process_status CoppOrch::processCoppRule(Consumer& consumer) return task_process_status::task_success; } +/* Program Sflow trap once we get sflow enable command */ +void CoppOrch::coppProcessSflow(Consumer &consumer) +{ + auto it = consumer.m_toSync.begin(); + + while (it != consumer.m_toSync.end()) + { + auto tuple = it->second; + string op = kfvOp(tuple); + + /* + * Need to handled just 'config sflow enable' command to install the sflow trap group + * for the first time to ensure support of genetlink attributes. Rest of the fields or + * disable value or DEL command are not required to be handled + * + */ + if (op == SET_COMMAND) + { + for (auto i : kfvFieldsValues(tuple)) + { + if (fvField(i) == "admin_state") + { + if (fvValue(i) == "up") + { + enable_sflow_trap = true; + } + } + } + } + it = consumer.m_toSync.erase(it); + } +} + void CoppOrch::doTask(Consumer &consumer) { SWSS_LOG_ENTER(); + string table_name = consumer.getTableName(); + + if (table_name == CFG_SFLOW_TABLE_NAME) + { + coppProcessSflow(consumer); + return; + } if (!gPortsOrch->allPortsReady()) { diff --git a/orchagent/copporch.h b/orchagent/copporch.h index 5fc5bc2ae8..c46ac6e62e 100644 --- a/orchagent/copporch.h +++ b/orchagent/copporch.h @@ -24,20 +24,38 @@ const std::string copp_policer_action_green_field = "green_action"; const std::string copp_policer_action_red_field = "red_action"; const std::string copp_policer_action_yellow_field = "yellow_action"; +// genetlink fields +const std::string copp_genetlink_name = "genetlink_name"; +const std::string copp_genetlink_mcgrp_name = "genetlink_mcgrp_name"; + +struct copp_trap_objects +{ + sai_object_id_t trap_obj; + sai_object_id_t trap_group_obj; +}; + /* TrapGroupPolicerTable: trap group ID, policer ID */ typedef std::map TrapGroupPolicerTable; -/* TrapIdTrapGroupTable: trap ID, trap group ID */ -typedef std::map TrapIdTrapGroupTable; +/* TrapIdTrapObjectsTable: trap ID, copp trap objects */ +typedef std::map TrapIdTrapObjectsTable; +/* TrapGroupHostIfMap: trap group ID, host interface ID */ +typedef std::map TrapGroupHostIfMap; +/* TrapIdHostIfTableMap: trap type, host table entry ID*/ +typedef std::map TrapIdHostIfTableMap; class CoppOrch : public Orch { public: - CoppOrch(swss::DBConnector *db, std::string tableName); + CoppOrch(std::vector &tableConnectors); protected: object_map m_trap_group_map; + bool enable_sflow_trap; TrapGroupPolicerTable m_trap_group_policer_map; - TrapIdTrapGroupTable m_syncdTrapIds; + TrapIdTrapObjectsTable m_syncdTrapIds; + + TrapGroupHostIfMap m_trap_group_hostif_map; + TrapIdHostIfTableMap m_trapid_hostif_table_map; void initDefaultHostIntfTable(); void initDefaultTrapGroup(); @@ -54,6 +72,11 @@ class CoppOrch : public Orch sai_object_id_t getPolicer(std::string trap_group_name); + bool createGenetlinkHostIf(std::string trap_group_name, std::vector &hostif_attribs); + bool removeGenetlinkHostIf(std::string trap_group_name); + bool createGenetlinkHostIfTable(std::vector &trap_id_name_list); + void coppProcessSflow(Consumer& consumer); + virtual void doTask(Consumer& consumer); }; #endif /* SWSS_COPPORCH_H */ diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 1ab322437b..e174160e02 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -122,7 +122,15 @@ bool OrchDaemon::init() gIntfsOrch = new IntfsOrch(m_applDb, APP_INTF_TABLE_NAME, vrf_orch); gNeighOrch = new NeighOrch(m_applDb, APP_NEIGH_TABLE_NAME, gIntfsOrch); gRouteOrch = new RouteOrch(m_applDb, APP_ROUTE_TABLE_NAME, gNeighOrch); - CoppOrch *copp_orch = new CoppOrch(m_applDb, APP_COPP_TABLE_NAME); + + TableConnector confDbSflowTable(m_configDb, CFG_SFLOW_TABLE_NAME); + TableConnector appCoppTable(m_applDb, APP_COPP_TABLE_NAME); + + vector copp_table_connectors = { + confDbSflowTable, + appCoppTable + }; + CoppOrch *copp_orch = new CoppOrch(copp_table_connectors); TunnelDecapOrch *tunnel_decap_orch = new TunnelDecapOrch(m_applDb, APP_TUNNEL_DECAP_TABLE_NAME); VxlanTunnelOrch *vxlan_tunnel_orch = new VxlanTunnelOrch(m_applDb, APP_VXLAN_TUNNEL_TABLE_NAME); From 20747fa63d59f4aadaa06da282e23d8e1e0b9fdf Mon Sep 17 00:00:00 2001 From: "Sudharsan D.G" Date: Mon, 28 Oct 2019 18:09:16 -0500 Subject: [PATCH 05/63] Sflow orchagent changes (#1012) * Sflow orchagent changes --- cfgmgr/Makefile.am | 9 +- cfgmgr/sflowmgr.cpp | 376 +++++++++++++++++++++++++++++++++++++++ cfgmgr/sflowmgr.h | 67 +++++++ cfgmgr/sflowmgrd.cpp | 88 +++++++++ orchagent/Makefile.am | 1 + orchagent/orchdaemon.cpp | 9 +- orchagent/orchdaemon.h | 1 + orchagent/saihelper.cpp | 3 + orchagent/sfloworch.cpp | 347 ++++++++++++++++++++++++++++++++++++ orchagent/sfloworch.h | 47 +++++ tests/test_sflow.py | 130 ++++++++++++++ 11 files changed, 1075 insertions(+), 3 deletions(-) create mode 100644 cfgmgr/sflowmgr.cpp create mode 100644 cfgmgr/sflowmgr.h create mode 100644 cfgmgr/sflowmgrd.cpp create mode 100644 orchagent/sfloworch.cpp create mode 100644 orchagent/sfloworch.h create mode 100644 tests/test_sflow.py diff --git a/cfgmgr/Makefile.am b/cfgmgr/Makefile.am index ee5c3b5ce6..c1e3f06996 100644 --- a/cfgmgr/Makefile.am +++ b/cfgmgr/Makefile.am @@ -3,7 +3,7 @@ CFLAGS_SAI = -I /usr/include/sai LIBNL_CFLAGS = -I/usr/include/libnl3 LIBNL_LIBS = -lnl-genl-3 -lnl-route-3 -lnl-3 -bin_PROGRAMS = vlanmgrd teammgrd portmgrd intfmgrd buffermgrd vrfmgrd nbrmgrd vxlanmgrd +bin_PROGRAMS = vlanmgrd teammgrd portmgrd intfmgrd buffermgrd vrfmgrd nbrmgrd vxlanmgrd sflowmgrd if DEBUG DBGFLAGS = -ggdb -DDEBUG @@ -49,4 +49,9 @@ nbrmgrd_LDADD = -lswsscommon $(LIBNL_LIBS) vxlanmgrd_SOURCES = vxlanmgrd.cpp vxlanmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h vxlanmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) vxlanmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) -vxlanmgrd_LDADD = -lswsscommon \ No newline at end of file +vxlanmgrd_LDADD = -lswsscommon + +sflowmgrd_SOURCES = sflowmgrd.cpp sflowmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +sflowmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) +sflowmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) +sflowmgrd_LDADD = -lswsscommon diff --git a/cfgmgr/sflowmgr.cpp b/cfgmgr/sflowmgr.cpp new file mode 100644 index 0000000000..98d6a77756 --- /dev/null +++ b/cfgmgr/sflowmgr.cpp @@ -0,0 +1,376 @@ +#include "logger.h" +#include "dbconnector.h" +#include "producerstatetable.h" +#include "tokenize.h" +#include "ipprefix.h" +#include "sflowmgr.h" +#include "exec.h" +#include "shellcmd.h" + +using namespace std; +using namespace swss; + +map sflowSpeedRateInitMap = +{ + {SFLOW_SAMPLE_RATE_KEY_400G, SFLOW_SAMPLE_RATE_VALUE_400G}, + {SFLOW_SAMPLE_RATE_KEY_100G, SFLOW_SAMPLE_RATE_VALUE_100G}, + {SFLOW_SAMPLE_RATE_KEY_50G, SFLOW_SAMPLE_RATE_VALUE_50G}, + {SFLOW_SAMPLE_RATE_KEY_40G, SFLOW_SAMPLE_RATE_VALUE_40G}, + {SFLOW_SAMPLE_RATE_KEY_25G, SFLOW_SAMPLE_RATE_VALUE_25G}, + {SFLOW_SAMPLE_RATE_KEY_10G, SFLOW_SAMPLE_RATE_VALUE_10G}, + {SFLOW_SAMPLE_RATE_KEY_1G, SFLOW_SAMPLE_RATE_VALUE_1G} +}; + +SflowMgr::SflowMgr(DBConnector *cfgDb, DBConnector *appDb, const vector &tableNames) : + Orch(cfgDb, tableNames), + m_cfgSflowTable(cfgDb, CFG_SFLOW_TABLE_NAME), + m_cfgSflowSessionTable(cfgDb, CFG_SFLOW_SESSION_TABLE_NAME), + m_appSflowTable(appDb, APP_SFLOW_TABLE_NAME), + m_appSflowSessionTable(appDb, APP_SFLOW_SESSION_TABLE_NAME) +{ + m_intfAllConf = true; + m_gEnable = false; +} + +void SflowMgr::sflowHandleService(bool enable) +{ + stringstream cmd; + string res; + + SWSS_LOG_ENTER(); + + if (enable) + { + cmd << "service hsflowd restart"; + } + else + { + cmd << "service hsflowd stop"; + } + + int ret = swss::exec(cmd.str(), res); + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmd.str().c_str(), ret); + } + else + { + SWSS_LOG_NOTICE("Starting hsflowd service"); + SWSS_LOG_INFO("Command '%s' succeeded", cmd.str().c_str()); + } + +} + +void SflowMgr::sflowUpdatePortInfo(Consumer &consumer) +{ + auto it = consumer.m_toSync.begin(); + + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + + string key = kfvKey(t); + string op = kfvOp(t); + auto values = kfvFieldsValues(t); + + if (op == SET_COMMAND) + { + SflowPortInfo port_info; + bool new_port = false; + + auto sflowPortConf = m_sflowPortConfMap.find(key); + if (sflowPortConf == m_sflowPortConfMap.end()) + { + new_port = true; + port_info.local_conf = false; + port_info.speed = SFLOW_ERROR_SPEED_STR; + port_info.rate = ""; + port_info.admin = ""; + m_sflowPortConfMap[key] = port_info; + } + for (auto i : values) + { + if (fvField(i) == "speed") + { + m_sflowPortConfMap[key].speed = fvValue(i); + } + } + + if (new_port) + { + if (m_gEnable && m_intfAllConf) + { + vector fvs; + sflowGetGlobalInfo(fvs, m_sflowPortConfMap[key].speed); + m_appSflowSessionTable.set(key, fvs); + } + } + } + else if (op == DEL_COMMAND) + { + auto sflowPortConf = m_sflowPortConfMap.find(key); + if (sflowPortConf != m_sflowPortConfMap.end()) + { + bool local_cfg = m_sflowPortConfMap[key].local_conf; + + m_sflowPortConfMap.erase(key); + if ((m_intfAllConf && m_gEnable) || local_cfg) + { + m_appSflowSessionTable.del(key); + } + } + } + it = consumer.m_toSync.erase(it); + } +} + +void SflowMgr::sflowHandleSessionAll(bool enable) +{ + for (auto it: m_sflowPortConfMap) + { + if (!it.second.local_conf) + { + vector fvs; + sflowGetGlobalInfo(fvs, it.second.speed); + if (enable) + { + m_appSflowSessionTable.set(it.first, fvs); + } + else + { + m_appSflowSessionTable.del(it.first); + } + } + } +} + +void SflowMgr::sflowHandleSessionLocal(bool enable) +{ + for (auto it: m_sflowPortConfMap) + { + if (it.second.local_conf) + { + vector fvs; + sflowGetPortInfo(fvs, it.second); + if (enable) + { + m_appSflowSessionTable.set(it.first, fvs); + } + else + { + m_appSflowSessionTable.del(it.first); + } + } + } +} + +void SflowMgr::sflowGetGlobalInfo(vector &fvs, string speed) +{ + string rate; + FieldValueTuple fv1("admin_state", "up"); + fvs.push_back(fv1); + + if (speed != SFLOW_ERROR_SPEED_STR) + { + rate = sflowSpeedRateInitMap[speed]; + } + else + { + rate = SFLOW_ERROR_SPEED_STR; + } + FieldValueTuple fv2("sample_rate",rate); + fvs.push_back(fv2); +} + +void SflowMgr::sflowGetPortInfo(vector &fvs, SflowPortInfo &local_info) +{ + if (local_info.admin.length() > 0) + { + FieldValueTuple fv1("admin_state", local_info.admin); + fvs.push_back(fv1); + } + + FieldValueTuple fv2("sample_rate", local_info.rate); + fvs.push_back(fv2); +} + +void SflowMgr::sflowCheckAndFillValues(string alias, vector &fvs) +{ + string rate; + bool admin_present = false; + bool rate_present = false; + + for (auto i : fvs) + { + if (fvField(i) == "sample_rate") + { + rate_present = true; + m_sflowPortConfMap[alias].rate = fvValue(i); + } + if (fvField(i) == "admin_state") + { + admin_present = true; + m_sflowPortConfMap[alias].admin = fvValue(i); + } + } + + if (!rate_present) + { + if (m_sflowPortConfMap[alias].rate == "") + { + string speed = m_sflowPortConfMap[alias].speed; + + if (speed != SFLOW_ERROR_SPEED_STR) + { + rate = sflowSpeedRateInitMap[speed]; + } + else + { + rate = SFLOW_ERROR_SPEED_STR; + } + m_sflowPortConfMap[alias].rate = rate; + } + FieldValueTuple fv("sample_rate", m_sflowPortConfMap[alias].rate); + fvs.push_back(fv); + } + + if (!admin_present) + { + if (m_sflowPortConfMap[alias].admin == "") + { + /* By default admin state is enable if not set explicitely */ + m_sflowPortConfMap[alias].admin = "up"; + } + FieldValueTuple fv("admin_state", m_sflowPortConfMap[alias].admin); + fvs.push_back(fv); + } +} + +void SflowMgr::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto table = consumer.getTableName(); + + if (table == CFG_PORT_TABLE_NAME) + { + sflowUpdatePortInfo(consumer); + return; + } + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + + string key = kfvKey(t); + string op = kfvOp(t); + auto values = kfvFieldsValues(t); + + if (op == SET_COMMAND) + { + if (table == CFG_SFLOW_TABLE_NAME) + { + for (auto i : values) + { + if (fvField(i) == "admin_state") + { + bool enable = false; + if (fvValue(i) == "up") + { + enable = true; + } + if (enable == m_gEnable) + { + break; + } + m_gEnable = enable; + sflowHandleService(enable); + if (m_intfAllConf) + { + sflowHandleSessionAll(enable); + } + sflowHandleSessionLocal(enable); + } + } + m_appSflowTable.set(key, values); + } + else if (table == CFG_SFLOW_SESSION_TABLE_NAME) + { + if (key == "all") + { + for (auto i : values) + { + if (fvField(i) == "admin_state") + { + bool enable = false; + + if (fvValue(i) == "up") + { + enable = true; + } + if ((enable != m_intfAllConf) && (m_gEnable)) + { + sflowHandleSessionAll(enable); + } + m_intfAllConf = enable; + } + } + } + else + { + auto sflowPortConf = m_sflowPortConfMap.find(key); + + if (sflowPortConf == m_sflowPortConfMap.end()) + { + it++; + continue; + } + sflowCheckAndFillValues(key,values); + m_sflowPortConfMap[key].local_conf = true; + m_appSflowSessionTable.set(key, values); + } + } + } + else if (op == DEL_COMMAND) + { + if (table == CFG_SFLOW_TABLE_NAME) + { + if (m_gEnable) + { + sflowHandleService(false); + sflowHandleSessionAll(false); + } + m_gEnable = false; + m_appSflowTable.del(key); + } + else if (table == CFG_SFLOW_SESSION_TABLE_NAME) + { + if (key == "all") + { + if (!m_intfAllConf) + { + sflowHandleSessionAll(true); + } + m_intfAllConf = true; + } + else + { + m_appSflowSessionTable.del(key); + m_sflowPortConfMap[key].local_conf = false; + m_sflowPortConfMap[key].rate = ""; + m_sflowPortConfMap[key].admin = ""; + + /* If Global configured, set global session on port after local config is deleted */ + if (m_intfAllConf) + { + vector fvs; + sflowGetGlobalInfo(fvs, m_sflowPortConfMap[key].speed); + m_appSflowSessionTable.set(key,fvs); + } + } + } + } + it = consumer.m_toSync.erase(it); + } +} diff --git a/cfgmgr/sflowmgr.h b/cfgmgr/sflowmgr.h new file mode 100644 index 0000000000..16cd223798 --- /dev/null +++ b/cfgmgr/sflowmgr.h @@ -0,0 +1,67 @@ +#pragma once + +#include "dbconnector.h" +#include "orch.h" +#include "producerstatetable.h" + +#include +#include +#include + +namespace swss { + +#define SFLOW_SAMPLE_RATE_KEY_400G "400000" +#define SFLOW_SAMPLE_RATE_KEY_100G "100000" +#define SFLOW_SAMPLE_RATE_KEY_50G "50000" +#define SFLOW_SAMPLE_RATE_KEY_40G "40000" +#define SFLOW_SAMPLE_RATE_KEY_25G "25000" +#define SFLOW_SAMPLE_RATE_KEY_10G "10000" +#define SFLOW_SAMPLE_RATE_KEY_1G "1000" + +#define SFLOW_SAMPLE_RATE_VALUE_400G "40000" +#define SFLOW_SAMPLE_RATE_VALUE_100G "10000" +#define SFLOW_SAMPLE_RATE_VALUE_50G "5000" +#define SFLOW_SAMPLE_RATE_VALUE_40G "4000" +#define SFLOW_SAMPLE_RATE_VALUE_25G "2500" +#define SFLOW_SAMPLE_RATE_VALUE_10G "1000" +#define SFLOW_SAMPLE_RATE_VALUE_1G "100" + +#define SFLOW_ERROR_SPEED_STR "error" + +struct SflowPortInfo +{ + bool local_conf; + std::string speed; + std::string rate; + std::string admin; +}; + +/* Port to Local config map */ +typedef std::map SflowPortConfMap; + +class SflowMgr : public Orch +{ +public: + SflowMgr(DBConnector *cfgDb, DBConnector *appDb, const std::vector &tableNames); + + using Orch::doTask; +private: + Table m_cfgSflowTable; + Table m_cfgSflowSessionTable; + ProducerStateTable m_appSflowTable; + ProducerStateTable m_appSflowSessionTable; + SflowPortConfMap m_sflowPortConfMap; + bool m_intfAllConf; + bool m_gEnable; + + void doTask(Consumer &consumer); + void sflowHandleService(bool enable); + void sflowUpdatePortInfo(Consumer &consumer); + void sflowHandleSessionAll(bool enable); + void sflowHandleSessionLocal(bool enable); + void sflowCheckAndFillValues(std::string alias, std::vector &fvs); + void sflowGetPortInfo(std::vector &fvs, SflowPortInfo &local_info); + void sflowGetGlobalInfo(std::vector &fvs, std::string speed); +}; + +} diff --git a/cfgmgr/sflowmgrd.cpp b/cfgmgr/sflowmgrd.cpp new file mode 100644 index 0000000000..343f0ead0a --- /dev/null +++ b/cfgmgr/sflowmgrd.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include + +#include "exec.h" +#include "sflowmgr.h" +#include "schema.h" +#include "select.h" + +using namespace std; +using namespace swss; + +/* select() function timeout retry time, in millisecond */ +#define SELECT_TIMEOUT 1000 + +/* + * Following global variables are defined here for the purpose of + * using existing Orch class which is to be refactored soon to + * eliminate the direct exposure of the global variables. + * + * Once Orch class refactoring is done, these global variables + * should be removed from here. + */ +int gBatchSize = 0; +bool gSwssRecord = false; +bool gLogRotate = false; +ofstream gRecordOfs; +string gRecordFile; +/* Global database mutex */ +mutex gDbMutex; + +int main(int argc, char **argv) +{ + Logger::linkToDbNative("sflowmgrd"); + SWSS_LOG_ENTER(); + + SWSS_LOG_NOTICE("--- Starting sflowmgrd ---"); + + try + { + vector cfg_sflow_tables = { + CFG_SFLOW_TABLE_NAME, + CFG_SFLOW_SESSION_TABLE_NAME, + CFG_PORT_TABLE_NAME + }; + + DBConnector cfgDb(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector appDb(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + + SflowMgr sflowmgr(&cfgDb, &appDb, cfg_sflow_tables); + + vector cfgOrchList = {&sflowmgr}; + + swss::Select s; + for (Orch *o : cfgOrchList) + { + s.addSelectables(o->getSelectables()); + } + + while (true) + { + Selectable *sel; + int ret; + + ret = s.select(&sel, SELECT_TIMEOUT); + if (ret == Select::ERROR) + { + SWSS_LOG_NOTICE("Error: %s!", strerror(errno)); + continue; + } + if (ret == Select::TIMEOUT) + { + sflowmgr.doTask(); + continue; + } + + auto *c = (Executor *)sel; + c->execute(); + } + } + catch (const exception &e) + { + SWSS_LOG_ERROR("Runtime error: %s", e.what()); + } + return -1; +} diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index 7db2f403b1..d7a705a490 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -53,6 +53,7 @@ orchagent_SOURCES = \ flexcounterorch.cpp \ watermarkorch.cpp \ policerorch.cpp \ + sfloworch.cpp \ chassisorch.cpp orchagent_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index e174160e02..df151f9966 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -193,6 +193,13 @@ bool OrchDaemon::init() WatermarkOrch *wm_orch = new WatermarkOrch(m_configDb, wm_tables); + vector sflow_tables = { + APP_SFLOW_TABLE_NAME, + APP_SFLOW_SESSION_TABLE_NAME, + APP_SFLOW_SAMPLE_RATE_TABLE_NAME + }; + SflowOrch *sflow_orch = new SflowOrch(m_applDb, sflow_tables); + /* * The order of the orch list is important for state restore of warm start and * the queued processing in m_toSync map after gPortsOrch->allPortsReady() is set. @@ -201,7 +208,7 @@ bool OrchDaemon::init() * when iterating ConsumerMap. * That is ensured implicitly by the order of map key, "LAG_TABLE" is smaller than "VLAN_TABLE" in lexicographic order. */ - m_orchList = { gSwitchOrch, gCrmOrch, gBufferOrch, gPortsOrch, gIntfsOrch, gNeighOrch, gRouteOrch, copp_orch, tunnel_decap_orch, qos_orch, wm_orch, policer_orch }; + m_orchList = { gSwitchOrch, gCrmOrch, gBufferOrch, gPortsOrch, gIntfsOrch, gNeighOrch, gRouteOrch, copp_orch, tunnel_decap_orch, qos_orch, wm_orch, policer_orch, sflow_orch}; bool initialize_dtel = false; diff --git a/orchagent/orchdaemon.h b/orchagent/orchdaemon.h index 14d43fb2c1..bfa52fdef8 100644 --- a/orchagent/orchdaemon.h +++ b/orchagent/orchdaemon.h @@ -27,6 +27,7 @@ #include "flexcounterorch.h" #include "watermarkorch.h" #include "policerorch.h" +#include "sfloworch.h" #include "directory.h" using namespace swss; diff --git a/orchagent/saihelper.cpp b/orchagent/saihelper.cpp index 4b01ddeb39..dbadc7e288 100644 --- a/orchagent/saihelper.cpp +++ b/orchagent/saihelper.cpp @@ -41,6 +41,7 @@ sai_mirror_api_t* sai_mirror_api; sai_fdb_api_t* sai_fdb_api; sai_dtel_api_t* sai_dtel_api; sai_bmtor_api_t* sai_bmtor_api; +sai_samplepacket_api_t* sai_samplepacket_api; extern sai_object_id_t gSwitchId; extern bool gSairedisRecord; @@ -130,6 +131,7 @@ void initSaiApi() sai_api_query(SAI_API_ACL, (void **)&sai_acl_api); sai_api_query(SAI_API_DTEL, (void **)&sai_dtel_api); sai_api_query((sai_api_t)SAI_API_BMTOR, (void **)&sai_bmtor_api); + sai_api_query(SAI_API_SAMPLEPACKET, (void **)&sai_samplepacket_api); sai_log_set(SAI_API_SWITCH, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_BRIDGE, SAI_LOG_LEVEL_NOTICE); @@ -156,6 +158,7 @@ void initSaiApi() sai_log_set(SAI_API_ACL, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_DTEL, SAI_LOG_LEVEL_NOTICE); sai_log_set((sai_api_t)SAI_API_BMTOR, SAI_LOG_LEVEL_NOTICE); + sai_log_set(SAI_API_SAMPLEPACKET, SAI_LOG_LEVEL_NOTICE); } void initSaiRedis(const string &record_location) diff --git a/orchagent/sfloworch.cpp b/orchagent/sfloworch.cpp new file mode 100644 index 0000000000..3c00c23f9f --- /dev/null +++ b/orchagent/sfloworch.cpp @@ -0,0 +1,347 @@ +#include "sai.h" +#include "sfloworch.h" +#include "tokenize.h" + +using namespace std; +using namespace swss; + +extern sai_samplepacket_api_t* sai_samplepacket_api; +extern sai_port_api_t* sai_port_api; +extern sai_object_id_t gSwitchId; +extern PortsOrch* gPortsOrch; + +SflowOrch::SflowOrch(DBConnector* db, vector &tableNames) : + Orch(db, tableNames) +{ + SWSS_LOG_ENTER(); + m_sflowStatus = false; +} + +bool SflowOrch::sflowCreateSession(uint32_t rate, SflowSession &session) +{ + sai_attribute_t attr; + sai_object_id_t session_id = SAI_NULL_OBJECT_ID; + sai_status_t sai_rc; + + attr.id = SAI_SAMPLEPACKET_ATTR_SAMPLE_RATE; + attr.value.u32 = rate; + + sai_rc = sai_samplepacket_api->create_samplepacket(&session_id, gSwitchId, + 1, &attr); + if (sai_rc != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create sample packet session with rate %d", rate); + return false; + } + session.m_sample_id = session_id; + session.ref_count = 0; + return true; +} + +bool SflowOrch::sflowDestroySession(SflowSession &session) +{ + sai_status_t sai_rc; + + sai_rc = sai_samplepacket_api->remove_samplepacket(session.m_sample_id); + if (sai_rc != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to destroy sample packet session with id %lx", + session.m_sample_id); + return false; + } + return true; +} + +bool SflowOrch::sflowUpdateRate(sai_object_id_t port_id, uint32_t rate) +{ + auto port_info = m_sflowPortInfoMap.find(port_id); + auto session = m_sflowRateSampleMap.find(rate); + SflowSession new_session; + uint32_t old_rate = sflowSessionGetRate(port_info->second.m_sample_id); + + if (session == m_sflowRateSampleMap.end()) + { + if (!sflowCreateSession(rate, new_session)) + { + SWSS_LOG_ERROR("Creating sflow session with rate %d failed", rate); + return false; + } + m_sflowRateSampleMap[rate] = new_session; + } + else + { + new_session = session->second; + } + + if (port_info->second.admin_state) + { + if (!sflowAddPort(new_session.m_sample_id, port_id)) + { + return false; + } + } + port_info->second.m_sample_id = new_session.m_sample_id; + + m_sflowRateSampleMap[rate].ref_count++; + m_sflowRateSampleMap[old_rate].ref_count--; + if (m_sflowRateSampleMap[old_rate].ref_count == 0) + { + if (!sflowDestroySession(m_sflowRateSampleMap[old_rate])) + { + SWSS_LOG_ERROR("Failed to clean old session %lx with rate %d", + m_sflowRateSampleMap[old_rate].m_sample_id, old_rate); + } + else + { + m_sflowRateSampleMap.erase(old_rate); + } + } + return true; +} + +bool SflowOrch::sflowAddPort(sai_object_id_t sample_id, sai_object_id_t port_id) +{ + sai_attribute_t attr; + sai_status_t sai_rc; + + attr.id = SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE; + attr.value.oid = sample_id; + sai_rc = sai_port_api->set_port_attribute(port_id, &attr); + + if (sai_rc != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set session %lx on port %lx", sample_id, port_id); + return false; + } + return true; +} + +bool SflowOrch::sflowDelPort(sai_object_id_t port_id) +{ + sai_attribute_t attr; + sai_status_t sai_rc; + + attr.id = SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE; + attr.value.oid = SAI_NULL_OBJECT_ID; + sai_rc = sai_port_api->set_port_attribute(port_id, &attr); + + if (sai_rc != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to delete session on port %lx", port_id); + return false; + } + return true; +} + +void SflowOrch::sflowExtractInfo(vector &fvs, bool &admin, uint32_t &rate) +{ + for (auto i : fvs) + { + if (fvField(i) == "admin_state") + { + if (fvValue(i) == "up") + { + admin = true; + } + else if (fvValue(i) == "down") + { + admin = false; + } + } + else if (fvField(i) == "sample_rate") + { + if (fvValue(i) != "error") + { + rate = (uint32_t)stoul(fvValue(i)); + } + else + { + rate = 0; + } + } + } +} + +void SflowOrch::sflowStatusSet(Consumer &consumer) +{ + auto it = consumer.m_toSync.begin(); + + while (it != consumer.m_toSync.end()) + { + auto tuple = it->second; + string op = kfvOp(tuple); + uint32_t rate = 0; + + if (op == SET_COMMAND) + { + sflowExtractInfo(kfvFieldsValues(tuple), m_sflowStatus, rate); + } + else if (op == DEL_COMMAND) + { + m_sflowStatus = false; + } + it = consumer.m_toSync.erase(it); + } +} + +uint32_t SflowOrch::sflowSessionGetRate(sai_object_id_t m_sample_id) +{ + for (auto it: m_sflowRateSampleMap) + { + if (it.second.m_sample_id == m_sample_id) + { + return it.first; + } + } + return 0; +} + +bool SflowOrch::handleSflowSessionDel(sai_object_id_t port_id) +{ + auto sflowInfo = m_sflowPortInfoMap.find(port_id); + + if (sflowInfo != m_sflowPortInfoMap.end()) + { + uint32_t rate = sflowSessionGetRate(sflowInfo->second.m_sample_id); + if (sflowInfo->second.admin_state) + { + if (!sflowDelPort(port_id)) + { + return false; + } + sflowInfo->second.admin_state = false; + } + + m_sflowPortInfoMap.erase(port_id); + m_sflowRateSampleMap[rate].ref_count--; + if (m_sflowRateSampleMap[rate].ref_count == 0) + { + if (!sflowDestroySession(m_sflowRateSampleMap[rate])) + { + return false; + } + m_sflowRateSampleMap.erase(rate); + } + } + return true; +} + +void SflowOrch::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + Port port; + string table_name = consumer.getTableName(); + + if (table_name == APP_SFLOW_TABLE_NAME) + { + sflowStatusSet(consumer); + return; + } + if (!gPortsOrch->allPortsReady()) + { + return; + } + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + auto tuple = it->second; + string op = kfvOp(tuple); + string alias = kfvKey(tuple); + + gPortsOrch->getPort(alias, port); + if (op == SET_COMMAND) + { + bool admin_state = m_sflowStatus; + uint32_t rate = 0; + + if (!m_sflowStatus) + { + return; + } + auto sflowInfo = m_sflowPortInfoMap.find(port.m_port_id); + if (sflowInfo != m_sflowPortInfoMap.end()) + { + rate = sflowSessionGetRate(sflowInfo->second.m_sample_id); + admin_state = sflowInfo->second.admin_state; + } + + sflowExtractInfo(kfvFieldsValues(tuple), admin_state, rate); + if (sflowInfo == m_sflowPortInfoMap.end()) + { + if (rate == 0) + { + it++; + continue; + } + + SflowPortInfo port_info; + auto session_info = m_sflowRateSampleMap.find(rate); + if (session_info != m_sflowRateSampleMap.end()) + { + port_info.m_sample_id = session_info->second.m_sample_id; + } + else + { + SflowSession session; + if (!sflowCreateSession(rate, session)) + { + it++; + continue; + } + m_sflowRateSampleMap[rate] = session; + port_info.m_sample_id = session.m_sample_id; + } + if (admin_state) + { + if (!sflowAddPort(port_info.m_sample_id, port.m_port_id)) + { + it++; + continue; + } + } + port_info.admin_state = admin_state; + m_sflowPortInfoMap[port.m_port_id] = port_info; + m_sflowRateSampleMap[rate].ref_count++; + } + else + { + if (rate != sflowSessionGetRate(sflowInfo->second.m_sample_id)) + { + if (!sflowUpdateRate(port.m_port_id, rate)) + { + it++; + continue; + } + } + if (admin_state != sflowInfo->second.admin_state) + { + bool ret = false; + if (admin_state) + { + ret = sflowAddPort(sflowInfo->second.m_sample_id, port.m_port_id); + } + else + { + ret = sflowDelPort(port.m_port_id); + } + if (!ret) + { + it++; + continue; + } + sflowInfo->second.admin_state = admin_state; + } + } + } + else if (op == DEL_COMMAND) + { + if (!handleSflowSessionDel(port.m_port_id)) + { + it++; + continue; + } + } + it = consumer.m_toSync.erase(it); + } +} diff --git a/orchagent/sfloworch.h b/orchagent/sfloworch.h new file mode 100644 index 0000000000..ea63c092a4 --- /dev/null +++ b/orchagent/sfloworch.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#include "orch.h" +#include "portsorch.h" + +struct SflowPortInfo +{ + bool admin_state; + sai_object_id_t m_sample_id; +}; + +struct SflowSession +{ + sai_object_id_t m_sample_id; + uint32_t ref_count; +}; + +/* SAI Port to Sflow Port Info Map */ +typedef std::map SflowPortInfoMap; + +/* Sample-rate(unsigned int) to Sflow session map */ +typedef std::map SflowRateSampleMap; + +class SflowOrch : public Orch +{ +public: + SflowOrch(DBConnector* db, std::vector &tableNames); + +private: + SflowPortInfoMap m_sflowPortInfoMap; + SflowRateSampleMap m_sflowRateSampleMap; + bool m_sflowStatus; + + virtual void doTask(Consumer& consumer); + bool sflowCreateSession(uint32_t rate, SflowSession &session); + bool sflowDestroySession(SflowSession &session); + bool sflowAddPort(sai_object_id_t sample_id, sai_object_id_t port_id); + bool sflowDelPort(sai_object_id_t port_id); + void sflowStatusSet(Consumer &consumer); + bool sflowUpdateRate(sai_object_id_t port_id, uint32_t rate); + uint32_t sflowSessionGetRate(sai_object_id_t sample_id); + bool handleSflowSessionDel(sai_object_id_t port_id); + void sflowExtractInfo(std::vector &fvs, bool &admin, uint32_t &rate); +}; diff --git a/tests/test_sflow.py b/tests/test_sflow.py new file mode 100644 index 0000000000..1e064d699e --- /dev/null +++ b/tests/test_sflow.py @@ -0,0 +1,130 @@ +from swsscommon import swsscommon + +import time +import os + +class TestSflow(object): + def setup_sflow(self, dvs): + self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) + self.adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) + ptbl = swsscommon.ProducerStateTable(self.pdb, "SFLOW_TABLE") + fvs = swsscommon.FieldValuePairs([("admin_state", "up")]) + ptbl.set("global", fvs) + + time.sleep(1) + + def test_SflowDisble(self, dvs, testlog): + self.setup_sflow(dvs) + ptbl = swsscommon.ProducerStateTable(self.pdb, "SFLOW_SESSION_TABLE") + gtbl = swsscommon.ProducerStateTable(self.pdb, "SFLOW_TABLE") + fvs = swsscommon.FieldValuePairs([("admin_state", "down")]) + gtbl.set("global", fvs) + + time.sleep(1) + fvs = swsscommon.FieldValuePairs([("admin_state", "up"),("sample_rate","1000")]) + ptbl.set("Ethernet0", fvs) + + time.sleep(1) + + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") + (status, fvs) = atbl.get(dvs.asicdb.portnamemap["Ethernet0"]) + + assert status == True + + sample_session = "" + for fv in fvs: + if fv[0] == "SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE": + sample_session = fv[1] + + assert sample_session == "" + + fvs = swsscommon.FieldValuePairs([("admin_state", "up")]) + gtbl.set("global", fvs) + + time.sleep(1) + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") + (status, fvs) = atbl.get(dvs.asicdb.portnamemap["Ethernet0"]) + + assert status == True + + sample_session = "" + for fv in fvs: + if fv[0] == "SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE": + sample_session = fv[1] + + assert sample_session != "oid:0x0" + assert sample_session != "" + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_SAMPLEPACKET") + (status, fvs) = atbl.get(sample_session) + + assert status == True + + for fv in fvs: + if fv[0] == "SAI_SAMPLEPACKET_ATTR_SAMPLE_RATE": + assert fv[1] == "1000" + + ptbl._del("Ethernet0") + + def test_InterfaceSet(self, dvs, testlog): + self.setup_sflow(dvs) + ptbl = swsscommon.ProducerStateTable(self.pdb, "SFLOW_SESSION_TABLE") + gtbl = swsscommon.ProducerStateTable(self.pdb, "SFLOW_TABLE") + fvs = swsscommon.FieldValuePairs([("admin_state", "up"),("sample_rate","1000")]) + ptbl.set("Ethernet0", fvs) + + time.sleep(1) + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") + (status, fvs) = atbl.get(dvs.asicdb.portnamemap["Ethernet0"]) + + assert status == True + + sample_session = "" + for fv in fvs: + if fv[0] == "SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE": + sample_session = fv[1] + + assert sample_session != "" + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_SAMPLEPACKET") + (status, fvs) = atbl.get(sample_session) + + assert status == True + + for fv in fvs: + if fv[0] == "SAI_SAMPLEPACKET_ATTR_SAMPLE_RATE": + assert fv[1] == "1000" + + ptbl._del("Ethernet0") + + def test_ConfigDel(self, dvs, testlog): + self.setup_sflow(dvs) + ptbl = swsscommon.ProducerStateTable(self.pdb, "SFLOW_SESSION_TABLE") + fvs = swsscommon.FieldValuePairs([("admin_state", "up"),("sample_rate","1000")]) + ptbl.set("Ethernet0", fvs) + + time.sleep(1) + + ptbl._del("Ethernet0") + + time.sleep(1) + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") + (status, fvs) = atbl.get(dvs.asicdb.portnamemap["Ethernet0"]) + + assert status == True + + sample_session = "" + speed = "" + + for fv in fvs: + if fv[0] == "SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE": + sample_session = fv[1] + elif fv[0] == "SAI_PORT_ATTR_SPEED": + speed = fv[1] + + assert speed != "" + assert sample_session == "oid:0x0" From 59440f28a3db5edaa123ae9ed34ad29f20d678f3 Mon Sep 17 00:00:00 2001 From: Wenda Ni Date: Tue, 29 Oct 2019 11:20:41 -0700 Subject: [PATCH 06/63] Allow buffer profile apply after init (#1099) * Allow buffer profile application after init (i.e., at run time) Signed-off-by: Wenda Ni * Address comment: Alert when a buffer profile is applied after the physical port is brought up Signed-off-by: Wenda Ni * Remove unnecessary space Signed-off-by: Wenda Ni * Correct logic Signed-off-by: Wenda Ni * Correct compile error Signed-off-by: Wenda Ni --- orchagent/bufferorch.cpp | 28 ++++++++++++++++++++++++++-- orchagent/portsorch.cpp | 11 +++++++++++ orchagent/portsorch.h | 1 + portsyncd/portsyncd.cpp | 1 - 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/orchagent/bufferorch.cpp b/orchagent/bufferorch.cpp index c560551bca..2257e927bd 100644 --- a/orchagent/bufferorch.cpp +++ b/orchagent/bufferorch.cpp @@ -579,7 +579,19 @@ task_process_status BufferOrch::processQueue(Consumer &consumer) } else { - SWSS_LOG_ERROR("Queue profile '%s' was inserted after BufferOrch init", key.c_str()); + // If a buffer queue profile is not in the initial CONFIG_DB BUFFER_QUEUE table + // at BufferOrch object instantiation, it is considered being applied + // at run time, and, in this case, is not tracked in the m_ready_list. It is up to + // the application to guarantee the set order that the buffer queue profile + // should be applied to a physical port before the physical port is brought up to + // carry traffic. Here, we alert to application through syslog when such a wrong + // set order is detected. + for (const auto &port_name : port_names) + { + if (gPortsOrch->isPortAdminUp(port_name)) { + SWSS_LOG_ERROR("Queue profile '%s' applied after port %s is up", key.c_str(), port_name.c_str()); + } + } } return task_process_status::task_success; @@ -666,7 +678,19 @@ task_process_status BufferOrch::processPriorityGroup(Consumer &consumer) } else { - SWSS_LOG_ERROR("PG profile '%s' was inserted after BufferOrch init", key.c_str()); + // If a buffer pg profile is not in the initial CONFIG_DB BUFFER_PG table + // at BufferOrch object instantiation, it is considered being applied + // at run time, and, in this case, is not tracked in the m_ready_list. It is up to + // the application to guarantee the set order that the buffer pg profile + // should be applied to a physical port before the physical port is brought up to + // carry traffic. Here, we alert to application through syslog when such a wrong + // set order is detected. + for (const auto &port_name : port_names) + { + if (gPortsOrch->isPortAdminUp(port_name)) { + SWSS_LOG_ERROR("PG profile '%s' applied after port %s is up", key.c_str(), port_name.c_str()); + } + } } return task_process_status::task_success; diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index 6901593754..ae876467fb 100644 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -400,6 +400,17 @@ bool PortsOrch::isInitDone() return m_initDone; } +bool PortsOrch::isPortAdminUp(const string &alias) +{ + auto it = m_portList.find(alias); + if (it == m_portList.end()) + { + SWSS_LOG_ERROR("Failed to get Port object by port alias: %s", alias.c_str()); + return false; + } + + return it->second.m_admin_state_up; +} map& PortsOrch::getAllPorts() { diff --git a/orchagent/portsorch.h b/orchagent/portsorch.h index d2819e62ea..6b12dd24fb 100644 --- a/orchagent/portsorch.h +++ b/orchagent/portsorch.h @@ -57,6 +57,7 @@ class PortsOrch : public Orch, public Subject bool allPortsReady(); bool isInitDone(); + bool isPortAdminUp(const string &alias); map& getAllPorts(); bool bake() override; diff --git a/portsyncd/portsyncd.cpp b/portsyncd/portsyncd.cpp index 132e1cbf83..7efec9d58a 100644 --- a/portsyncd/portsyncd.cpp +++ b/portsyncd/portsyncd.cpp @@ -166,7 +166,6 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - return 1; } From 5516ec4d0c9689edc9ad2d4f9ae9e812442064c6 Mon Sep 17 00:00:00 2001 From: Prince Sunny Date: Tue, 29 Oct 2019 15:59:09 -0700 Subject: [PATCH 07/63] Check RIF/Port exists only for add entries (#1110) --- orchagent/neighorch.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/orchagent/neighorch.cpp b/orchagent/neighorch.cpp index 5f81e4ba73..ae0db0751c 100644 --- a/orchagent/neighorch.cpp +++ b/orchagent/neighorch.cpp @@ -317,27 +317,27 @@ void NeighOrch::doTask(Consumer &consumer) continue; } - Port p; - if (!gPortsOrch->getPort(alias, p)) - { - SWSS_LOG_INFO("Port %s doesn't exist", alias.c_str()); - it++; - continue; - } - - if (!p.m_rif_id) - { - SWSS_LOG_INFO("Router interface doesn't exist on %s", alias.c_str()); - it++; - continue; - } - IpAddress ip_address(key.substr(found+1)); NeighborEntry neighbor_entry = { ip_address, alias }; if (op == SET_COMMAND) { + Port p; + if (!gPortsOrch->getPort(alias, p)) + { + SWSS_LOG_INFO("Port %s doesn't exist", alias.c_str()); + it++; + continue; + } + + if (!p.m_rif_id) + { + SWSS_LOG_INFO("Router interface doesn't exist on %s", alias.c_str()); + it++; + continue; + } + MacAddress mac_address; for (auto i = kfvFieldsValues(t).begin(); i != kfvFieldsValues(t).end(); i++) From d4ccdc3310e828d68421377454cc5b3d28c2af3a Mon Sep 17 00:00:00 2001 From: Qi Luo Date: Tue, 29 Oct 2019 18:34:25 -0700 Subject: [PATCH 08/63] Quote input strings before constructing a command line (#1098) * Quote input strings before constructing a command line * Fix an input injection with embedded command * Use dash double quotation instead of bash $'string' style quotation, because system() and popen() will use `sh` internally and `sh` is symlink of `dash` on Debian --- cfgmgr/intfmgr.cpp | 12 ++--- cfgmgr/portmgr.cpp | 4 +- cfgmgr/shellcmd.h | 9 ++++ cfgmgr/teammgr.cpp | 16 +++--- cfgmgr/vlanmgr.cpp | 36 ++++++------- cfgmgr/vrfmgr.cpp | 6 +-- cfgmgr/vxlanmgr.cpp | 117 +++++++++++++++++++++-------------------- configure.ac | 2 +- portsyncd/linksync.cpp | 16 +++--- tests/Makefile.am | 3 +- tests/quoted_ut.cpp | 40 ++++++++++++++ 11 files changed, 158 insertions(+), 103 deletions(-) create mode 100644 tests/quoted_ut.cpp diff --git a/cfgmgr/intfmgr.cpp b/cfgmgr/intfmgr.cpp index e7365f538d..18f1d8e2f1 100644 --- a/cfgmgr/intfmgr.cpp +++ b/cfgmgr/intfmgr.cpp @@ -41,14 +41,14 @@ void IntfMgr::setIntfIp(const string &alias, const string &opCmd, if (ipPrefix.isV4()) { (prefixLen < 31) ? - (cmd << IP_CMD << " address " << opCmd << " " << ipPrefixStr << " broadcast " << broadcastIpStr <<" dev " << alias) : - (cmd << IP_CMD << " address " << opCmd << " " << ipPrefixStr << " dev " << alias); + (cmd << IP_CMD << " address " << shellquote(opCmd) << " " << shellquote(ipPrefixStr) << " broadcast " << shellquote(broadcastIpStr) <<" dev " << shellquote(alias)) : + (cmd << IP_CMD << " address " << shellquote(opCmd) << " " << shellquote(ipPrefixStr) << " dev " << shellquote(alias)); } else { (prefixLen < 127) ? - (cmd << IP_CMD << " -6 address " << opCmd << " " << ipPrefixStr << " broadcast " << broadcastIpStr << " dev " << alias) : - (cmd << IP_CMD << " -6 address " << opCmd << " " << ipPrefixStr << " dev " << alias); + (cmd << IP_CMD << " -6 address " << shellquote(opCmd) << " " << shellquote(ipPrefixStr) << " broadcast " << shellquote(broadcastIpStr) << " dev " << shellquote(alias)) : + (cmd << IP_CMD << " -6 address " << shellquote(opCmd) << " " << shellquote(ipPrefixStr) << " dev " << shellquote(alias)); } int ret = swss::exec(cmd.str(), res); @@ -65,11 +65,11 @@ void IntfMgr::setIntfVrf(const string &alias, const string vrfName) if (!vrfName.empty()) { - cmd << IP_CMD << " link set " << alias << " master " << vrfName; + cmd << IP_CMD << " link set " << shellquote(alias) << " master " << shellquote(vrfName); } else { - cmd << IP_CMD << " link set " << alias << " nomaster"; + cmd << IP_CMD << " link set " << shellquote(alias) << " nomaster"; } EXEC_WITH_ERROR_THROW(cmd.str(), res); } diff --git a/cfgmgr/portmgr.cpp b/cfgmgr/portmgr.cpp index 03b2e61eb6..765198833a 100644 --- a/cfgmgr/portmgr.cpp +++ b/cfgmgr/portmgr.cpp @@ -25,7 +25,7 @@ bool PortMgr::setPortMtu(const string &alias, const string &mtu) string res; // ip link set dev mtu - cmd << IP_CMD << " link set dev " << alias << " mtu " << mtu; + cmd << IP_CMD << " link set dev " << shellquote(alias) << " mtu " << shellquote(mtu); EXEC_WITH_ERROR_THROW(cmd.str(), res); // Set the port MTU in application database to update both @@ -44,7 +44,7 @@ bool PortMgr::setPortAdminStatus(const string &alias, const bool up) string res; // ip link set dev [up|down] - cmd << IP_CMD << " link set dev " << alias << (up ? " up" : " down"); + cmd << IP_CMD << " link set dev " << shellquote(alias) << (up ? " up" : " down"); EXEC_WITH_ERROR_THROW(cmd.str(), res); vector fvs; diff --git a/cfgmgr/shellcmd.h b/cfgmgr/shellcmd.h index 7507da2892..a8a7afa51c 100644 --- a/cfgmgr/shellcmd.h +++ b/cfgmgr/shellcmd.h @@ -1,6 +1,9 @@ #ifndef __SHELLCMD__ #define __SHELLCMD__ +#include +#include + #define IP_CMD "/sbin/ip" #define BRIDGE_CMD "/sbin/bridge" #define BRCTL_CMD "/sbin/brctl" @@ -18,4 +21,10 @@ } \ }) +static inline std::string shellquote(const std::string& str) +{ + static const std::regex re("([$`\"\\\n])"); + return "\"" + std::regex_replace(str, re, "\\$1") + "\""; +} + #endif /* __SHELLCMD__ */ diff --git a/cfgmgr/teammgr.cpp b/cfgmgr/teammgr.cpp index 448a7ac800..2149e1ce5f 100644 --- a/cfgmgr/teammgr.cpp +++ b/cfgmgr/teammgr.cpp @@ -319,7 +319,7 @@ bool TeamMgr::setLagAdminStatus(const string &alias, const string &admin_status) string res; // ip link set dev [up|down] - cmd << IP_CMD << " link set dev " << alias << " " << admin_status; + cmd << IP_CMD << " link set dev " << shellquote(alias) << " " << shellquote(admin_status); EXEC_WITH_ERROR_THROW(cmd.str(), res); SWSS_LOG_NOTICE("Set port channel %s admin status to %s", @@ -336,7 +336,7 @@ bool TeamMgr::setLagMtu(const string &alias, const string &mtu) string res; // ip link set dev mtu - cmd << IP_CMD << " link set dev " << alias << " mtu " << mtu; + cmd << IP_CMD << " link set dev " << shellquote(alias) << " mtu " << shellquote(mtu); EXEC_WITH_ERROR_THROW(cmd.str(), res); vector fvs; @@ -423,7 +423,7 @@ bool TeamMgr::removeLag(const string &alias) stringstream cmd; string res; - cmd << TEAMD_CMD << " -k -t " << alias; + cmd << TEAMD_CMD << " -k -t " << shellquote(alias); EXEC_WITH_ERROR_THROW(cmd.str(), res); SWSS_LOG_NOTICE("Stop port channel %s", alias.c_str()); @@ -451,8 +451,8 @@ task_process_status TeamMgr::addLagMember(const string &lag, const string &membe // Set admin down LAG member (required by teamd) and enslave it // ip link set dev down; // teamdctl port add ; - cmd << IP_CMD << " link set dev " << member << " down; "; - cmd << TEAMDCTL_CMD << " " << lag << " port add " << member; + cmd << IP_CMD << " link set dev " << shellquote(member) << " down; "; + cmd << TEAMDCTL_CMD << " " << shellquote(lag) << " port add " << shellquote(member); if (exec(cmd.str(), res) != 0) { @@ -505,7 +505,7 @@ task_process_status TeamMgr::addLagMember(const string &lag, const string &membe // ip link set dev [up|down] cmd.str(string()); - cmd << IP_CMD << " link set dev " << member << " " << admin_status; + cmd << IP_CMD << " link set dev " << shellquote(member) << " " << shellquote(admin_status); EXEC_WITH_ERROR_THROW(cmd.str(), res); fvs.clear(); @@ -550,8 +550,8 @@ bool TeamMgr::removeLagMember(const string &lag, const string &member) // ip link set dev [up|down]; // ip link set dev mtu - cmd << IP_CMD << " link set dev " << member << " " << admin_status << "; "; - cmd << IP_CMD << " link set dev " << member << " mtu " << mtu; + cmd << IP_CMD << " link set dev " << shellquote(member) << " " << shellquote(admin_status) << "; "; + cmd << IP_CMD << " link set dev " << shellquote(member) << " mtu " << shellquote(mtu); EXEC_WITH_ERROR_THROW(cmd.str(), res); fvs.clear(); diff --git a/cfgmgr/vlanmgr.cpp b/cfgmgr/vlanmgr.cpp index fc5949252e..d8834d12d9 100644 --- a/cfgmgr/vlanmgr.cpp +++ b/cfgmgr/vlanmgr.cpp @@ -134,11 +134,11 @@ bool VlanMgr::setHostVlanAdminState(int vlan_id, const string &admin_status) // The command should be generated as: // /sbin/ip link set Vlan{{vlan_id}} {{admin_status}} - const std::string cmds = std::string("") - + IP_CMD + " link set " + VLAN_PREFIX + std::to_string(vlan_id) + " " + admin_status; + ostringstream cmds; + cmds << IP_CMD " link set " VLAN_PREFIX + std::to_string(vlan_id) + " " << shellquote(admin_status); std::string res; - EXEC_WITH_ERROR_THROW(cmds, res); + EXEC_WITH_ERROR_THROW(cmds.str(), res); return true; } @@ -177,14 +177,14 @@ bool VlanMgr::addHostVlanMember(int vlan_id, const string &port_alias, const str // /bin/bash -c "/sbin/ip link set {{port_alias}} master Bridge && // /sbin/bridge vlan del vid 1 dev {{ port_alias }} && // /sbin/bridge vlan add vid {{vlan_id}} dev {{port_alias}} {{tagging_mode}}" - const std::string cmds = std::string("") - + BASH_CMD + " -c \"" - + IP_CMD + " link set " + port_alias + " master " + DOT1Q_BRIDGE_NAME + " && " - + BRIDGE_CMD + " vlan del vid " + DEFAULT_VLAN_ID + " dev " + port_alias + " && " - + BRIDGE_CMD + " vlan add vid " + std::to_string(vlan_id) + " dev " + port_alias + " " + tagging_cmd + "\""; + ostringstream cmds, inner; + inner << IP_CMD " link set " << shellquote(port_alias) << " master " DOT1Q_BRIDGE_NAME " && " + BRIDGE_CMD " vlan del vid " DEFAULT_VLAN_ID " dev " << shellquote(port_alias) << " && " + BRIDGE_CMD " vlan add vid " + std::to_string(vlan_id) + " dev " << shellquote(port_alias) << " " + tagging_cmd; + cmds << BASH_CMD " -c " << shellquote(inner.str()); std::string res; - EXEC_WITH_ERROR_THROW(cmds, res); + EXEC_WITH_ERROR_THROW(cmds.str(), res); return true; } @@ -202,17 +202,17 @@ bool VlanMgr::removeHostVlanMember(int vlan_id, const string &port_alias) // else exit $ret; fi )' // When port is not member of any VLAN, it shall be detached from Dot1Q bridge! - const std::string cmds = std::string("") - + BASH_CMD + " -c \'" - + BRIDGE_CMD + " vlan del vid " + std::to_string(vlan_id) + " dev " + port_alias + " && ( " - + BRIDGE_CMD + " vlan show dev " + port_alias + " | " - + GREP_CMD + " -q None; ret=$?; if [ $ret -eq 0 ]; then " - + IP_CMD + " link set " + port_alias + " nomaster; " - + "elif [ $ret -eq 1 ]; then exit 0; " - + "else exit $ret; fi )\'"; + ostringstream cmds, inner; + inner << BRIDGE_CMD " vlan del vid " + std::to_string(vlan_id) + " dev " << shellquote(port_alias) << " && ( " + BRIDGE_CMD " vlan show dev " << shellquote(port_alias) << " | " + GREP_CMD " -q None; ret=$?; if [ $ret -eq 0 ]; then " + IP_CMD " link set " << shellquote(port_alias) << " nomaster; " + "elif [ $ret -eq 1 ]; then exit 0; " + "else exit $ret; fi )"; + cmds << BASH_CMD " -c " << shellquote(cmds.str()); std::string res; - EXEC_WITH_ERROR_THROW(cmds, res); + EXEC_WITH_ERROR_THROW(cmds.str(), res); return true; } diff --git a/cfgmgr/vrfmgr.cpp b/cfgmgr/vrfmgr.cpp index c8edf21512..d8048a6b35 100644 --- a/cfgmgr/vrfmgr.cpp +++ b/cfgmgr/vrfmgr.cpp @@ -99,7 +99,7 @@ bool VrfMgr::delLink(const string& vrfName) return false; } - cmd << IP_CMD << " link del " << vrfName; + cmd << IP_CMD << " link del " << shellquote(vrfName); EXEC_WITH_ERROR_THROW(cmd.str(), res); recycleTable(m_vrfTableMap[vrfName]); @@ -126,14 +126,14 @@ bool VrfMgr::setLink(const string& vrfName) return false; } - cmd << IP_CMD << " link add " << vrfName << " type vrf table " << table; + cmd << IP_CMD << " link add " << shellquote(vrfName) << " type vrf table " << table; EXEC_WITH_ERROR_THROW(cmd.str(), res); m_vrfTableMap.emplace(vrfName, table); cmd.str(""); cmd.clear(); - cmd << IP_CMD << " link set " << vrfName << " up"; + cmd << IP_CMD << " link set " << shellquote(vrfName) << " up"; EXEC_WITH_ERROR_THROW(cmd.str(), res); return true; diff --git a/cfgmgr/vxlanmgr.cpp b/cfgmgr/vxlanmgr.cpp index 40d3362406..6706407bdb 100644 --- a/cfgmgr/vxlanmgr.cpp +++ b/cfgmgr/vxlanmgr.cpp @@ -40,111 +40,113 @@ static std::string getVxlanIfName(const swss::VxlanMgr::VxlanInfo & info) // Commands #define RET_SUCCESS 0 -#define EXECUTE(CMD, RESULT) swss::exec(std::string() + BASH_CMD + " -c \"" + CMD + "\"", RESULT); static int cmdCreateVxlan(const swss::VxlanMgr::VxlanInfo & info, std::string & res) { // ip link add {{VXLAN}} type vxlan id {{VNI}} [local {{SOURCE IP}}] dstport 4789 - const std::string cmd = std::string("") - + IP_CMD " link add " - + info.m_vxlan - + " type vxlan id " - + info.m_vni - + " " - + (info.m_sourceIp.empty() ? "" : (" local " + info.m_sourceIp)) - + " dstport 4789"; - return EXECUTE(cmd, res); + ostringstream cmd; + cmd << IP_CMD " link add " + << shellquote(info.m_vxlan) + << " type vxlan id " + << shellquote(info.m_vni) + << " "; + if (!info.m_sourceIp.empty()) + { + cmd << " local " << shellquote(info.m_sourceIp); + } + cmd << " dstport 4789"; + return swss::exec(cmd.str(), res); } static int cmdUpVxlan(const swss::VxlanMgr::VxlanInfo & info, std::string & res) { // ip link set dev {{VXLAN}} up - const std::string cmd = std::string("") - + IP_CMD " link set dev " - + info.m_vxlan - + " up"; - return EXECUTE(cmd, res); + ostringstream cmd; + cmd << IP_CMD " link set dev " + << shellquote(info.m_vxlan) + << " up"; + return swss::exec(cmd.str(), res); } static int cmdCreateVxlanIf(const swss::VxlanMgr::VxlanInfo & info, std::string & res) { // ip link add {{VXLAN_IF}} type bridge - const std::string cmd = std::string("") - + IP_CMD " link add " - + info.m_vxlanIf - + " type bridge"; - return EXECUTE(cmd, res); + ostringstream cmd; + cmd << IP_CMD " link add " + << shellquote(info.m_vxlanIf) + << " type bridge"; + return swss::exec(cmd.str(), res); } static int cmdAddVxlanIntoVxlanIf(const swss::VxlanMgr::VxlanInfo & info, std::string & res) { // brctl addif {{VXLAN_IF}} {{VXLAN}} - const std::string cmd = std::string("") - + BRCTL_CMD " addif " - + info.m_vxlanIf - + " " - + info.m_vxlan; - return EXECUTE(cmd, res); + ostringstream cmd; + cmd << BRCTL_CMD " addif " + << shellquote(info.m_vxlanIf) + << " " + << shellquote(info.m_vxlan); + return swss::exec(cmd.str(), res); } static int cmdAttachVxlanIfToVnet(const swss::VxlanMgr::VxlanInfo & info, std::string & res) { // ip link set dev {{VXLAN_IF}} master {{VNET}} - const std::string cmd = std::string("") - + IP_CMD " link set dev " - + info.m_vxlanIf - + " master " - + info.m_vnet; - return EXECUTE(cmd, res); + ostringstream cmd; + cmd << IP_CMD " link set dev " + << shellquote(info.m_vxlanIf) + << " master " + << shellquote(info.m_vnet); + return swss::exec(cmd.str(), res); } static int cmdUpVxlanIf(const swss::VxlanMgr::VxlanInfo & info, std::string & res) { // ip link set dev {{VXLAN_IF}} up - const std::string cmd = std::string("") - + IP_CMD " link set dev " - + info.m_vxlanIf - + " up"; - return EXECUTE(cmd, res); + ostringstream cmd; + cmd << IP_CMD " link set dev " + << shellquote(info.m_vxlanIf) + << " up"; + return swss::exec(cmd.str(), res); } static int cmdDeleteVxlan(const swss::VxlanMgr::VxlanInfo & info, std::string & res) { // ip link del dev {{VXLAN}} - const std::string cmd = std::string("") - + IP_CMD " link del dev " - + info.m_vxlan; - return EXECUTE(cmd, res); + ostringstream cmd; + cmd << IP_CMD " link del dev " + << shellquote(info.m_vxlan); + return swss::exec(cmd.str(), res); } static int cmdDeleteVxlanFromVxlanIf(const swss::VxlanMgr::VxlanInfo & info, std::string & res) { // brctl delif {{VXLAN_IF}} {{VXLAN}} - const std::string cmd = std::string("") - + BRCTL_CMD " delif " - + info.m_vxlanIf - + " " - + info.m_vxlan; - return EXECUTE(cmd, res); + ostringstream cmd; + cmd << BRCTL_CMD " delif " + << shellquote(info.m_vxlanIf) + << " " + << shellquote(info.m_vxlan); + return swss::exec(cmd.str(), res); } static int cmdDeleteVxlanIf(const swss::VxlanMgr::VxlanInfo & info, std::string & res) { // ip link del {{VXLAN_IF}} - const std::string cmd = std::string("") - + IP_CMD " link del " - + info.m_vxlanIf; - return EXECUTE(cmd, res); + ostringstream cmd; + cmd << IP_CMD " link del " + << shellquote(info.m_vxlanIf); + return swss::exec(cmd.str(), res); } static int cmdDetachVxlanIfFromVnet(const swss::VxlanMgr::VxlanInfo & info, std::string & res) { // ip link set dev {{VXLAN_IF}} nomaster - const std::string cmd = std::string("") - + IP_CMD " link set dev " - + info.m_vxlanIf - + " nomaster"; - return EXECUTE(cmd, res); + ostringstream cmd; + cmd << IP_CMD " link set dev " + << shellquote(info.m_vxlanIf) + << " nomaster"; + return swss::exec(cmd.str(), res); } // Vxlanmgr @@ -540,7 +542,7 @@ void VxlanMgr::clearAllVxlanDevices() { std::string stdout; const std::string cmd = std::string("") + IP_CMD + " link"; - int ret = EXECUTE(cmd, stdout); + int ret = swss::exec(cmd, stdout); if (ret != 0) { SWSS_LOG_ERROR("Cannot get devices by command : %s", cmd.c_str()); @@ -570,4 +572,3 @@ void VxlanMgr::clearAllVxlanDevices() } } } - diff --git a/configure.ac b/configure.ac index 3d22f0d02b..44071996d5 100644 --- a/configure.ac +++ b/configure.ac @@ -34,7 +34,7 @@ AC_ARG_ENABLE(debug, esac],[debug=false]) AM_CONDITIONAL(DEBUG, test x$debug = xtrue) -CFLAGS_COMMON="-std=c++11 -Wall -fPIC -Wno-write-strings -I/usr/include/libnl3 -I/usr/include/swss" +CFLAGS_COMMON="-std=c++14 -Wall -fPIC -Wno-write-strings -I/usr/include/libnl3 -I/usr/include/swss" AM_CONDITIONAL(sonic_asic_platform_barefoot, test x$CONFIGURED_PLATFORM = xbarefoot) AM_COND_IF([sonic_asic_platform_barefoot], diff --git a/portsyncd/linksync.cpp b/portsyncd/linksync.cpp index 20d41b7b8d..bf39f8f001 100644 --- a/portsyncd/linksync.cpp +++ b/portsyncd/linksync.cpp @@ -18,6 +18,8 @@ #include #include +#include +#include using namespace std; using namespace swss; @@ -58,11 +60,12 @@ LinkSync::LinkSync(DBConnector *appl_db, DBConnector *state_db) : * This piece of information is used by SNMP. */ if (!key.compare(0, MGMT_PREFIX.length(), MGMT_PREFIX)) { - string cmd, res; - cmd = "cat /sys/class/net/" + key + "/operstate"; + ostringstream cmd; + string res; + cmd << "cat /sys/class/net/" << shellquote(key) << "/operstate"; try { - EXEC_WITH_ERROR_THROW(cmd, res); + EXEC_WITH_ERROR_THROW(cmd.str(), res); } catch (...) { @@ -132,13 +135,14 @@ LinkSync::LinkSync(DBConnector *appl_db, DBConnector *state_db) : m_ifindexOldNameMap[idx_p->if_index] = key; - string cmd, res; + ostringstream cmd; + string res; /* Bring down the existing kernel interfaces */ SWSS_LOG_INFO("Bring down old interface %s(%d)", key.c_str(), idx_p->if_index); - cmd = "ip link set " + key + " down"; + cmd << "ip link set " << quoted(key) << " down"; try { - swss::exec(cmd, res); + swss::exec(cmd.str(), res); } catch (...) { diff --git a/tests/Makefile.am b/tests/Makefile.am index 8818cbf57f..b6bff493a6 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -13,7 +13,8 @@ endif CFLAGS_GTEST = LDADD_GTEST = -L/usr/src/gtest -tests_SOURCES = swssnet_ut.cpp request_parser_ut.cpp ../orchagent/request_parser.cpp +tests_SOURCES = swssnet_ut.cpp request_parser_ut.cpp ../orchagent/request_parser.cpp \ + quoted_ut.cpp tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) tests_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) -I../orchagent diff --git a/tests/quoted_ut.cpp b/tests/quoted_ut.cpp new file mode 100644 index 0000000000..cd8fef409f --- /dev/null +++ b/tests/quoted_ut.cpp @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include "swssnet.h" +#include "cfgmgr/shellcmd.h" + +using namespace std; +using namespace swss; + +#define DOT1Q_BRIDGE_NAME "Bridge" +#define DEFAULT_VLAN_ID "1" + +TEST(quoted, copy1_v6) +{ + ostringstream cmd; + string key = "Ethernet0"; + cmd << "ip link set " << shellquote(key) << " down"; + EXPECT_EQ(cmd.str(), "ip link set \"Ethernet0\" down"); + + ostringstream cmd2; + key = "; rm -rf /; echo '\"'"; + cmd2 << "ip link set " << shellquote(key) << " down"; + EXPECT_EQ(cmd2.str(), "ip link set \"; rm -rf /; echo '\\\"'\" down"); + + ostringstream cmds, inner; + string port_alias = "etp1"; + string tagging_cmd = "pvid untagged"; + int vlan_id = 111; + inner << IP_CMD " link set " << shellquote(port_alias) << " master " DOT1Q_BRIDGE_NAME " && " + BRIDGE_CMD " vlan del vid " DEFAULT_VLAN_ID " dev " << shellquote(port_alias) << " && " + BRIDGE_CMD " vlan add vid " + std::to_string(vlan_id) + " dev " << shellquote(port_alias) << " " + tagging_cmd; + cmds << BASH_CMD " -c " << shellquote(inner.str()); + EXPECT_EQ(cmds.str(), "/bin/bash -c \"/sbin/ip link set \\\"etp1\\\" master Bridge && /sbin/bridge vlan del vid 1 dev \\\"etp1\\\" && /sbin/bridge vlan add vid 111 dev \\\"etp1\\\" pvid untagged\""); + + ostringstream cmd4; + key = "$(echo hi)"; + cmd4 << "cat /sys/class/net/" << shellquote(key) << "/operstate"; + EXPECT_EQ(cmd4.str(), "cat /sys/class/net/\"\\$(echo hi)\"/operstate"); +} From 5ab3f6b6dd4ccb572b9e9d16a2418ae2ed12937c Mon Sep 17 00:00:00 2001 From: "Sudharsan D.G" Date: Thu, 31 Oct 2019 11:33:07 -0700 Subject: [PATCH 09/63] Updating pytest for sflow (#1095) --- tests/test_sflow.py | 225 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 199 insertions(+), 26 deletions(-) diff --git a/tests/test_sflow.py b/tests/test_sflow.py index 1e064d699e..7ab49a3332 100644 --- a/tests/test_sflow.py +++ b/tests/test_sflow.py @@ -3,29 +3,88 @@ import time import os + class TestSflow(object): + speed_rate_table = { + "400000":"40000", + "100000":"10000", + "50000":"5000", + "40000":"4000", + "25000":"2500", + "10000":"1000", + "1000":"100" + } def setup_sflow(self, dvs): - self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) self.adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) - ptbl = swsscommon.ProducerStateTable(self.pdb, "SFLOW_TABLE") + self.cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) + ctbl = swsscommon.Table(self.cdb, "SFLOW") + fvs = swsscommon.FieldValuePairs([("admin_state", "up")]) - ptbl.set("global", fvs) + ctbl.set("global", fvs) time.sleep(1) - def test_SflowDisble(self, dvs, testlog): + def test_defaultGlobal(self, dvs, testlog): self.setup_sflow(dvs) - ptbl = swsscommon.ProducerStateTable(self.pdb, "SFLOW_SESSION_TABLE") - gtbl = swsscommon.ProducerStateTable(self.pdb, "SFLOW_TABLE") + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") + (status, fvs) = atbl.get(dvs.asicdb.portnamemap["Ethernet0"]) + + assert status == True + + sample_session = "" + speed = "" + for fv in fvs: + if fv[0] == "SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE": + sample_session = fv[1] + elif fv[0] == "SAI_PORT_ATTR_SPEED": + speed = fv[1] + + assert sample_session != "" + assert speed != "" + + rate = "" + + if speed in self.speed_rate_table: + rate = self.speed_rate_table[speed] + + assert rate != "" + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_SAMPLEPACKET") + (status, fvs) = atbl.get(sample_session) + + assert status == True + + for fv in fvs: + if fv[0] == "SAI_SAMPLEPACKET_ATTR_SAMPLE_RATE": + assert fv[1] == rate + + ctbl = swsscommon.Table(self.cdb, "SFLOW") fvs = swsscommon.FieldValuePairs([("admin_state", "down")]) - gtbl.set("global", fvs) + ctbl.set("global", fvs) time.sleep(1) - fvs = swsscommon.FieldValuePairs([("admin_state", "up"),("sample_rate","1000")]) - ptbl.set("Ethernet0", fvs) - time.sleep(1) + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") + (status, fvs) = atbl.get(dvs.asicdb.portnamemap["Ethernet0"]) + assert status == True + + sample_session = "" + speed = "" + for fv in fvs: + if fv[0] == "SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE": + sample_session = fv[1] + + assert sample_session == "oid:0x0" + + def test_globalAll(self, dvs, testlog): + self.setup_sflow(dvs) + + ctbl = swsscommon.Table(self.cdb, "SFLOW_SESSION") + fvs = swsscommon.FieldValuePairs([("admin_state", "down")]) + ctbl.set("all", fvs) + + time.sleep(1) atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") (status, fvs) = atbl.get(dvs.asicdb.portnamemap["Ethernet0"]) @@ -33,18 +92,18 @@ def test_SflowDisble(self, dvs, testlog): assert status == True sample_session = "" + speed = "" for fv in fvs: if fv[0] == "SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE": sample_session = fv[1] - assert sample_session == "" + assert sample_session == "oid:0x0" fvs = swsscommon.FieldValuePairs([("admin_state", "up")]) - gtbl.set("global", fvs) + ctbl.set("all", fvs) time.sleep(1) - atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") (status, fvs) = atbl.get(dvs.asicdb.portnamemap["Ethernet0"]) assert status == True @@ -54,7 +113,45 @@ def test_SflowDisble(self, dvs, testlog): if fv[0] == "SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE": sample_session = fv[1] + assert sample_session != "" assert sample_session != "oid:0x0" + + ctbl._del("all") + + time.sleep(1) + + (status, fvs) = atbl.get(dvs.asicdb.portnamemap["Ethernet0"]) + + assert status == True + + sample_session = "" + for fv in fvs: + if fv[0] == "SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE": + sample_session = fv[1] + + assert sample_session != "" + assert sample_session != "oid:0x0" + + + def test_InterfaceSet(self, dvs, testlog): + self.setup_sflow(dvs) + ctbl = swsscommon.Table(self.cdb, "SFLOW_SESSION") + gtbl = swsscommon.Table(self.cdb, "SFLOW") + fvs = swsscommon.FieldValuePairs([("admin_state", "up"),("sample_rate","1000")]) + ctbl.set("Ethernet0", fvs) + + time.sleep(1) + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") + (status, fvs) = atbl.get(dvs.asicdb.portnamemap["Ethernet0"]) + + assert status == True + + sample_session = "" + for fv in fvs: + if fv[0] == "SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE": + sample_session = fv[1] + assert sample_session != "" atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_SAMPLEPACKET") @@ -66,14 +163,25 @@ def test_SflowDisble(self, dvs, testlog): if fv[0] == "SAI_SAMPLEPACKET_ATTR_SAMPLE_RATE": assert fv[1] == "1000" - ptbl._del("Ethernet0") + fvs = swsscommon.FieldValuePairs([("admin_state", "down")]) + ctbl.set("all", fvs) - def test_InterfaceSet(self, dvs, testlog): - self.setup_sflow(dvs) - ptbl = swsscommon.ProducerStateTable(self.pdb, "SFLOW_SESSION_TABLE") - gtbl = swsscommon.ProducerStateTable(self.pdb, "SFLOW_TABLE") - fvs = swsscommon.FieldValuePairs([("admin_state", "up"),("sample_rate","1000")]) - ptbl.set("Ethernet0", fvs) + time.sleep(1) + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") + (status, fvs) = atbl.get(dvs.asicdb.portnamemap["Ethernet0"]) + + assert status == True + + sample_session = "" + for fv in fvs: + if fv[0] == "SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE": + sample_session = fv[1] + assert sample_session != "" + assert sample_session != "oid:0x0" + + fvs = swsscommon.FieldValuePairs([("admin_state", "down")]) + gtbl.set("global", fvs) time.sleep(1) @@ -87,7 +195,40 @@ def test_InterfaceSet(self, dvs, testlog): if fv[0] == "SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE": sample_session = fv[1] + assert sample_session == "oid:0x0" + ctbl._del("all") + ctbl._del("Ethernet0") + + def test_defaultRate(self, dvs, testlog): + self.setup_sflow(dvs) + ctbl = swsscommon.Table(self.cdb, "SFLOW_SESSION") + fvs = swsscommon.FieldValuePairs([("admin_state", "up")]) + ctbl.set("Ethernet4", fvs) + + time.sleep(1) + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") + (status, fvs) = atbl.get(dvs.asicdb.portnamemap["Ethernet4"]) + + assert status == True + + sample_session = "" + speed = "" + for fv in fvs: + if fv[0] == "SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE": + sample_session = fv[1] + elif fv[0] == "SAI_PORT_ATTR_SPEED": + speed = fv[1] + assert sample_session != "" + assert speed != "" + + rate = "" + + if speed in self.speed_rate_table: + rate = self.speed_rate_table[speed] + + assert rate != "" atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_SAMPLEPACKET") (status, fvs) = atbl.get(sample_session) @@ -96,19 +237,19 @@ def test_InterfaceSet(self, dvs, testlog): for fv in fvs: if fv[0] == "SAI_SAMPLEPACKET_ATTR_SAMPLE_RATE": - assert fv[1] == "1000" + assert fv[1] == rate - ptbl._del("Ethernet0") + ctbl._del("Ethernet4") def test_ConfigDel(self, dvs, testlog): self.setup_sflow(dvs) - ptbl = swsscommon.ProducerStateTable(self.pdb, "SFLOW_SESSION_TABLE") + ctbl = swsscommon.Table(self.cdb, "SFLOW_SESSION_TABLE") fvs = swsscommon.FieldValuePairs([("admin_state", "up"),("sample_rate","1000")]) - ptbl.set("Ethernet0", fvs) + ctbl.set("Ethernet0", fvs) time.sleep(1) - ptbl._del("Ethernet0") + ctbl._del("Ethernet0") time.sleep(1) @@ -127,4 +268,36 @@ def test_ConfigDel(self, dvs, testlog): speed = fv[1] assert speed != "" - assert sample_session == "oid:0x0" + assert sample_session != "" + assert sample_session != "oid:0x0" + + rate = "" + + if speed in self.speed_rate_table: + rate = self.speed_rate_table[speed] + + assert rate != "" + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_SAMPLEPACKET") + (status, fvs) = atbl.get(sample_session) + + assert status == True + + rf = False + for fv in fvs: + if fv[0] == "SAI_SAMPLEPACKET_ATTR_SAMPLE_RATE": + assert fv[1] == rate + rf = True + + assert rf == True + + def test_Teardown(self, dvs, testlog): + self.setup_sflow(dvs) + ctbl = swsscommon.Table(self.cdb, "SFLOW") + ctbl._del("global") + + time.sleep(1) + + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_SAMPLEPACKET") + assert len(atbl.getKeys()) == 0 From bab7b9331a7a942042d8afa65b400c190c5aa003 Mon Sep 17 00:00:00 2001 From: Stepan Blyshchak <38952541+stepanblyschak@users.noreply.github.com> Date: Sat, 2 Nov 2019 00:44:02 +0200 Subject: [PATCH 10/63] [portsorch] fix PortsOrch::allPortsReady() returns true when it should not (#1103) * [portsorch] fix PortsOrch::allPortsReady() returns true when it should not Warm start flow before the change: 1st iteration: - BufferOrch::doTask(): returns since PortInitDone hasn't arived yet - PortsOrch::doTask(): processes all PORT_TABLE untill PortInitDone flag m_pendingPortSet is empty yet and m_portInitDone is true so allPortsReady() will return true - AnyOrch::doTask(): check g_portsOrch->allPortsRead() 2nd iteration: - BufferOrch::doTask(): now buffers are applied This causes BufferOrch override PfcWdOrch's zero-buffer profile. The change swaps BufferOrch and PortsOrch in m_orchList, because 1st BufferOrch iteration will always skip processing and eliminates possibility of having m_pendingPortSet not filled with ports after m_initDone is set to true. * remove extra newline * [pfcwdorch] fix PfcWdSwOrch::doTask() starts WD action when WD wasn't started in warm boot It appeared that pfc watchdog relied on a buggy behaviour of PortsOrch::allPortsReady(). In fixed PortsOrch::allPortsReady() you'll see that watchdog action is trying to start before watchdog was started, because allPortsReady() in PfcWdOrch::doTask() returned false. Before the fix watchdog was started before, because allPortsReady() lied that ports are ready when they were not. * [portsorch] populate m_pendingPortSet in PortsOrch::bake() * [portsorch] optimize to 3 iterations instead of 4 * Revert "[portsorch] optimize to 3 iterations instead of 4" * revert change of order in m_orchList * [mock_tests] fix tests build * [mock_test] create unittest for PortsOrch::allPortsReady cold/warm flows * [orchdaemon] fix removed sfloworch * [mock_tests] make mock_tests run on "make check" --- orchagent/pfcwdorch.cpp | 5 + orchagent/portsorch.cpp | 10 + tests/Makefile.am | 2 + tests/mock_tests/.gitignore | 1 + tests/mock_tests/Makefile.am | 70 +++-- tests/mock_tests/aclorch_ut.cpp | 18 +- tests/mock_tests/mock_consumerstatetable.cpp | 2 +- tests/mock_tests/mock_dbconnector.cpp | 4 +- tests/mock_tests/mock_orchagent_main.h | 56 ++++ tests/mock_tests/mock_table.cpp | 74 +++++ tests/mock_tests/mock_table.h | 6 + tests/mock_tests/portsorch_ut.cpp | 313 +++++++++++++++++++ tests/mock_tests/ut_helper.h | 8 + tests/mock_tests/ut_saihelper.cpp | 163 ++++++++++ 14 files changed, 689 insertions(+), 43 deletions(-) create mode 100644 tests/mock_tests/.gitignore create mode 100644 tests/mock_tests/mock_orchagent_main.h create mode 100644 tests/mock_tests/mock_table.cpp create mode 100644 tests/mock_tests/mock_table.h create mode 100644 tests/mock_tests/portsorch_ut.cpp create mode 100644 tests/mock_tests/ut_saihelper.cpp diff --git a/orchagent/pfcwdorch.cpp b/orchagent/pfcwdorch.cpp index c4002a1c3d..d6635b49b3 100644 --- a/orchagent/pfcwdorch.cpp +++ b/orchagent/pfcwdorch.cpp @@ -759,6 +759,11 @@ void PfcWdSwOrch::doTask(Consumer& consumer) { PfcWdOrch::doTask(consumer); + if (!gPortsOrch->allPortsReady()) + { + return; + } + if ((consumer.getDbId() == APPL_DB) && (consumer.getTableName() == APP_PFC_WD_TABLE_NAME)) { auto it = consumer.m_toSync.begin(); diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index ae876467fb..8958ea85dc 100644 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -1471,6 +1471,16 @@ bool PortsOrch::bake() return false; } + for (const auto& alias: keys) + { + if (alias == "PortConfigDone" || alias == "PortInitDone") + { + continue; + } + + m_pendingPortSet.emplace(alias); + } + addExistingData(m_portTable.get()); addExistingData(APP_LAG_TABLE_NAME); addExistingData(APP_LAG_MEMBER_TABLE_NAME); diff --git a/tests/Makefile.am b/tests/Makefile.am index b6bff493a6..0196e0a1a2 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -2,6 +2,8 @@ CFLAGS_SAI = -I /usr/include/sai TESTS = tests +SUBDIRS = mock_tests + noinst_PROGRAMS = tests if DEBUG diff --git a/tests/mock_tests/.gitignore b/tests/mock_tests/.gitignore new file mode 100644 index 0000000000..2b29f27645 --- /dev/null +++ b/tests/mock_tests/.gitignore @@ -0,0 +1 @@ +tests diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 8ff7d81597..247a33bf1a 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -1,6 +1,8 @@ CFLAGS_SAI = -I /usr/include/sai -bin_PROGRAMS = tests +TESTS = tests + +noinst_PROGRAMS = tests LDADD_SAI = -lsaimeta -lsaimetadata -lsaivs -lsairedis @@ -13,42 +15,48 @@ endif CFLAGS_GTEST = LDADD_GTEST = -L/usr/src/gtest -tests_SOURCES = swssnet_ut.cpp request_parser_ut.cpp aclorch_ut.cpp saispy_ut.cpp \ +tests_SOURCES = aclorch_ut.cpp \ + portsorch_ut.cpp \ + saispy_ut.cpp \ + ut_saihelper.cpp \ mock_orchagent_main.cpp \ mock_dbconnector.cpp \ mock_consumerstatetable.cpp \ + mock_table.cpp \ mock_hiredis.cpp \ mock_redisreply.cpp \ - ../orchagent/orchdaemon.cpp \ - ../orchagent/orch.cpp \ - ../orchagent/notifications.cpp \ - ../orchagent/routeorch.cpp \ - ../orchagent/neighorch.cpp \ - ../orchagent/intfsorch.cpp \ - ../orchagent/portsorch.cpp \ - ../orchagent/copporch.cpp \ - ../orchagent/tunneldecaporch.cpp \ - ../orchagent/qosorch.cpp \ - ../orchagent/bufferorch.cpp \ - ../orchagent/mirrororch.cpp \ - ../orchagent/fdborch.cpp \ - ../orchagent/aclorch.cpp \ - ../orchagent/saihelper.cpp \ - ../orchagent/switchorch.cpp \ - ../orchagent/pfcwdorch.cpp \ - ../orchagent/pfcactionhandler.cpp \ - ../orchagent/policerorch.cpp \ - ../orchagent/crmorch.cpp \ - ../orchagent/request_parser.cpp \ - ../orchagent/vrforch.cpp \ - ../orchagent/countercheckorch.cpp \ - ../orchagent/vxlanorch.cpp \ - ../orchagent/vnetorch.cpp \ - ../orchagent/dtelorch.cpp \ - ../orchagent/flexcounterorch.cpp \ - ../orchagent/watermarkorch.cpp + $(top_srcdir)/orchagent/orchdaemon.cpp \ + $(top_srcdir)/orchagent/orch.cpp \ + $(top_srcdir)/orchagent/notifications.cpp \ + $(top_srcdir)/orchagent/routeorch.cpp \ + $(top_srcdir)/orchagent/neighorch.cpp \ + $(top_srcdir)/orchagent/intfsorch.cpp \ + $(top_srcdir)/orchagent/portsorch.cpp \ + $(top_srcdir)/orchagent/copporch.cpp \ + $(top_srcdir)/orchagent/tunneldecaporch.cpp \ + $(top_srcdir)/orchagent/qosorch.cpp \ + $(top_srcdir)/orchagent/bufferorch.cpp \ + $(top_srcdir)/orchagent/mirrororch.cpp \ + $(top_srcdir)/orchagent/fdborch.cpp \ + $(top_srcdir)/orchagent/aclorch.cpp \ + $(top_srcdir)/orchagent/saihelper.cpp \ + $(top_srcdir)/orchagent/switchorch.cpp \ + $(top_srcdir)/orchagent/pfcwdorch.cpp \ + $(top_srcdir)/orchagent/pfcactionhandler.cpp \ + $(top_srcdir)/orchagent/policerorch.cpp \ + $(top_srcdir)/orchagent/crmorch.cpp \ + $(top_srcdir)/orchagent/request_parser.cpp \ + $(top_srcdir)/orchagent/vrforch.cpp \ + $(top_srcdir)/orchagent/countercheckorch.cpp \ + $(top_srcdir)/orchagent/vxlanorch.cpp \ + $(top_srcdir)/orchagent/vnetorch.cpp \ + $(top_srcdir)/orchagent/dtelorch.cpp \ + $(top_srcdir)/orchagent/flexcounterorch.cpp \ + $(top_srcdir)/orchagent/watermarkorch.cpp \ + $(top_srcdir)/orchagent/chassisorch.cpp \ + $(top_srcdir)/orchagent/sfloworch.cpp tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) -tests_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) -I../orchagent +tests_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) -I$(top_srcdir)/orchagent tests_LDADD = $(LDADD_GTEST) $(LDADD_SAI) -lnl-genl-3 -lhiredis -lhiredis -lpthread \ -lswsscommon -lswsscommon -lgtest -lgtest_main diff --git a/tests/mock_tests/aclorch_ut.cpp b/tests/mock_tests/aclorch_ut.cpp index ec1e2d4316..ae4267ddb5 100644 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -643,16 +643,16 @@ namespace aclorch_test // leakage check bool validateResourceCountWithLowerLayerDb(const AclOrch *aclOrch) { - // TODO: Using the need to include "sai_vs_state.h". That will need the include path from `configure` - // Do this later ... + // TODO: Using the need to include "sai_vs_state.h". That will need the include path from `configure` + // Do this later ... #if WITH_SAI == LIBVS - // { - // auto& aclTableHash = g_switch_state_map.at(gSwitchId)->objectHash.at(SAI_OBJECT_TYPE_ACL_TABLE); - // - // return aclTableHash.size() == Portal::AclOrchInternal::getAclTables(aclOrch).size(); - // } - // - // TODO: add rule check + // { + // auto& aclTableHash = g_switch_state_map.at(gSwitchId)->objectHash.at(SAI_OBJECT_TYPE_ACL_TABLE); + // + // return aclTableHash.size() == Portal::AclOrchInternal::getAclTables(aclOrch).size(); + // } + // + // TODO: add rule check #endif return true; diff --git a/tests/mock_tests/mock_consumerstatetable.cpp b/tests/mock_tests/mock_consumerstatetable.cpp index 0af574a5f5..822727929a 100644 --- a/tests/mock_tests/mock_consumerstatetable.cpp +++ b/tests/mock_tests/mock_consumerstatetable.cpp @@ -7,4 +7,4 @@ namespace swss TableName_KeySet(tableName) { } -} \ No newline at end of file +} diff --git a/tests/mock_tests/mock_dbconnector.cpp b/tests/mock_tests/mock_dbconnector.cpp index 14f955eb3e..6a021e036a 100644 --- a/tests/mock_tests/mock_dbconnector.cpp +++ b/tests/mock_tests/mock_dbconnector.cpp @@ -40,6 +40,6 @@ namespace swss int DBConnector::getDbId() const { - return 12345; + return m_dbId; } -} \ No newline at end of file +} diff --git a/tests/mock_tests/mock_orchagent_main.h b/tests/mock_tests/mock_orchagent_main.h new file mode 100644 index 0000000000..d2d313279d --- /dev/null +++ b/tests/mock_tests/mock_orchagent_main.h @@ -0,0 +1,56 @@ +#pragma once + +#include "orch.h" +#include "switchorch.h" +#include "crmorch.h" +#include "portsorch.h" +#include "routeorch.h" +#include "intfsorch.h" +#include "neighorch.h" +#include "fdborch.h" +#include "mirrororch.h" +#include "bufferorch.h" +#include "vrforch.h" +#include "vnetorch.h" +#include "vxlanorch.h" +#include "policerorch.h" + +extern int gBatchSize; +extern bool gSwssRecord; +extern bool gSairedisRecord; +extern bool gLogRotate; +extern ofstream gRecordOfs; +extern string gRecordFile; + +extern MacAddress gMacAddress; +extern MacAddress gVxlanMacAddress; + +extern sai_object_id_t gSwitchId; +extern sai_object_id_t gVirtualRouterId; +extern sai_object_id_t gUnderlayIfId; + +extern SwitchOrch *gSwitchOrch; +extern CrmOrch *gCrmOrch; +extern PortsOrch *gPortsOrch; +extern RouteOrch *gRouteOrch; +extern IntfsOrch *gIntfsOrch; +extern NeighOrch *gNeighOrch; +extern FdbOrch *gFdbOrch; +extern MirrorOrch *gMirrorOrch; +extern BufferOrch *gBufferOrch; +extern VRFOrch *gVrfOrch; + +extern sai_acl_api_t *sai_acl_api; +extern sai_switch_api_t *sai_switch_api; +extern sai_virtual_router_api_t *sai_virtual_router_api; +extern sai_port_api_t *sai_port_api; +extern sai_lag_api_t *sai_lag_api; +extern sai_vlan_api_t *sai_vlan_api; +extern sai_bridge_api_t *sai_bridge_api; +extern sai_router_interface_api_t *sai_router_intfs_api; +extern sai_route_api_t *sai_route_api; +extern sai_neighbor_api_t *sai_neighbor_api; +extern sai_tunnel_api_t *sai_tunnel_api; +extern sai_next_hop_api_t *sai_next_hop_api; +extern sai_hostif_api_t *sai_hostif_api; +extern sai_buffer_api_t *sai_buffer_api; diff --git a/tests/mock_tests/mock_table.cpp b/tests/mock_tests/mock_table.cpp new file mode 100644 index 0000000000..7cd532c208 --- /dev/null +++ b/tests/mock_tests/mock_table.cpp @@ -0,0 +1,74 @@ +#include "table.h" + +using TableDataT = std::map>; +using TablesT = std::map; + +namespace testing_db +{ + + TableDataT gTableData; + TablesT gTables; + std::map gDB; + + void reset() + { + gDB.clear(); + } +} + +namespace swss +{ + + using namespace testing_db; + + bool Table::get(const std::string &key, std::vector &ovalues) + { + auto table = gDB[getDbId()][getTableName()]; + if (table.find(key) == table.end()) + { + return false; + } + + ovalues = table[key]; + return true; + } + + bool Table::hget(const std::string &key, const std::string &field, std::string &value) + { + auto table = gDB[getDbId()][getTableName()]; + if (table.find(key) == table.end()) + { + return false; + } + + for (const auto &it : table[key]) + { + if (it.first == field) + { + value = it.second; + return true; + } + } + + return false; + } + + void Table::set(const std::string &key, + const std::vector &values, + const std::string &op, + const std::string &prefix) + { + auto &table = gDB[getDbId()][getTableName()]; + table[key] = values; + } + + void Table::getKeys(std::vector &keys) + { + keys.clear(); + auto table = gDB[getDbId()][getTableName()]; + for (const auto &it : table) + { + keys.push_back(it.first); + } + } +} diff --git a/tests/mock_tests/mock_table.h b/tests/mock_tests/mock_table.h new file mode 100644 index 0000000000..bf8a4bdef6 --- /dev/null +++ b/tests/mock_tests/mock_table.h @@ -0,0 +1,6 @@ +#include "table.h" + +namespace testing_db +{ + void reset(); +} diff --git a/tests/mock_tests/portsorch_ut.cpp b/tests/mock_tests/portsorch_ut.cpp new file mode 100644 index 0000000000..40cfbd3ecc --- /dev/null +++ b/tests/mock_tests/portsorch_ut.cpp @@ -0,0 +1,313 @@ +#include "ut_helper.h" +#include "mock_orchagent_main.h" +#include "mock_table.h" + +#include + +namespace portsorch_test +{ + + using namespace std; + + struct PortsOrchTest : public ::testing::Test + { + shared_ptr m_app_db; + shared_ptr m_config_db; + shared_ptr m_state_db; + + PortsOrchTest() + { + // FIXME: move out from constructor + m_app_db = make_shared( + APPL_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + m_config_db = make_shared( + CONFIG_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + m_state_db = make_shared( + STATE_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + } + + virtual void SetUp() override + { + ::testing_db::reset(); + } + + virtual void TearDown() override + { + ::testing_db::reset(); + delete gPortsOrch; + gPortsOrch = nullptr; + delete gBufferOrch; + gBufferOrch = nullptr; + } + + static void SetUpTestCase() + { + // Init switch and create dependencies + + map profile = { + { "SAI_VS_SWITCH_TYPE", "SAI_VS_SWITCH_TYPE_BCM56850" }, + { "KV_DEVICE_MAC_ADDRESS", "20:03:04:05:06:00" } + }; + + auto status = ut_helper::initSaiApi(profile); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + sai_attribute_t attr; + + attr.id = SAI_SWITCH_ATTR_INIT_SWITCH; + attr.value.booldata = true; + + status = sai_switch_api->create_switch(&gSwitchId, 1, &attr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + // Get switch source MAC address + attr.id = SAI_SWITCH_ATTR_SRC_MAC_ADDRESS; + status = sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr); + + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + gMacAddress = attr.value.mac; + + // Get the default virtual router ID + attr.id = SAI_SWITCH_ATTR_DEFAULT_VIRTUAL_ROUTER_ID; + status = sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr); + + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + gVirtualRouterId = attr.value.oid; + } + + static void TearDownTestCase() + { + auto status = sai_switch_api->remove_switch(gSwitchId); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + gSwitchId = 0; + + ut_helper::uninitSaiApi(); + } + }; + + TEST_F(PortsOrchTest, PortReadinessColdBoot) + { + Table portTable = Table(m_app_db.get(), APP_PORT_TABLE_NAME); + Table pgTable = Table(m_config_db.get(), CFG_BUFFER_PG_TABLE_NAME); + Table profileTable = Table(m_config_db.get(), CFG_BUFFER_PROFILE_TABLE_NAME); + Table poolTable = Table(m_config_db.get(), CFG_BUFFER_POOL_TABLE_NAME); + + // Get SAI default ports to populate DB + + auto ports = ut_helper::getInitialSaiPorts(); + + // Create test buffer pool + poolTable.set( + "test_pool", + { + { "type", "ingress" }, + { "mode", "dynamic" }, + { "size", "4200000" }, + }); + + // Create test buffer profile + profileTable.set("test_profile", { { "pool", "[BUFFER_POOL|test_pool]" }, + { "xon", "14832" }, + { "xoff", "14832" }, + { "size", "35000" }, + { "dynamic_th", "0" } }); + + // Apply profile on PGs 3-4 all ports + for (const auto &it : ports) + { + std::ostringstream oss; + oss << it.first << "|3-4"; + pgTable.set(oss.str(), { { "profile", "[BUFFER_PROFILE|test_profile]" } }); + } + + // Create dependencies ... + + const int portsorch_base_pri = 40; + + vector ports_tables = { + { APP_PORT_TABLE_NAME, portsorch_base_pri + 5 }, + { APP_VLAN_TABLE_NAME, portsorch_base_pri + 2 }, + { APP_VLAN_MEMBER_TABLE_NAME, portsorch_base_pri }, + { APP_LAG_TABLE_NAME, portsorch_base_pri + 4 }, + { APP_LAG_MEMBER_TABLE_NAME, portsorch_base_pri } + }; + + ASSERT_EQ(gPortsOrch, nullptr); + gPortsOrch = new PortsOrch(m_app_db.get(), ports_tables); + vector buffer_tables = { CFG_BUFFER_POOL_TABLE_NAME, + CFG_BUFFER_PROFILE_TABLE_NAME, + CFG_BUFFER_QUEUE_TABLE_NAME, + CFG_BUFFER_PG_TABLE_NAME, + CFG_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME, + CFG_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME }; + + ASSERT_EQ(gBufferOrch, nullptr); + gBufferOrch = new BufferOrch(m_config_db.get(), buffer_tables); + + // Populate pot table with SAI ports + for (const auto &it : ports) + { + portTable.set(it.first, it.second); + } + + // Set PortConfigDone + portTable.set("PortConfigDone", { { "count", to_string(ports.size()) } }); + + // refill consumer + gPortsOrch->addExistingData(&portTable); + gBufferOrch->addExistingData(&pgTable); + gBufferOrch->addExistingData(&poolTable); + gBufferOrch->addExistingData(&profileTable); + + // Apply configuration : + // create ports + + static_cast(gBufferOrch)->doTask(); + + static_cast(gPortsOrch)->doTask(); + + // Ports are not ready yet + + ASSERT_FALSE(gPortsOrch->allPortsReady()); + + // Ports host interfaces are created + + portTable.set("PortInitDone", { { "lanes", "0" } }); + gPortsOrch->addExistingData(&portTable); + + // Apply configuration + // configure buffers + // ports + static_cast(gPortsOrch)->doTask(); + + // Since init done is set now, apply buffers + static_cast(gBufferOrch)->doTask(); + + // Ports are not ready yet, mtu, speed left + ASSERT_FALSE(gPortsOrch->allPortsReady()); + + static_cast(gPortsOrch)->doTask(); + ASSERT_TRUE(gPortsOrch->allPortsReady()); + + // No more tasks + + vector ts; + + gPortsOrch->dumpPendingTasks(ts); + ASSERT_TRUE(ts.empty()); + + ts.clear(); + + gBufferOrch->dumpPendingTasks(ts); + ASSERT_TRUE(ts.empty()); + } + + TEST_F(PortsOrchTest, PortReadinessWarmBoot) + { + + Table portTable = Table(m_app_db.get(), APP_PORT_TABLE_NAME); + Table pgTable = Table(m_config_db.get(), CFG_BUFFER_PG_TABLE_NAME); + Table profileTable = Table(m_config_db.get(), CFG_BUFFER_PROFILE_TABLE_NAME); + Table poolTable = Table(m_config_db.get(), CFG_BUFFER_POOL_TABLE_NAME); + + // Get SAI default ports to populate DB + + auto ports = ut_helper::getInitialSaiPorts(); + + // Create test buffer pool + poolTable.set( + "test_pool", + { + { "type", "ingress" }, + { "mode", "dynamic" }, + { "size", "4200000" }, + }); + + // Create test buffer profile + profileTable.set("test_profile", { { "pool", "[BUFFER_POOL|test_pool]" }, + { "xon", "14832" }, + { "xoff", "14832" }, + { "size", "35000" }, + { "dynamic_th", "0" } }); + + // Apply profile on PGs 3-4 all ports + for (const auto &it : ports) + { + std::ostringstream oss; + oss << it.first << "|3-4"; + pgTable.set(oss.str(), { { "profile", "[BUFFER_PROFILE|test_profile]" } }); + } + + // Populate pot table with SAI ports + for (const auto &it : ports) + { + portTable.set(it.first, it.second); + } + + // Set PortConfigDone, PortInitDone + + portTable.set("PortConfigDone", { { "count", to_string(ports.size()) } }); + portTable.set("PortInitDone", { { "lanes", "0" } }); + + // Create dependencies ... + + const int portsorch_base_pri = 40; + + vector ports_tables = { + { APP_PORT_TABLE_NAME, portsorch_base_pri + 5 }, + { APP_VLAN_TABLE_NAME, portsorch_base_pri + 2 }, + { APP_VLAN_MEMBER_TABLE_NAME, portsorch_base_pri }, + { APP_LAG_TABLE_NAME, portsorch_base_pri + 4 }, + { APP_LAG_MEMBER_TABLE_NAME, portsorch_base_pri } + }; + + ASSERT_EQ(gPortsOrch, nullptr); + gPortsOrch = new PortsOrch(m_app_db.get(), ports_tables); + vector buffer_tables = { CFG_BUFFER_POOL_TABLE_NAME, + CFG_BUFFER_PROFILE_TABLE_NAME, + CFG_BUFFER_QUEUE_TABLE_NAME, + CFG_BUFFER_PG_TABLE_NAME, + CFG_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME, + CFG_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME }; + + ASSERT_EQ(gBufferOrch, nullptr); + gBufferOrch = new BufferOrch(m_config_db.get(), buffer_tables); + + // warm start, bake fill refill consumer + + gBufferOrch->bake(); + gPortsOrch->bake(); + + // Create ports, BufferOrch skips processing + static_cast(gBufferOrch)->doTask(); + static_cast(gPortsOrch)->doTask(); + + // Ports should not be ready here, buffers not applied, + // BufferOrch depends on ports to be created + + ASSERT_FALSE(gPortsOrch->allPortsReady()); + + // Drain remaining + + static_cast(gBufferOrch)->doTask(); + static_cast(gPortsOrch)->doTask(); + + // Now ports should be ready + + ASSERT_TRUE(gPortsOrch->allPortsReady()); + + // No more tasks + + vector ts; + + gPortsOrch->dumpPendingTasks(ts); + ASSERT_TRUE(ts.empty()); + + ts.clear(); + + gBufferOrch->dumpPendingTasks(ts); + ASSERT_TRUE(ts.empty()); + } +} diff --git a/tests/mock_tests/ut_helper.h b/tests/mock_tests/ut_helper.h index ba1a649d8f..baaf8184d8 100644 --- a/tests/mock_tests/ut_helper.h +++ b/tests/mock_tests/ut_helper.h @@ -9,3 +9,11 @@ #include "saispy.h" #include "check.h" + +namespace ut_helper +{ + sai_status_t initSaiApi(const std::map &profile); + void uninitSaiApi(); + + map> getInitialSaiPorts(); +} diff --git a/tests/mock_tests/ut_saihelper.cpp b/tests/mock_tests/ut_saihelper.cpp new file mode 100644 index 0000000000..5d5d07c190 --- /dev/null +++ b/tests/mock_tests/ut_saihelper.cpp @@ -0,0 +1,163 @@ +#include "ut_helper.h" +#include "mock_orchagent_main.h" + +namespace ut_helper +{ + map gProfileMap; + map::iterator gProfileIter; + + const char *profile_get_value( + sai_switch_profile_id_t profile_id, + const char *variable) + { + map::const_iterator it = gProfileMap.find(variable); + if (it == gProfileMap.end()) + { + return NULL; + } + + return it->second.c_str(); + } + + int profile_get_next_value( + sai_switch_profile_id_t profile_id, + const char **variable, + const char **value) + { + if (value == NULL) + { + gProfileIter = gProfileMap.begin(); + return 0; + } + + if (variable == NULL) + { + return -1; + } + + if (gProfileIter == gProfileMap.end()) + { + return -1; + } + + *variable = gProfileIter->first.c_str(); + *value = gProfileIter->second.c_str(); + + gProfileIter++; + + return 0; + } + + sai_status_t initSaiApi(const std::map &profile) + { + sai_service_method_table_t services = { + profile_get_value, + profile_get_next_value + }; + + gProfileMap = profile; + + auto status = sai_api_initialize(0, (sai_service_method_table_t *)&services); + if (status != SAI_STATUS_SUCCESS) + { + return status; + } + + sai_api_query(SAI_API_SWITCH, (void **)&sai_switch_api); + sai_api_query(SAI_API_BRIDGE, (void **)&sai_bridge_api); + sai_api_query(SAI_API_VIRTUAL_ROUTER, (void **)&sai_virtual_router_api); + sai_api_query(SAI_API_PORT, (void **)&sai_port_api); + sai_api_query(SAI_API_LAG, (void **)&sai_lag_api); + sai_api_query(SAI_API_VLAN, (void **)&sai_vlan_api); + sai_api_query(SAI_API_ROUTER_INTERFACE, (void **)&sai_router_intfs_api); + sai_api_query(SAI_API_ROUTE, (void **)&sai_route_api); + sai_api_query(SAI_API_NEIGHBOR, (void **)&sai_neighbor_api); + sai_api_query(SAI_API_TUNNEL, (void **)&sai_tunnel_api); + sai_api_query(SAI_API_NEXT_HOP, (void **)&sai_next_hop_api); + sai_api_query(SAI_API_ACL, (void **)&sai_acl_api); + sai_api_query(SAI_API_HOSTIF, (void **)&sai_hostif_api); + sai_api_query(SAI_API_BUFFER, (void **)&sai_buffer_api); + + return SAI_STATUS_SUCCESS; + } + + void uninitSaiApi() + { + sai_api_uninitialize(); + + sai_switch_api = nullptr; + sai_bridge_api = nullptr; + sai_virtual_router_api = nullptr; + sai_port_api = nullptr; + sai_lag_api = nullptr; + sai_vlan_api = nullptr; + sai_router_intfs_api = nullptr; + sai_route_api = nullptr; + sai_neighbor_api = nullptr; + sai_tunnel_api = nullptr; + sai_next_hop_api = nullptr; + sai_acl_api = nullptr; + sai_hostif_api = nullptr; + sai_buffer_api = nullptr; + } + + map> getInitialSaiPorts() + { + vector port_list; + sai_attribute_t attr; + sai_status_t status; + uint32_t port_count; + + attr.id = SAI_SWITCH_ATTR_PORT_NUMBER; + status = sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr); + if (status != SAI_STATUS_SUCCESS) + { + throw std::runtime_error("failed to get port count"); + } + port_count = attr.value.u32; + + port_list.resize(port_count); + attr.id = SAI_SWITCH_ATTR_PORT_LIST; + attr.value.objlist.count = port_count; + attr.value.objlist.list = port_list.data(); + status = sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr); + if (status != SAI_STATUS_SUCCESS || attr.value.objlist.count != port_count) + { + throw std::runtime_error("failed to get port list"); + } + + std::map> ports; + for (uint32_t i = 0; i < port_count; ++i) + { + string lanes_str = ""; + sai_uint32_t lanes[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + attr.id = SAI_PORT_ATTR_HW_LANE_LIST; + attr.value.u32list.count = 8; + attr.value.u32list.list = lanes; + status = sai_port_api->get_port_attribute(port_list[i], 1, &attr); + if (status != SAI_STATUS_SUCCESS) + { + throw std::runtime_error("failed to get hw lane list"); + } + + for (uint32_t j = 0; j < attr.value.u32list.count; ++j) + { + lanes_str += (j == 0) ? "" : ","; + lanes_str += to_string(attr.value.u32list.list[j]); + } + + vector fvs = { + { "lanes", lanes_str }, + { "speed", "1000" }, + { "mtu", "6000" }, + { "admin_status", "up" } + }; + + auto key = FRONT_PANEL_PORT_PREFIX + to_string(i); + + ports[key] = fvs; + } + + return ports; + } +} From bb4e19c7d3adb8479962e189325102a52cb8e721 Mon Sep 17 00:00:00 2001 From: Wenda Ni Date: Mon, 4 Nov 2019 09:46:20 -0800 Subject: [PATCH 11/63] Not wait till kernel net_devices are created for all physical ports to (#1109) Apply buffer profiles when hardware physical ports are created Introduce a mini state machine to track the portConfigState transition on receiving event from APPL_DB PORT_TABLE Signed-off-by: Wenda Ni --- orchagent/bufferorch.cpp | 2 +- orchagent/portsorch.cpp | 25 +++++++++++++++++++------ orchagent/portsorch.h | 10 +++++++++- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/orchagent/bufferorch.cpp b/orchagent/bufferorch.cpp index 2257e927bd..20944304b8 100644 --- a/orchagent/bufferorch.cpp +++ b/orchagent/bufferorch.cpp @@ -830,7 +830,7 @@ void BufferOrch::doTask(Consumer &consumer) { SWSS_LOG_ENTER(); - if (!gPortsOrch->isInitDone()) + if (!gPortsOrch->isConfigDone()) { return; } diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index 8958ea85dc..8d12d1ff91 100644 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -394,12 +394,23 @@ bool PortsOrch::allPortsReady() return m_initDone && m_pendingPortSet.empty(); } -/* Upon receiving PortInitDone, all the configured ports have been created*/ +/* Upon receiving PortInitDone, all the configured ports have been created in both hardware and kernel*/ bool PortsOrch::isInitDone() { return m_initDone; } +// Upon m_portConfigState transiting to PORT_CONFIG_DONE state, all physical ports have been "created" in hardware. +// Because of the asynchronous nature of sairedis calls, "create" in the strict sense means that the SAI create_port() +// function is called and the create port event has been pushed to the sairedis pipeline. Because sairedis pipeline +// preserves the order of the events received, any event that depends on the physical port being created first, e.g., +// buffer profile apply, will be popped in the FIFO fashion, processed in the right order after the physical port is +// physically created in the ASIC, and thus can be issued safely when this function call returns true. +bool PortsOrch::isConfigDone() +{ + return m_portConfigState == PORT_CONFIG_DONE; +} + bool PortsOrch::isPortAdminUp(const string &alias) { auto it = m_portList.find(alias); @@ -1513,14 +1524,14 @@ void PortsOrch::doPortTask(Consumer &consumer) if (alias == "PortConfigDone") { - if (m_portConfigDone) + if (m_portConfigState != PORT_CONFIG_MISSING) { - // Already done, ignore this task + // Already received, ignore this task it = consumer.m_toSync.erase(it); continue; } - m_portConfigDone = true; + m_portConfigState = PORT_CONFIG_RECEIVED; for (auto i : kfvFieldsValues(t)) { @@ -1652,7 +1663,7 @@ void PortsOrch::doPortTask(Consumer &consumer) * 2. Create new ports * 3. Initialize all ports */ - if (m_portConfigDone && (m_lanesAliasSpeedMap.size() == m_portCount)) + if (m_portConfigState == PORT_CONFIG_RECEIVED && (m_lanesAliasSpeedMap.size() == m_portCount)) { for (auto it = m_portListLaneMap.begin(); it != m_portListLaneMap.end();) { @@ -1697,9 +1708,11 @@ void PortsOrch::doPortTask(Consumer &consumer) it = m_lanesAliasSpeedMap.erase(it); } + + m_portConfigState = PORT_CONFIG_DONE; } - if (!m_portConfigDone) + if (m_portConfigState != PORT_CONFIG_DONE) { // Not yet receive PortConfigDone. Save it for future retry it++; diff --git a/orchagent/portsorch.h b/orchagent/portsorch.h index 6b12dd24fb..75831fdaf9 100644 --- a/orchagent/portsorch.h +++ b/orchagent/portsorch.h @@ -57,6 +57,7 @@ class PortsOrch : public Orch, public Subject bool allPortsReady(); bool isInitDone(); + bool isConfigDone(); bool isPortAdminUp(const string &alias); map& getAllPorts(); @@ -116,7 +117,14 @@ class PortsOrch : public Orch, public Subject sai_object_id_t m_default1QBridge; sai_object_id_t m_defaultVlan; - bool m_portConfigDone = false; + typedef enum + { + PORT_CONFIG_MISSING, + PORT_CONFIG_RECEIVED, + PORT_CONFIG_DONE, + } port_config_state_t; + + port_config_state_t m_portConfigState = PORT_CONFIG_MISSING; sai_uint32_t m_portCount; map, sai_object_id_t> m_portListLaneMap; map, tuple> m_lanesAliasSpeedMap; From 038d994f594115b626c3aed35297321a19827a78 Mon Sep 17 00:00:00 2001 From: Volodymyr Samotiy Date: Tue, 5 Nov 2019 20:43:14 +0200 Subject: [PATCH 12/63] [vnet]: Correct VNET route table size for BITMAP implementation (#1115) Signed-off-by: Volodymyr Samotiy --- orchagent/vnetorch.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchagent/vnetorch.h b/orchagent/vnetorch.h index 126a737d3c..49f3413d1f 100644 --- a/orchagent/vnetorch.h +++ b/orchagent/vnetorch.h @@ -14,7 +14,7 @@ #include "observer.h" #define VNET_BITMAP_SIZE 32 -#define VNET_TUNNEL_SIZE 512 +#define VNET_TUNNEL_SIZE 3072 #define VNET_NEIGHBOR_MAX 0xffff #define VXLAN_ENCAP_TTL 128 From 560456606b2c898d15c9cb2e588d65077fb03084 Mon Sep 17 00:00:00 2001 From: Tyler Li Date: Wed, 6 Nov 2019 02:44:31 +0800 Subject: [PATCH 13/63] [vs_test] fix fdb test failed randomly (#1118) - Change test_FdbAddedAfterMemberCreated not to assert the count of entries but to check the existance of entry added by itself, because other entry may be learned after flush. The former test case does't clean the config, that makes more chance to learn fdb entry. - Fix the bug is_fdb_entry_exists didn't match key. Add a function get_vlan_oid to change vlanid to bvid. Change mac format to match in asic_db. --- tests/conftest.py | 22 ++++++++++++++++++++-- tests/test_fdb.py | 20 ++++++++++++++------ tests/test_fdb_update.py | 8 ++++++-- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c510e441cd..7ae5792229 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -548,6 +548,20 @@ def get_map_iface_bridge_port_id(self, asic_db): return iface_2_bridge_port_id + def get_vlan_oid(self, asic_db, vlan_id): + tbl = swsscommon.Table(asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + keys = tbl.getKeys() + + for key in keys: + status, fvs = tbl.get(key) + assert status, "Error reading from table %s" % "ASIC_STATE:SAI_OBJECT_TYPE_VLAN" + + for k, v in fvs: + if k == "SAI_VLAN_ATTR_VLAN_ID" and v == vlan_id: + return True, key + + return False, "Not found vlan id %s" % vlan_id + def is_table_entry_exists(self, db, table, keyregex, attributes): tbl = swsscommon.Table(db, table) keys = tbl.getKeys() @@ -637,11 +651,15 @@ def is_fdb_entry_exists(self, db, table, key_values, attributes): except ValueError: d_key = json.loads('{' + key + '}') + key_found = True + for k, v in key_values: if k not in d_key or v != d_key[k]: - continue + key_found = False + break - key_found = True + if not key_found: + continue status, fvs = tbl.get(key) assert status, "Error reading from table %s" % table diff --git a/tests/test_fdb.py b/tests/test_fdb.py index 4e714d2021..37c210c492 100644 --- a/tests/test_fdb.py +++ b/tests/test_fdb.py @@ -334,16 +334,26 @@ def test_FdbAddedAfterMemberCreated(self, dvs, testlog): ] ) - # check that the FDB entry wasn't inserted into ASIC DB - assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 0, "The fdb entry leaked to ASIC" - vlan_before = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") bp_before = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT") vm_before = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN_MEMBER") # create vlan dvs.create_vlan("2") + time.sleep(1) + + # Get bvid from vlanid + ok, bvid = dvs.get_vlan_oid(dvs.adb, "2") + assert ok, bvid + + # check that the FDB entry wasn't inserted into ASIC DB + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "52:54:00:25:06:E9"), ("bvid", bvid)], []) + assert ok == False, "The fdb entry leaked to ASIC" + + # create vlan member dvs.create_vlan_member("2", "Ethernet0") + time.sleep(1) # check that the vlan information was propagated vlan_after = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") @@ -358,10 +368,8 @@ def test_FdbAddedAfterMemberCreated(self, dvs, testlog): iface_2_bridge_port_id = dvs.get_map_iface_bridge_port_id(dvs.adb) # check that the FDB entry was inserted into ASIC DB - assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 1, "The fdb entry wasn't inserted to ASIC" - ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", - [("mac", "52-54-00-25-06-E9"), ("vlan", "2")], + [("mac", "52:54:00:25:06:E9"), ("bvid", bvid)], [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_DYNAMIC"), ("SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID", iface_2_bridge_port_id["Ethernet0"]), ('SAI_FDB_ENTRY_ATTR_PACKET_ACTION', 'SAI_PACKET_ACTION_FORWARD')] diff --git a/tests/test_fdb_update.py b/tests/test_fdb_update.py index b3fd1a9670..d74c3f0c0f 100644 --- a/tests/test_fdb_update.py +++ b/tests/test_fdb_update.py @@ -102,6 +102,10 @@ def test_FDBAddedAndUpdated(dvs, testlog): assert bp_after - bp_before == 1, "The bridge port wasn't created" assert vm_after - vm_before == 1, "The vlan member wasn't added" + # Get bvid from vlanid + ok, bvid = dvs.get_vlan_oid(dvs.adb, "2") + assert ok, bvid + # Get mapping between interface name and its bridge port_id iface_2_bridge_port_id = dvs.get_map_iface_bridge_port_id(dvs.adb) @@ -109,7 +113,7 @@ def test_FDBAddedAndUpdated(dvs, testlog): assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 1, "The fdb entry wasn't inserted to ASIC" ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", - [("mac", "52-54-00-25-06-E9"), ("vlan", "2")], + [("mac", "52:54:00:25:06:E9"), ("bvid", bvid)], [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_DYNAMIC"), ("SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID", iface_2_bridge_port_id["Ethernet0"]), ('SAI_FDB_ENTRY_ATTR_PACKET_ACTION', 'SAI_PACKET_ACTION_FORWARD')] @@ -139,7 +143,7 @@ def test_FDBAddedAndUpdated(dvs, testlog): iface_2_bridge_port_id = dvs.get_map_iface_bridge_port_id(dvs.adb) ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", - [("mac", "52-54-00-25-06-E9"), ("vlan", "2")], + [("mac", "52:54:00:25:06:E9"), ("bvid", bvid)], [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_DYNAMIC"), ("SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID", iface_2_bridge_port_id["Ethernet4"]), ('SAI_FDB_ENTRY_ATTR_PACKET_ACTION', 'SAI_PACKET_ACTION_FORWARD')] From 85ff17da8ec7b750b668fb8d03d3494dcb51b555 Mon Sep 17 00:00:00 2001 From: Tyler Li Date: Thu, 7 Nov 2019 05:18:52 +0800 Subject: [PATCH 14/63] [VRF]: submit vrf feature (#943) Dataplane vrf feature --- cfgmgr/intfmgr.cpp | 161 ++- cfgmgr/intfmgr.h | 7 +- cfgmgr/nbrmgr.cpp | 23 +- cfgmgr/vrfmgr.cpp | 68 +- cfgmgr/vrfmgr.h | 5 +- doc/swss-schema.md | 28 +- fpmsyncd/routesync.cpp | 27 +- fpmsyncd/routesync.h | 2 +- neighsyncd/neighsync.cpp | 5 + orchagent/intfsorch.cpp | 234 ++-- orchagent/intfsorch.h | 6 +- orchagent/orchdaemon.cpp | 4 +- orchagent/request_parser.cpp | 4 +- orchagent/routeorch.cpp | 280 +++-- orchagent/routeorch.h | 27 +- orchagent/vnetorch.cpp | 2 +- orchagent/vrforch.cpp | 15 +- orchagent/vrforch.h | 66 +- tests/conftest.py | 6 + tests/mock_tests/aclorch_ut.cpp | 4 +- tests/test_acl.py | 88 ++ tests/test_interface.py | 1799 ++++++++++++++++++++++++++++--- tests/test_neighbor.py | 390 +++++++ tests/test_route.py | 627 ++++++++++- tests/test_vnet.py | 15 +- tests/test_vrf.py | 347 +++--- tests/test_warm_reboot.py | 123 +++ 27 files changed, 3695 insertions(+), 668 deletions(-) create mode 100644 tests/test_neighbor.py diff --git a/cfgmgr/intfmgr.cpp b/cfgmgr/intfmgr.cpp index 18f1d8e2f1..a84fe92a9e 100644 --- a/cfgmgr/intfmgr.cpp +++ b/cfgmgr/intfmgr.cpp @@ -15,6 +15,9 @@ using namespace swss; #define LAG_PREFIX "PortChannel" #define LOOPBACK_PREFIX "Loopback" #define VNET_PREFIX "Vnet" +#define VRF_PREFIX "Vrf" + +#define LOOPBACK_DEFAULT_MTU_STR "65536" IntfMgr::IntfMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, const vector &tableNames) : Orch(cfgDb, tableNames), @@ -58,7 +61,7 @@ void IntfMgr::setIntfIp(const string &alias, const string &opCmd, } } -void IntfMgr::setIntfVrf(const string &alias, const string vrfName) +void IntfMgr::setIntfVrf(const string &alias, const string &vrfName) { stringstream cmd; string res; @@ -71,7 +74,97 @@ void IntfMgr::setIntfVrf(const string &alias, const string vrfName) { cmd << IP_CMD << " link set " << shellquote(alias) << " nomaster"; } - EXEC_WITH_ERROR_THROW(cmd.str(), res); + int ret = swss::exec(cmd.str(), res); + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmd.str().c_str(), ret); + } +} + +void IntfMgr::addLoopbackIntf(const string &alias) +{ + stringstream cmd; + string res; + + cmd << IP_CMD << " link add " << alias << " mtu " << LOOPBACK_DEFAULT_MTU_STR << " type dummy && "; + cmd << IP_CMD << " link set " << alias << " up"; + int ret = swss::exec(cmd.str(), res); + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmd.str().c_str(), ret); + } +} + +void IntfMgr::delLoopbackIntf(const string &alias) +{ + stringstream cmd; + string res; + + cmd << IP_CMD << " link del " << alias; + int ret = swss::exec(cmd.str(), res); + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmd.str().c_str(), ret); + } +} + +int IntfMgr::getIntfIpCount(const string &alias) +{ + stringstream cmd; + string res; + + /* query ip address of the device with master name, it is much faster */ + // ip address show {{intf_name}} + // $(ip link show {{intf_name}} | grep -o 'master [^\\s]*') ==> [master {{vrf_name}}] + // | grep inet | grep -v 'inet6 fe80:' | wc -l + cmd << IP_CMD << " address show " << alias + << " $(" << IP_CMD << " link show " << alias << " | grep -o 'master [^\\s]*')" + << " | grep inet | grep -v 'inet6 fe80:' | wc -l"; + + int ret = swss::exec(cmd.str(), res); + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmd.str().c_str(), ret); + return 0; + } + + return std::stoi(res); +} + +bool IntfMgr::isIntfCreated(const string &alias) +{ + vector temp; + + if (m_stateIntfTable.get(alias, temp)) + { + SWSS_LOG_DEBUG("Intf %s is ready", alias.c_str()); + return true; + } + + return false; +} + +bool IntfMgr::isIntfChangeVrf(const string &alias, const string &vrfName) +{ + vector temp; + + if (m_stateIntfTable.get(alias, temp)) + { + for (auto idx : temp) + { + const auto &field = fvField(idx); + const auto &value = fvValue(idx); + if (field == "vrf") + { + if (value == vrfName) + return false; + else + return true; + } + } + } + + return false; } bool IntfMgr::isIntfStateOk(const string &alias) @@ -102,6 +195,14 @@ bool IntfMgr::isIntfStateOk(const string &alias) return true; } } + else if (!alias.compare(0, strlen(VRF_PREFIX), VRF_PREFIX)) + { + if (m_stateVrfTable.get(alias, temp)) + { + SWSS_LOG_DEBUG("Vrf %s is ready", alias.c_str()); + return true; + } + } else if (m_statePortTable.get(alias, temp)) { SWSS_LOG_DEBUG("Port %s is ready", alias.c_str()); @@ -149,32 +250,38 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, return false; } - // Set Interface VRF except for lo - if (!is_lo) + /* if to change vrf then skip */ + if (isIntfChangeVrf(alias, vrf_name)) { - if (!vrf_name.empty()) - { - setIntfVrf(alias, vrf_name); - } - m_appIntfTableProducer.set(alias, data); + SWSS_LOG_ERROR("%s can not change to %s directly, skipping", alias.c_str(), vrf_name.c_str()); + return true; } - else + if (is_lo) { - m_appIntfTableProducer.set("lo", data); + addLoopbackIntf(alias); } + if (!vrf_name.empty()) + { + setIntfVrf(alias, vrf_name); + } + m_appIntfTableProducer.set(alias, data); + m_stateIntfTable.hset(alias, "vrf", vrf_name); } else if (op == DEL_COMMAND) { - // Set Interface VRF except for lo - if (!is_lo) + /* make sure all ip addresses associated with interface are removed, otherwise these ip address would + be set with global vrf and it may cause ip address confliction. */ + if (getIntfIpCount(alias)) { - setIntfVrf(alias, ""); - m_appIntfTableProducer.del(alias); + return false; } - else + setIntfVrf(alias, ""); + if (is_lo) { - m_appIntfTableProducer.del("lo"); + delLoopbackIntf(alias); } + m_appIntfTableProducer.del(alias); + m_stateIntfTable.del(alias); } else { @@ -192,26 +299,21 @@ bool IntfMgr::doIntfAddrTask(const vector& keys, string alias(keys[0]); IpPrefix ip_prefix(keys[1]); - bool is_lo = !alias.compare(0, strlen(LOOPBACK_PREFIX), LOOPBACK_PREFIX); - string appKey = (is_lo ? "lo" : keys[0]) + ":" + keys[1]; + string appKey = keys[0] + ":" + keys[1]; if (op == SET_COMMAND) { /* - * Don't proceed if port/LAG/VLAN is not ready yet. + * Don't proceed if port/LAG/VLAN and intfGeneral are not ready yet. * The pending task will be checked periodically and retried. */ - if (!isIntfStateOk(alias)) + if (!isIntfStateOk(alias) || !isIntfCreated(alias)) { SWSS_LOG_DEBUG("Interface is not ready, skipping %s", alias.c_str()); return false; } - // Set Interface IP except for lo - if (!is_lo) - { - setIntfIp(alias, "add", ip_prefix); - } + setIntfIp(alias, "add", ip_prefix); std::vector fvVector; FieldValueTuple f("family", ip_prefix.isV4() ? IPV4_NAME : IPV6_NAME); @@ -224,11 +326,8 @@ bool IntfMgr::doIntfAddrTask(const vector& keys, } else if (op == DEL_COMMAND) { - // Set Interface IP except for lo - if (!is_lo) - { - setIntfIp(alias, "del", ip_prefix); - } + setIntfIp(alias, "del", ip_prefix); + m_appIntfTableProducer.del(appKey); m_stateIntfTable.del(keys[0] + state_db_key_delimiter + keys[1]); } diff --git a/cfgmgr/intfmgr.h b/cfgmgr/intfmgr.h index a0746e723e..4e82baef5e 100644 --- a/cfgmgr/intfmgr.h +++ b/cfgmgr/intfmgr.h @@ -22,11 +22,16 @@ class IntfMgr : public Orch Table m_statePortTable, m_stateLagTable, m_stateVlanTable, m_stateVrfTable, m_stateIntfTable; void setIntfIp(const std::string &alias, const std::string &opCmd, const IpPrefix &ipPrefix); - void setIntfVrf(const std::string &alias, const std::string vrfName); + void setIntfVrf(const std::string &alias, const std::string &vrfName); bool doIntfGeneralTask(const std::vector& keys, const std::vector& data, const std::string& op); bool doIntfAddrTask(const std::vector& keys, const std::vector& data, const std::string& op); void doTask(Consumer &consumer); bool isIntfStateOk(const std::string &alias); + bool isIntfCreated(const std::string &alias); + bool isIntfChangeVrf(const std::string &alias, const std::string &vrfName); + int getIntfIpCount(const std::string &alias); + void addLoopbackIntf(const std::string &alias); + void delLoopbackIntf(const std::string &alias); }; } diff --git a/cfgmgr/nbrmgr.cpp b/cfgmgr/nbrmgr.cpp index e3080b7c01..e525a7f0af 100644 --- a/cfgmgr/nbrmgr.cpp +++ b/cfgmgr/nbrmgr.cpp @@ -13,9 +13,6 @@ using namespace swss; -#define VLAN_PREFIX "Vlan" -#define LAG_PREFIX "PortChannel" - static bool send_message(struct nl_sock *sk, struct nl_msg *msg) { bool rc = false; @@ -67,25 +64,9 @@ bool NbrMgr::isIntfStateOk(const string &alias) { vector temp; - if (!alias.compare(0, strlen(VLAN_PREFIX), VLAN_PREFIX)) - { - if (m_stateVlanTable.get(alias, temp)) - { - SWSS_LOG_DEBUG("Vlan %s is ready", alias.c_str()); - return true; - } - } - else if (!alias.compare(0, strlen(LAG_PREFIX), LAG_PREFIX)) - { - if (m_stateLagTable.get(alias, temp)) - { - SWSS_LOG_DEBUG("Lag %s is ready", alias.c_str()); - return true; - } - } - else if (m_statePortTable.get(alias, temp)) + if (m_stateIntfTable.get(alias, temp)) { - SWSS_LOG_DEBUG("Port %s is ready", alias.c_str()); + SWSS_LOG_DEBUG("Intf %s is ready", alias.c_str()); return true; } diff --git a/cfgmgr/vrfmgr.cpp b/cfgmgr/vrfmgr.cpp index d8048a6b35..d8718813ef 100644 --- a/cfgmgr/vrfmgr.cpp +++ b/cfgmgr/vrfmgr.cpp @@ -10,6 +10,7 @@ #define VRF_TABLE_START 1001 #define VRF_TABLE_END 2000 +#define TABLE_LOCAL_PREF 1001 // after l3mdev-table using namespace swss; @@ -17,7 +18,8 @@ VrfMgr::VrfMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, con Orch(cfgDb, tableNames), m_appVrfTableProducer(appDb, APP_VRF_TABLE_NAME), m_appVnetTableProducer(appDb, APP_VNET_TABLE_NAME), - m_stateVrfTable(stateDb, STATE_VRF_TABLE_NAME) + m_stateVrfTable(stateDb, STATE_VRF_TABLE_NAME), + m_stateVrfObjectTable(stateDb, STATE_VRF_OBJECT_TABLE_NAME) { for (uint32_t i = VRF_TABLE_START; i < VRF_TABLE_END; i++) { @@ -63,6 +65,18 @@ VrfMgr::VrfMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, con break; } } + + cmd.str(""); + cmd.clear(); + cmd << IP_CMD << " rule | grep '^0:'"; + if (swss::exec(cmd.str(), res) == 0) + { + cmd.str(""); + cmd.clear(); + cmd << IP_CMD << " rule add pref " << TABLE_LOCAL_PREF << " table local && " << IP_CMD << " rule del pref 0 && " + << IP_CMD << " -6 rule add pref " << TABLE_LOCAL_PREF << " table local && " << IP_CMD << " -6 rule del pref 0"; + EXEC_WITH_ERROR_THROW(cmd.str(), res); + } } uint32_t VrfMgr::getFreeTable(void) @@ -139,6 +153,19 @@ bool VrfMgr::setLink(const string& vrfName) return true; } +bool VrfMgr::isVrfObjExist(const string& vrfName) +{ + vector temp; + + if (m_stateVrfObjectTable.get(vrfName, temp)) + { + SWSS_LOG_DEBUG("Vrf %s object exist", vrfName.c_str()); + return true; + } + + return false; +} + void VrfMgr::doTask(Consumer &consumer) { SWSS_LOG_ENTER(); @@ -173,20 +200,43 @@ void VrfMgr::doTask(Consumer &consumer) } else if (op == DEL_COMMAND) { - if (!delLink(vrfName)) - { - SWSS_LOG_ERROR("Failed to remove vrf netdev %s", vrfName.c_str()); - } - - m_stateVrfTable.del(vrfName); - + /* + * Delay delLink until vrf object deleted in orchagent to ensure fpmsyncd can get vrf ifname. + * Now state VRF_TABLE|Vrf represent vrf exist in appDB, if it exist vrf device is always effective. + * VRFOrch add/del state VRF_OBJECT_TABLE|Vrf to represent object existence. VNETOrch is not do so now. + */ if (consumer.getTableName() == CFG_VRF_TABLE_NAME) { - m_appVrfTableProducer.del(vrfName); + vector temp; + + if (m_stateVrfTable.get(vrfName, temp)) + { + /* VRFOrch add delay so wait */ + if (!isVrfObjExist(vrfName)) + { + it++; + continue; + } + + m_appVrfTableProducer.del(vrfName); + m_stateVrfTable.del(vrfName); + } + + if (isVrfObjExist(vrfName)) + { + it++; + continue; + } } else { m_appVnetTableProducer.del(vrfName); + m_stateVrfTable.del(vrfName); + } + + if (!delLink(vrfName)) + { + SWSS_LOG_ERROR("Failed to remove vrf netdev %s", vrfName.c_str()); } SWSS_LOG_NOTICE("Removed vrf netdev %s", vrfName.c_str()); diff --git a/cfgmgr/vrfmgr.h b/cfgmgr/vrfmgr.h index aae82a906c..f8da87d318 100644 --- a/cfgmgr/vrfmgr.h +++ b/cfgmgr/vrfmgr.h @@ -21,15 +21,16 @@ class VrfMgr : public Orch private: bool delLink(const std::string& vrfName); bool setLink(const std::string& vrfName); + bool isVrfObjExist(const std::string& vrfName); void recycleTable(uint32_t table); uint32_t getFreeTable(void); void handleVnetConfigSet(KeyOpFieldsValuesTuple &t); void doTask(Consumer &consumer); std::map m_vrfTableMap; - set m_freeTables; + std::set m_freeTables; - Table m_stateVrfTable; + Table m_stateVrfTable, m_stateVrfObjectTable; ProducerStateTable m_appVrfTableProducer, m_appVnetTableProducer; }; diff --git a/doc/swss-schema.md b/doc/swss-schema.md index b58d7cc81f..8cb191a231 100644 --- a/doc/swss-schema.md +++ b/doc/swss-schema.md @@ -466,8 +466,10 @@ Stores rules associated with a specific ACL table on the switch. ; it could be: : name of physical port. Example: "Ethernet10" : name of LAG port Example: "PortChannel5" - : next-hop ip address Example: "10.0.0.1" - : next-hop group set of addresses Example: "10.0.0.1,10.0.0.3" + : next-hop ip address (in global) Example: "10.0.0.1" + : next-hop ip address and vrf Example: "10.0.0.2@Vrf2" + : next-hop ip address and ifname Example: "10.0.0.3@Ethernet1" + : next-hop group set of next-hop Example: "10.0.0.1,10.0.0.3@Ethernet1" mirror_action = 1*255VCHAR ; refer to the mirror session (by default it will be ingress mirror action) mirror_ingress_action = 1*255VCHAR ; refer to the mirror session @@ -878,6 +880,28 @@ Stores information for physical switch ports managed by the switch chip. Ports t full-date = date-fullyear "-" date-month "-" date-mday time-stamp = full-date %x20 partial-time +### INTERFACE_TABLE + ;State for interface status, including two types of key + + key = INTERFACE_TABLE|ifname ; ifname should be Ethernet,Portchannel,Vlan,Loopback + vrf = "" / vrf_name ; interface has been created, global or vrf + + key = INTERFACE_TABLE|ifname|IPprefix + state = "ok" ; IP address has been set to interface + +### VRF_TABLE + ;State for vrf status, vrfmgrd has written it to app_db + + key = VRF_TABLE|vrf_name ; vrf_name start with 'Vrf' or 'Vnet' prefix + state = "ok" ; vrf entry exist in app_db, if yes vrf device must exist + +### VRF_OBJECT_TABLE + ;State for vrf object status, vrf exist in vrforch + + key = VRF_OBJECT_TABLE|vrf_name ; vrf_name start with 'Vrf' prefix + state = "ok" ; vrf entry exist in orchagent + + ## Configuration files What configuration files should we have? Do apps, orch agent each need separate files? diff --git a/fpmsyncd/routesync.cpp b/fpmsyncd/routesync.cpp index 9e7a9adf4e..2250d26d1a 100644 --- a/fpmsyncd/routesync.cpp +++ b/fpmsyncd/routesync.cpp @@ -16,6 +16,7 @@ using namespace swss; #define VXLAN_IF_NAME_PREFIX "Brvxlan" #define VNET_PREFIX "Vnet" +#define VRF_PREFIX "Vrf" RouteSync::RouteSync(RedisPipeline *pipeline) : m_routeTable(pipeline, APP_ROUTE_TABLE_NAME, true), @@ -60,13 +61,13 @@ void RouteSync::onMsg(int nlmsg_type, struct nl_object *obj) /* Otherwise, it is a regular route (include VRF route). */ else { - onRouteMsg(nlmsg_type, obj); + onRouteMsg(nlmsg_type, obj, master_name); } } else { - onRouteMsg(nlmsg_type, obj); + onRouteMsg(nlmsg_type, obj, NULL); } } @@ -74,15 +75,31 @@ void RouteSync::onMsg(int nlmsg_type, struct nl_object *obj) * Handle regular route (include VRF route) * @arg nlmsg_type Netlink message type * @arg obj Netlink object + * @arg vrf Vrf name */ -void RouteSync::onRouteMsg(int nlmsg_type, struct nl_object *obj) +void RouteSync::onRouteMsg(int nlmsg_type, struct nl_object *obj, char *vrf) { struct rtnl_route *route_obj = (struct rtnl_route *)obj; struct nl_addr *dip; - char destipprefix[MAX_ADDR_SIZE + 1] = {0}; + char destipprefix[IFNAMSIZ + MAX_ADDR_SIZE + 2] = {0}; + + if (vrf) + { + /* + * Now vrf device name is required to start with VRF_PREFIX, + * it is difficult to split vrf_name:ipv6_addr. + */ + if (memcmp(vrf, VRF_PREFIX, strlen(VRF_PREFIX))) + { + SWSS_LOG_ERROR("Invalid VRF name %s (ifindex %u)", vrf, rtnl_route_get_table(route_obj)); + return; + } + memcpy(destipprefix, vrf, strlen(vrf)); + destipprefix[strlen(vrf)] = ':'; + } dip = rtnl_route_get_dst(route_obj); - nl_addr2str(dip, destipprefix, MAX_ADDR_SIZE); + nl_addr2str(dip, destipprefix + strlen(destipprefix), MAX_ADDR_SIZE); SWSS_LOG_DEBUG("Receive new route message dest ip prefix: %s", destipprefix); /* diff --git a/fpmsyncd/routesync.h b/fpmsyncd/routesync.h index 8ab8fc9cb4..d636ca134a 100644 --- a/fpmsyncd/routesync.h +++ b/fpmsyncd/routesync.h @@ -33,7 +33,7 @@ class RouteSync : public NetMsg struct nl_sock *m_nl_sock; /* Handle regular route (include VRF route) */ - void onRouteMsg(int nlmsg_type, struct nl_object *obj); + void onRouteMsg(int nlmsg_type, struct nl_object *obj, char *vrf); /* Handle vnet route */ void onVnetRouteMsg(int nlmsg_type, struct nl_object *obj, string vnet); diff --git a/neighsyncd/neighsync.cpp b/neighsyncd/neighsync.cpp index c1f83c3aac..69ca563f2e 100644 --- a/neighsyncd/neighsync.cpp +++ b/neighsyncd/neighsync.cpp @@ -70,6 +70,11 @@ void NeighSync::onMsg(int nlmsg_type, struct nl_object *obj) key+= ipStr; int state = rtnl_neigh_get_state(neigh); + if (state == NUD_NOARP) + { + return; + } + bool delete_key = false; if ((nlmsg_type == RTM_DELNEIGH) || (state == NUD_INCOMPLETE) || (state == NUD_FAILED)) diff --git a/orchagent/intfsorch.cpp b/orchagent/intfsorch.cpp index 4497865892..fc84a39752 100644 --- a/orchagent/intfsorch.cpp +++ b/orchagent/intfsorch.cpp @@ -35,6 +35,8 @@ const int intfsorch_pri = 35; #define RIF_FLEX_STAT_COUNTER_POLL_MSECS "1000" #define UPDATE_MAPS_SEC 1 +#define LOOPBACK_PREFIX "Loopback" + static const vector rifStatIds = { SAI_ROUTER_INTERFACE_STAT_IN_PACKETS, @@ -82,6 +84,22 @@ sai_object_id_t IntfsOrch::getRouterIntfsId(const string &alias) return port.m_rif_id; } +bool IntfsOrch::isPrefixSubnet(const IpPrefix &ip_prefix, const string &alias) +{ + if (m_syncdIntfses.find(alias) == m_syncdIntfses.end()) + { + return false; + } + for (auto &prefixIt: m_syncdIntfses[alias].ip_addresses) + { + if (prefixIt.getSubnet() == ip_prefix) + { + return true; + } + } + return false; +} + string IntfsOrch::getRouterIntfsAlias(const IpAddress &ip, const string &vrf_name) { sai_object_id_t vrf_id = gVirtualRouterId; @@ -174,13 +192,14 @@ bool IntfsOrch::setIntf(const string& alias, sai_object_id_t vrf_id, const IpPre auto it_intfs = m_syncdIntfses.find(alias); if (it_intfs == m_syncdIntfses.end()) { - if (addRouterIntfs(vrf_id, port)) + if (!ip_prefix && addRouterIntfs(vrf_id, port)) { gPortsOrch->increasePortRefCount(alias); IntfsEntry intfs_entry; intfs_entry.ref_count = 0; intfs_entry.vrf_id = vrf_id; m_syncdIntfses[alias] = intfs_entry; + m_vrfOrch->increaseVrfRefCount(vrf_id); } else { @@ -202,28 +221,36 @@ bool IntfsOrch::setIntf(const string& alias, sai_object_id_t vrf_id, const IpPre * delete IP with netmask /8. To handle this we in case of overlap * we should wait until entry with /8 netmask will be removed. * Time frame between those event is quite small.*/ + /* NOTE: Overlap checking in this interface is not enough. + * So extend to check in all interfaces of this VRF */ bool overlaps = false; - for (const auto &prefixIt: m_syncdIntfses[alias].ip_addresses) + for (const auto &intfsIt: m_syncdIntfses) { - if (prefixIt.isAddressInSubnet(ip_prefix->getIp()) || - ip_prefix->isAddressInSubnet(prefixIt.getIp())) + if (port.m_vr_id != intfsIt.second.vrf_id) { - overlaps = true; - SWSS_LOG_NOTICE("Router interface %s IP %s overlaps with %s.", port.m_alias.c_str(), - prefixIt.to_string().c_str(), ip_prefix->to_string().c_str()); - break; + continue; } - } - if (overlaps) - { - /* Overlap of IP address network */ - return false; + for (const auto &prefixIt: intfsIt.second.ip_addresses) + { + if (prefixIt.isAddressInSubnet(ip_prefix->getIp()) || + ip_prefix->isAddressInSubnet(prefixIt.getIp())) + { + overlaps = true; + SWSS_LOG_NOTICE("Router interface %s IP %s overlaps with %s.", port.m_alias.c_str(), + prefixIt.to_string().c_str(), ip_prefix->to_string().c_str()); + break; + } + } + + if (overlaps) + { + /* Overlap of IP address network */ + return false; + } } - vrf_id = port.m_vr_id; - addSubnetRoute(port, *ip_prefix); - addIp2MeRoute(vrf_id, *ip_prefix); + addIp2MeRoute(port.m_vr_id, *ip_prefix); if (port.m_type == Port::VLAN) { @@ -246,8 +273,7 @@ bool IntfsOrch::removeIntf(const string& alias, sai_object_id_t vrf_id, const Ip if (ip_prefix && m_syncdIntfses[alias].ip_addresses.count(*ip_prefix)) { - removeSubnetRoute(port, *ip_prefix); - removeIp2MeRoute(vrf_id, *ip_prefix); + removeIp2MeRoute(port.m_vr_id, *ip_prefix); if(port.m_type == Port::VLAN) { @@ -257,13 +283,13 @@ bool IntfsOrch::removeIntf(const string& alias, sai_object_id_t vrf_id, const Ip m_syncdIntfses[alias].ip_addresses.erase(*ip_prefix); } - /* Remove router interface that no IP addresses are associated with */ - if (m_syncdIntfses[alias].ip_addresses.size() == 0) + if (!ip_prefix) { - if (removeRouterIntfs(port)) + if (m_syncdIntfses[alias].ip_addresses.size() == 0 && removeRouterIntfs(port)) { gPortsOrch->decreasePortRefCount(alias); m_syncdIntfses.erase(alias); + m_vrfOrch->decreaseVrfRefCount(vrf_id); return true; } else @@ -293,6 +319,7 @@ void IntfsOrch::doTask(Consumer &consumer) string alias(keys[0]); IpPrefix ip_prefix; bool ip_prefix_in_key = false; + bool is_lo = !alias.compare(0, strlen(LOOPBACK_PREFIX), LOOPBACK_PREFIX); if (keys.size() > 1) { @@ -337,53 +364,37 @@ void IntfsOrch::doTask(Consumer &consumer) string op = kfvOp(t); if (op == SET_COMMAND) { - if (alias == "lo") + if (is_lo) { if (!ip_prefix_in_key) { - it = consumer.m_toSync.erase(it); - continue; - } - - bool addIp2Me = false; - // set request for lo may come after warm start restore. - // It is also to prevent dupicate set requests in normal running case. - auto it_intfs = m_syncdIntfses.find(alias); - if (it_intfs == m_syncdIntfses.end()) - { - IntfsEntry intfs_entry; - - intfs_entry.ref_count = 0; - intfs_entry.vrf_id = vrf_id; - intfs_entry.ip_addresses.insert(ip_prefix); - m_syncdIntfses[alias] = intfs_entry; - addIp2Me = true; + if (m_syncdIntfses.find(alias) == m_syncdIntfses.end()) + { + IntfsEntry intfs_entry; + intfs_entry.ref_count = 0; + intfs_entry.vrf_id = vrf_id; + m_syncdIntfses[alias] = intfs_entry; + m_vrfOrch->increaseVrfRefCount(vrf_id); + } } else { - if (m_syncdIntfses[alias].ip_addresses.count(ip_prefix) == 0) - { + if (m_syncdIntfses.find(alias) == m_syncdIntfses.end()) + { + it++; + continue; + } + if (m_syncdIntfses[alias].ip_addresses.count(ip_prefix) == 0) + { m_syncdIntfses[alias].ip_addresses.insert(ip_prefix); - addIp2Me = true; - } - } - if (addIp2Me) - { - addIp2MeRoute(vrf_id, ip_prefix); + addIp2MeRoute(m_syncdIntfses[alias].vrf_id, ip_prefix); + } } it = consumer.m_toSync.erase(it); continue; } - /* Wait for the Interface entry first */ - auto it_intfs = m_syncdIntfses.find(alias); - if (ip_prefix_in_key && it_intfs == m_syncdIntfses.end()) - { - it++; - continue; - } - Port port; if (!gPortsOrch->getPort(alias, port)) { @@ -429,19 +440,33 @@ void IntfsOrch::doTask(Consumer &consumer) } else if (op == DEL_COMMAND) { - if (alias == "lo") + if (is_lo) { - // TODO: handle case for which lo is not in default vrf gVirtualRouterId - if (m_syncdIntfses.find(alias) != m_syncdIntfses.end()) + if (!ip_prefix_in_key) { - if (m_syncdIntfses[alias].ip_addresses.count(ip_prefix)) + if (m_syncdIntfses.find(alias) != m_syncdIntfses.end()) { - m_syncdIntfses[alias].ip_addresses.erase(ip_prefix); - removeIp2MeRoute(vrf_id, ip_prefix); + if (m_syncdIntfses[alias].ip_addresses.size() == 0) + { + m_vrfOrch->decreaseVrfRefCount(m_syncdIntfses[alias].vrf_id); + m_syncdIntfses.erase(alias); + } + else + { + it++; + continue; + } } - if (m_syncdIntfses[alias].ip_addresses.size() == 0) + } + else + { + if (m_syncdIntfses.find(alias) != m_syncdIntfses.end()) { - m_syncdIntfses.erase(alias); + if (m_syncdIntfses[alias].ip_addresses.count(ip_prefix)) + { + m_syncdIntfses[alias].ip_addresses.erase(ip_prefix); + removeIp2MeRoute(m_syncdIntfses[alias].vrf_id, ip_prefix); + } } } @@ -616,89 +641,6 @@ bool IntfsOrch::removeRouterIntfs(Port &port) return true; } -void IntfsOrch::addSubnetRoute(const Port &port, const IpPrefix &ip_prefix) -{ - sai_route_entry_t unicast_route_entry; - unicast_route_entry.switch_id = gSwitchId; - unicast_route_entry.vr_id = port.m_vr_id; - copy(unicast_route_entry.destination, ip_prefix); - subnet(unicast_route_entry.destination, unicast_route_entry.destination); - - sai_attribute_t attr; - vector attrs; - - attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; - attr.value.s32 = SAI_PACKET_ACTION_FORWARD; - attrs.push_back(attr); - - attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; - attr.value.oid = port.m_rif_id; - attrs.push_back(attr); - - sai_status_t status = sai_route_api->create_route_entry(&unicast_route_entry, (uint32_t)attrs.size(), attrs.data()); - if (status != SAI_STATUS_SUCCESS) - { - SWSS_LOG_ERROR("Failed to create subnet route to %s from %s, rv:%d", - ip_prefix.to_string().c_str(), port.m_alias.c_str(), status); - throw runtime_error("Failed to create subnet route."); - } - - SWSS_LOG_NOTICE("Create subnet route to %s from %s", - ip_prefix.to_string().c_str(), port.m_alias.c_str()); - increaseRouterIntfsRefCount(port.m_alias); - - if (unicast_route_entry.destination.addr_family == SAI_IP_ADDR_FAMILY_IPV4) - { - gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); - } - else - { - gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); - } - - gRouteOrch->notifyNextHopChangeObservers(ip_prefix, NextHopGroupKey(), true); -} - -void IntfsOrch::removeSubnetRoute(const Port &port, const IpPrefix &ip_prefix) -{ - sai_route_entry_t unicast_route_entry; - unicast_route_entry.switch_id = gSwitchId; - unicast_route_entry.vr_id = port.m_vr_id; - copy(unicast_route_entry.destination, ip_prefix); - subnet(unicast_route_entry.destination, unicast_route_entry.destination); - - sai_status_t status = sai_route_api->remove_route_entry(&unicast_route_entry); - if (status != SAI_STATUS_SUCCESS) - { - if (status == SAI_STATUS_ITEM_NOT_FOUND) - { - SWSS_LOG_ERROR("No subnet route found for %s", ip_prefix.to_string().c_str()); - return; - } - else - { - SWSS_LOG_ERROR("Failed to remove subnet route to %s from %s, rv:%d", - ip_prefix.to_string().c_str(), port.m_alias.c_str(), status); - throw runtime_error("Failed to remove subnet route."); - } - } - - SWSS_LOG_NOTICE("Remove subnet route to %s from %s", - ip_prefix.to_string().c_str(), port.m_alias.c_str()); - decreaseRouterIntfsRefCount(port.m_alias); - - if (unicast_route_entry.destination.addr_family == SAI_IP_ADDR_FAMILY_IPV4) - { - gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); - } - else - { - gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); - } - - gRouteOrch->notifyNextHopChangeObservers(ip_prefix, NextHopGroupKey(), false); -} - void IntfsOrch::addIp2MeRoute(sai_object_id_t vrf_id, const IpPrefix &ip_prefix) { sai_route_entry_t unicast_route_entry; diff --git a/orchagent/intfsorch.h b/orchagent/intfsorch.h index acf1276a62..5ac0d047ed 100644 --- a/orchagent/intfsorch.h +++ b/orchagent/intfsorch.h @@ -33,6 +33,7 @@ class IntfsOrch : public Orch IntfsOrch(DBConnector *db, string tableName, VRFOrch *vrf_orch); sai_object_id_t getRouterIntfsId(const string&); + bool isPrefixSubnet(const IpPrefix&, const string&); string getRouterIntfsAlias(const IpAddress &ip, const string &vrf_name = ""); void increaseRouterIntfsRefCount(const string&); @@ -78,14 +79,9 @@ class IntfsOrch : public Orch std::string getRifFlexCounterTableKey(std::string s); - int getRouterIntfsRefCount(const string&); - bool addRouterIntfs(sai_object_id_t vrf_id, Port &port); bool removeRouterIntfs(Port &port); - void addSubnetRoute(const Port &port, const IpPrefix &ip_prefix); - void removeSubnetRoute(const Port &port, const IpPrefix &ip_prefix); - void addDirectedBroadcast(const Port &port, const IpPrefix &ip_prefix); void removeDirectedBroadcast(const Port &port, const IpPrefix &ip_prefix); }; diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index df151f9966..ada19d1494 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -110,7 +110,7 @@ bool OrchDaemon::init() gDirectory.set(cfg_vnet_rt_orch); VNetRouteOrch *vnet_rt_orch = new VNetRouteOrch(m_applDb, vnet_tables, vnet_orch); gDirectory.set(vnet_rt_orch); - VRFOrch *vrf_orch = new VRFOrch(m_applDb, APP_VRF_TABLE_NAME); + VRFOrch *vrf_orch = new VRFOrch(m_applDb, APP_VRF_TABLE_NAME, m_stateDb, STATE_VRF_OBJECT_TABLE_NAME); gDirectory.set(vrf_orch); const vector chassis_frontend_tables = { @@ -121,7 +121,7 @@ bool OrchDaemon::init() gIntfsOrch = new IntfsOrch(m_applDb, APP_INTF_TABLE_NAME, vrf_orch); gNeighOrch = new NeighOrch(m_applDb, APP_NEIGH_TABLE_NAME, gIntfsOrch); - gRouteOrch = new RouteOrch(m_applDb, APP_ROUTE_TABLE_NAME, gNeighOrch); + gRouteOrch = new RouteOrch(m_applDb, APP_ROUTE_TABLE_NAME, gNeighOrch, gIntfsOrch, vrf_orch); TableConnector confDbSflowTable(m_configDb, CFG_SFLOW_TABLE_NAME); TableConnector appCoppTable(m_applDb, APP_COPP_TABLE_NAME); diff --git a/orchagent/request_parser.cpp b/orchagent/request_parser.cpp index cdd74c3d40..59676a01f3 100644 --- a/orchagent/request_parser.cpp +++ b/orchagent/request_parser.cpp @@ -115,9 +115,9 @@ void Request::parseAttrs(const KeyOpFieldsValuesTuple& request) for (auto i = kfvFieldsValues(request).begin(); i != kfvFieldsValues(request).end(); i++) { - if (fvField(*i) == "empty") + if (fvField(*i) == "empty" || fvField(*i) == "NULL") { - // if name of the attribute is 'empty', just skip it. + // if name of the attribute is 'empty' or 'NULL', just skip it. // it's used when we don't have any attributes, but we have to provide one for redis continue; } diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index 1b611ca890..8aabef4a9e 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -13,7 +13,6 @@ extern sai_route_api_t* sai_route_api; extern sai_switch_api_t* sai_switch_api; extern PortsOrch *gPortsOrch; -extern IntfsOrch *gIntfsOrch; extern CrmOrch *gCrmOrch; /* Default maximum number of next hop groups */ @@ -22,9 +21,11 @@ extern CrmOrch *gCrmOrch; const int routeorch_pri = 5; -RouteOrch::RouteOrch(DBConnector *db, string tableName, NeighOrch *neighOrch) : +RouteOrch::RouteOrch(DBConnector *db, string tableName, NeighOrch *neighOrch, IntfsOrch *intfsOrch, VRFOrch *vrfOrch) : Orch(db, tableName, routeorch_pri), m_neighOrch(neighOrch), + m_intfsOrch(intfsOrch), + m_vrfOrch(vrfOrch), m_nextHopGroupCount(0), m_resync(false) { @@ -82,7 +83,7 @@ RouteOrch::RouteOrch(DBConnector *db, string tableName, NeighOrch *neighOrch) : gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); /* Add default IPv4 route into the m_syncdRoutes */ - m_syncdRoutes[default_ip_prefix] = NextHopGroupKey(); + m_syncdRoutes[gVirtualRouterId][default_ip_prefix] = NextHopGroupKey(); SWSS_LOG_NOTICE("Create IPv4 default route with packet action drop"); @@ -101,7 +102,7 @@ RouteOrch::RouteOrch(DBConnector *db, string tableName, NeighOrch *neighOrch) : gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); /* Add default IPv6 route into the m_syncdRoutes */ - m_syncdRoutes[v6_default_ip_prefix] = NextHopGroupKey(); + m_syncdRoutes[gVirtualRouterId][v6_default_ip_prefix] = NextHopGroupKey(); SWSS_LOG_NOTICE("Create IPv6 default route with packet action drop"); @@ -202,39 +203,32 @@ sai_object_id_t RouteOrch::getNextHopGroupId(const NextHopGroupKey& nexthops) return m_syncdNextHopGroups[nexthops].next_hop_group_id; } -void RouteOrch::attach(Observer *observer, const IpAddress& dstAddr) +void RouteOrch::attach(Observer *observer, const IpAddress& dstAddr, sai_object_id_t vrf_id) { SWSS_LOG_ENTER(); - auto observerEntry = m_nextHopObservers.find(dstAddr); + Host host = std::make_pair(vrf_id, dstAddr); + auto observerEntry = m_nextHopObservers.find(host); /* Create a new observer entry if no current observer is observing this * IP address */ if (observerEntry == m_nextHopObservers.end()) { - m_nextHopObservers.emplace(dstAddr, NextHopObserverEntry()); - observerEntry = m_nextHopObservers.find(dstAddr); + m_nextHopObservers.emplace(host, NextHopObserverEntry()); + observerEntry = m_nextHopObservers.find(host); /* Find the prefixes that cover the destination IP */ - for (auto route : m_syncdRoutes) + if (m_syncdRoutes.find(vrf_id) != m_syncdRoutes.end()) { - if (route.first.isAddressInSubnet(dstAddr)) + for (auto route : m_syncdRoutes.at(vrf_id)) { - SWSS_LOG_INFO("Prefix %s covers destination address", - route.first.to_string().c_str()); - observerEntry->second.routeTable.emplace( - route.first, route.second); - } - } - - /* Find the subnets that cover the destination IP - * The next hop of the subnet routes is left empty */ - for (auto prefix : gIntfsOrch->getSubnetRoutes()) - { - if (prefix.isAddressInSubnet(dstAddr)) - { - observerEntry->second.routeTable.emplace( - prefix, NextHopGroupKey()); + if (route.first.isAddressInSubnet(dstAddr)) + { + SWSS_LOG_INFO("Prefix %s covers destination address", + route.first.to_string().c_str()); + observerEntry->second.routeTable.emplace( + route.first, route.second); + } } } } @@ -249,16 +243,16 @@ void RouteOrch::attach(Observer *observer, const IpAddress& dstAddr) SWSS_LOG_NOTICE("Attached next hop observer of route %s for destination IP %s", observerEntry->second.routeTable.rbegin()->first.to_string().c_str(), dstAddr.to_string().c_str()); - NextHopUpdate update = { dstAddr, route->first, route->second }; + NextHopUpdate update = { vrf_id, dstAddr, route->first, route->second }; observer->update(SUBJECT_TYPE_NEXTHOP_CHANGE, static_cast(&update)); } } -void RouteOrch::detach(Observer *observer, const IpAddress& dstAddr) +void RouteOrch::detach(Observer *observer, const IpAddress& dstAddr, sai_object_id_t vrf_id) { SWSS_LOG_ENTER(); - auto observerEntry = m_nextHopObservers.find(dstAddr); + auto observerEntry = m_nextHopObservers.find(std::make_pair(vrf_id, dstAddr)); if (observerEntry == m_nextHopObservers.end()) { @@ -398,11 +392,22 @@ void RouteOrch::doTask(Consumer& consumer) { /* Mark all current routes as dirty (DEL) in consumer.m_toSync map */ SWSS_LOG_NOTICE("Start resync routes\n"); - for (auto i : m_syncdRoutes) + for (auto j : m_syncdRoutes) { - vector v; - auto x = KeyOpFieldsValuesTuple(i.first.to_string(), DEL_COMMAND, v); - consumer.m_toSync[i.first.to_string()] = x; + string vrf; + + if (j.first != gVirtualRouterId) + { + vrf = m_vrfOrch->getVRFname(j.first) + ":"; + } + + for (auto i : j.second) + { + vector v; + key = vrf + i.first.to_string(); + auto x = KeyOpFieldsValuesTuple(key, DEL_COMMAND, v); + consumer.m_toSync[key] = x; + } } m_resync = true; } @@ -422,7 +427,27 @@ void RouteOrch::doTask(Consumer& consumer) continue; } - IpPrefix ip_prefix = IpPrefix(key); + sai_object_id_t vrf_id; + IpPrefix ip_prefix; + + if (!key.compare(0, strlen(VRF_PREFIX), VRF_PREFIX)) + { + size_t found = key.find(':'); + string vrf_name = key.substr(0, found); + + if (!m_vrfOrch->isVRFexists(vrf_name)) + { + it++; + continue; + } + vrf_id = m_vrfOrch->getVRFid(vrf_name); + ip_prefix = IpPrefix(key.substr(found+1)); + } + else + { + vrf_id = gVirtualRouterId; + ip_prefix = IpPrefix(key); + } if (op == SET_COMMAND) { @@ -455,15 +480,10 @@ void RouteOrch::doTask(Consumer& consumer) { /* If any existing routes are updated to point to the * above interfaces, remove them from the ASIC. */ - if (m_syncdRoutes.find(ip_prefix) != m_syncdRoutes.end()) - { - if (removeRoute(ip_prefix)) - it = consumer.m_toSync.erase(it); - else - it++; - } - else + if (removeRoute(vrf_id, ip_prefix)) it = consumer.m_toSync.erase(it); + else + it++; continue; } @@ -477,13 +497,43 @@ void RouteOrch::doTask(Consumer& consumer) if (ipv.size() == 1 && IpAddress(ipv[0]).isZero()) { - it = consumer.m_toSync.erase(it); + /* blackhole to be done */ + if (alsv[0] == "unknown") + { + /* add addBlackholeRoute or addRoute support empty nhg */ + it = consumer.m_toSync.erase(it); + } + /* directly connected route to VRF interface which come from kernel */ + else if (!alsv[0].compare(0, strlen(VRF_PREFIX), VRF_PREFIX)) + { + it = consumer.m_toSync.erase(it); + } + /* skip prefix which is linklocal or multicast */ + else if (ip_prefix.getIp().getAddrScope() != IpAddress::GLOBAL_SCOPE) + { + it = consumer.m_toSync.erase(it); + } + /* fullmask subnet route is same as ip2me route */ + else if (ip_prefix.isFullMask() && m_intfsOrch->isPrefixSubnet(ip_prefix, alsv[0])) + { + it = consumer.m_toSync.erase(it); + } + /* subnet route, vrf leaked route, etc */ + else + { + if (addRoute(vrf_id, ip_prefix, nhg)) + it = consumer.m_toSync.erase(it); + else + it++; + } continue; } - if (m_syncdRoutes.find(ip_prefix) == m_syncdRoutes.end() || m_syncdRoutes[ip_prefix] != nhg) + if (m_syncdRoutes.find(vrf_id) == m_syncdRoutes.end() || + m_syncdRoutes.at(vrf_id).find(ip_prefix) == m_syncdRoutes.at(vrf_id).end() || + m_syncdRoutes.at(vrf_id).at(ip_prefix) != nhg) { - if (addRoute(ip_prefix, nhg)) + if (addRoute(vrf_id, ip_prefix, nhg)) it = consumer.m_toSync.erase(it); else it++; @@ -494,16 +544,11 @@ void RouteOrch::doTask(Consumer& consumer) } else if (op == DEL_COMMAND) { - if (m_syncdRoutes.find(ip_prefix) != m_syncdRoutes.end()) - { - if (removeRoute(ip_prefix)) - it = consumer.m_toSync.erase(it); - else - it++; - } - else - /* Cannot locate the route */ + /* Cannot locate the route or remove succeed */ + if (removeRoute(vrf_id, ip_prefix)) it = consumer.m_toSync.erase(it); + else + it++; } else { @@ -513,13 +558,13 @@ void RouteOrch::doTask(Consumer& consumer) } } -void RouteOrch::notifyNextHopChangeObservers(const IpPrefix &prefix, const NextHopGroupKey &nexthops, bool add) +void RouteOrch::notifyNextHopChangeObservers(sai_object_id_t vrf_id, const IpPrefix &prefix, const NextHopGroupKey &nexthops, bool add) { SWSS_LOG_ENTER(); for (auto& entry : m_nextHopObservers) { - if (!prefix.isAddressInSubnet(entry.first)) + if (vrf_id != entry.first.first || !prefix.isAddressInSubnet(entry.first.second)) { continue; } @@ -527,7 +572,7 @@ void RouteOrch::notifyNextHopChangeObservers(const IpPrefix &prefix, const NextH if (add) { bool update_required = false; - NextHopUpdate update = { entry.first, prefix, nexthops }; + NextHopUpdate update = { vrf_id, entry.first.second, prefix, nexthops }; /* Table should not be empty. Default route should always exists. */ assert(!entry.second.routeTable.empty()); @@ -578,7 +623,7 @@ void RouteOrch::notifyNextHopChangeObservers(const IpPrefix &prefix, const NextH assert(!entry.second.routeTable.empty()); auto route = entry.second.routeTable.rbegin(); - NextHopUpdate update = { entry.first, route->first, route->second }; + NextHopUpdate update = { vrf_id, entry.first.second, route->first, route->second }; for (auto observer : entry.second.observers) { @@ -604,7 +649,10 @@ void RouteOrch::increaseNextHopRefCount(const NextHopGroupKey &nexthops) else if (nexthops.getSize() == 1) { NextHopKey nexthop(nexthops.to_string()); - m_neighOrch->increaseNextHopRefCount(nexthop); + if (nexthop.ip_address.isZero()) + m_intfsOrch->increaseRouterIntfsRefCount(nexthop.alias); + else + m_neighOrch->increaseNextHopRefCount(nexthop); } else { @@ -621,7 +669,10 @@ void RouteOrch::decreaseNextHopRefCount(const NextHopGroupKey &nexthops) else if (nexthops.getSize() == 1) { NextHopKey nexthop(nexthops.to_string()); - m_neighOrch->decreaseNextHopRefCount(nexthop); + if (nexthop.ip_address.isZero()) + m_intfsOrch->decreaseRouterIntfsRefCount(nexthop.alias); + else + m_neighOrch->decreaseNextHopRefCount(nexthop); } else { @@ -816,7 +867,7 @@ bool RouteOrch::removeNextHopGroup(const NextHopGroupKey &nexthops) return true; } -void RouteOrch::addTempRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHops) +void RouteOrch::addTempRoute(sai_object_id_t vrf_id, const IpPrefix &ipPrefix, const NextHopGroupKey &nextHops) { SWSS_LOG_ENTER(); @@ -845,30 +896,51 @@ void RouteOrch::addTempRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &ne /* Set the route's temporary next hop to be the randomly picked one */ NextHopGroupKey tmp_next_hop((*it).to_string()); - addRoute(ipPrefix, tmp_next_hop); + addRoute(vrf_id, ipPrefix, tmp_next_hop); } -bool RouteOrch::addRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHops) +bool RouteOrch::addRoute(sai_object_id_t vrf_id, const IpPrefix &ipPrefix, const NextHopGroupKey &nextHops) { SWSS_LOG_ENTER(); /* next_hop_id indicates the next hop id or next hop group id of this route */ sai_object_id_t next_hop_id; - auto it_route = m_syncdRoutes.find(ipPrefix); + + if (m_syncdRoutes.find(vrf_id) == m_syncdRoutes.end()) + { + m_syncdRoutes.emplace(vrf_id, RouteTable()); + m_vrfOrch->increaseVrfRefCount(vrf_id); + } + + auto it_route = m_syncdRoutes.at(vrf_id).find(ipPrefix); /* The route is pointing to a next hop */ if (nextHops.getSize() == 1) { NextHopKey nexthop(nextHops.to_string()); - if (m_neighOrch->hasNextHop(nexthop)) + if (nexthop.ip_address.isZero()) { - next_hop_id = m_neighOrch->getNextHopId(nexthop); + next_hop_id = m_intfsOrch->getRouterIntfsId(nexthop.alias); + /* rif is not created yet */ + if (next_hop_id == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_INFO("Failed to get next hop %s for %s", + nextHops.to_string().c_str(), ipPrefix.to_string().c_str()); + return false; + } } else { - SWSS_LOG_INFO("Failed to get next hop %s for %s", - nextHops.to_string().c_str(), ipPrefix.to_string().c_str()); - return false; + if (m_neighOrch->hasNextHop(nexthop)) + { + next_hop_id = m_neighOrch->getNextHopId(nexthop); + } + else + { + SWSS_LOG_INFO("Failed to get next hop %s for %s", + nextHops.to_string().c_str(), ipPrefix.to_string().c_str()); + return false; + } } } /* The route is pointing to a next hop group */ @@ -884,7 +956,7 @@ bool RouteOrch::addRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHo /* If the current next hop is part of the next hop group to sync, * then return false and no need to add another temporary route. */ - if (it_route != m_syncdRoutes.end() && it_route->second.getSize() == 1) + if (it_route != m_syncdRoutes.at(vrf_id).end() && it_route->second.getSize() == 1) { NextHopKey nexthop(it_route->second.to_string()); if (nextHops.contains(nexthop)) @@ -896,7 +968,7 @@ bool RouteOrch::addRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHo /* Add a temporary route when a next hop group cannot be added, * and there is no temporary route right now or the current temporary * route is not pointing to a member of the next hop group to sync. */ - addTempRoute(ipPrefix, nextHops); + addTempRoute(vrf_id, ipPrefix, nextHops); /* Return false since the original route is not successfully added */ return false; } @@ -907,7 +979,7 @@ bool RouteOrch::addRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHo /* Sync the route entry */ sai_route_entry_t route_entry; - route_entry.vr_id = gVirtualRouterId; + route_entry.vr_id = vrf_id; route_entry.switch_id = gSwitchId; copy(route_entry.destination, ipPrefix); @@ -919,7 +991,7 @@ bool RouteOrch::addRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHo * (group) id. The old next hop (group) is then not used and the reference * count will decrease by 1. */ - if (it_route == m_syncdRoutes.end()) + if (it_route == m_syncdRoutes.at(vrf_id).end()) { route_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; route_attr.value.oid = next_hop_id; @@ -996,18 +1068,33 @@ bool RouteOrch::addRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHo ipPrefix.to_string().c_str(), nextHops.to_string().c_str()); } - m_syncdRoutes[ipPrefix] = nextHops; + m_syncdRoutes[vrf_id][ipPrefix] = nextHops; - notifyNextHopChangeObservers(ipPrefix, nextHops, true); + notifyNextHopChangeObservers(vrf_id, ipPrefix, nextHops, true); return true; } -bool RouteOrch::removeRoute(const IpPrefix &ipPrefix) +bool RouteOrch::removeRoute(sai_object_id_t vrf_id, const IpPrefix &ipPrefix) { SWSS_LOG_ENTER(); + auto it_route_table = m_syncdRoutes.find(vrf_id); + if (it_route_table == m_syncdRoutes.end()) + { + SWSS_LOG_INFO("Failed to find route table, vrf_id 0x%lx\n", vrf_id); + return true; + } + + auto it_route = it_route_table->second.find(ipPrefix); + if (it_route == it_route_table->second.end()) + { + SWSS_LOG_INFO("Failed to find route entry, vrf_id 0x%lx, prefix %s\n", vrf_id, + ipPrefix.to_string().c_str()); + return true; + } + sai_route_entry_t route_entry; - route_entry.vr_id = gVirtualRouterId; + route_entry.vr_id = vrf_id; route_entry.switch_id = gSwitchId; copy(route_entry.destination, ipPrefix); @@ -1060,39 +1147,42 @@ bool RouteOrch::removeRoute(const IpPrefix &ipPrefix) } } - /* Remove next hop group entry if ref_count is zero */ - auto it_route = m_syncdRoutes.find(ipPrefix); - if (it_route != m_syncdRoutes.end()) + + /* + * Decrease the reference count only when the route is pointing to a next hop. + * Decrease the reference count when the route is pointing to a next hop group, + * and check whether the reference count decreases to zero. If yes, then we need + * to remove the next hop group. + */ + decreaseNextHopRefCount(it_route->second); + if (it_route->second.getSize() > 1 + && m_syncdNextHopGroups[it_route->second].ref_count == 0) { - /* - * Decrease the reference count only when the route is pointing to a next hop. - * Decrease the reference count when the route is pointing to a next hop group, - * and check whether the reference count decreases to zero. If yes, then we need - * to remove the next hop group. - */ - decreaseNextHopRefCount(it_route->second); - if (it_route->second.getSize() > 1 - && m_syncdNextHopGroups[it_route->second].ref_count == 0) - { - removeNextHopGroup(it_route->second); - } + removeNextHopGroup(it_route->second); } + SWSS_LOG_INFO("Remove route %s with next hop(s) %s", ipPrefix.to_string().c_str(), it_route->second.to_string().c_str()); if (ipPrefix.isDefaultRoute()) { - m_syncdRoutes[ipPrefix] = NextHopGroupKey(); + it_route_table->second[ipPrefix] = NextHopGroupKey(); /* Notify about default route next hop change */ - notifyNextHopChangeObservers(ipPrefix, m_syncdRoutes[ipPrefix], true); + notifyNextHopChangeObservers(vrf_id, ipPrefix, it_route_table->second[ipPrefix], true); } else { - m_syncdRoutes.erase(ipPrefix); + it_route_table->second.erase(ipPrefix); /* Notify about the route next hop removal */ - notifyNextHopChangeObservers(ipPrefix, NextHopGroupKey(), false); + notifyNextHopChangeObservers(vrf_id, ipPrefix, NextHopGroupKey(), false); + + if (it_route_table->second.size() == 0) + { + m_syncdRoutes.erase(vrf_id); + m_vrfOrch->decreaseVrfRefCount(vrf_id); + } } return true; diff --git a/orchagent/routeorch.h b/orchagent/routeorch.h index e00223245c..84550d7589 100644 --- a/orchagent/routeorch.h +++ b/orchagent/routeorch.h @@ -29,6 +29,7 @@ struct NextHopGroupEntry struct NextHopUpdate { + sai_object_id_t vrf_id; IpAddress destination; IpPrefix prefix; NextHopGroupKey nexthopGroup; @@ -40,8 +41,12 @@ struct NextHopObserverEntry; typedef std::map NextHopGroupTable; /* RouteTable: destination network, NextHopGroupKey */ typedef std::map RouteTable; -/* NextHopObserverTable: Destination IP address, next hop observer entry */ -typedef std::map NextHopObserverTable; +/* RouteTables: vrf_id, RouteTable */ +typedef std::map RouteTables; +/* Host: vrf_id, IpAddress */ +typedef std::pair Host; +/* NextHopObserverTable: Host, next hop observer entry */ +typedef std::map NextHopObserverTable; struct NextHopObserverEntry { @@ -52,13 +57,13 @@ struct NextHopObserverEntry class RouteOrch : public Orch, public Subject { public: - RouteOrch(DBConnector *db, string tableName, NeighOrch *neighOrch); + RouteOrch(DBConnector *db, string tableName, NeighOrch *neighOrch, IntfsOrch *intfsOrch, VRFOrch *vrfOrch); bool hasNextHopGroup(const NextHopGroupKey&) const; sai_object_id_t getNextHopGroupId(const NextHopGroupKey&); - void attach(Observer *, const IpAddress&); - void detach(Observer *, const IpAddress&); + void attach(Observer *, const IpAddress&, sai_object_id_t vrf_id = gVirtualRouterId); + void detach(Observer *, const IpAddress&, sai_object_id_t vrf_id = gVirtualRouterId); void increaseNextHopRefCount(const NextHopGroupKey&); void decreaseNextHopRefCount(const NextHopGroupKey&); @@ -70,22 +75,24 @@ class RouteOrch : public Orch, public Subject bool validnexthopinNextHopGroup(const NextHopKey&); bool invalidnexthopinNextHopGroup(const NextHopKey&); - void notifyNextHopChangeObservers(const IpPrefix&, const NextHopGroupKey&, bool); + void notifyNextHopChangeObservers(sai_object_id_t, const IpPrefix&, const NextHopGroupKey&, bool); private: NeighOrch *m_neighOrch; + IntfsOrch *m_intfsOrch; + VRFOrch *m_vrfOrch; int m_nextHopGroupCount; int m_maxNextHopGroupCount; bool m_resync; - RouteTable m_syncdRoutes; + RouteTables m_syncdRoutes; NextHopGroupTable m_syncdNextHopGroups; NextHopObserverTable m_nextHopObservers; - void addTempRoute(const IpPrefix&, const NextHopGroupKey&); - bool addRoute(const IpPrefix&, const NextHopGroupKey&); - bool removeRoute(const IpPrefix&); + void addTempRoute(sai_object_id_t, const IpPrefix&, const NextHopGroupKey&); + bool addRoute(sai_object_id_t, const IpPrefix&, const NextHopGroupKey&); + bool removeRoute(sai_object_id_t, const IpPrefix&); std::string getLinkLocalEui64Addr(void); void addLinkLocalRouteToMe(sai_object_id_t vrf_id, IpPrefix linklocal_prefix); diff --git a/orchagent/vnetorch.cpp b/orchagent/vnetorch.cpp index 684d773433..6552f79cc5 100644 --- a/orchagent/vnetorch.cpp +++ b/orchagent/vnetorch.cpp @@ -1748,7 +1748,7 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP } else if (vr_id == port.m_vr_id) { - vr_set.insert(vrf_obj->getVRidEgress()); + vr_set = vrf_obj->getVRids(); } else { diff --git a/orchagent/vrforch.cpp b/orchagent/vrforch.cpp index 8258aacc90..62b56e1e0e 100644 --- a/orchagent/vrforch.cpp +++ b/orchagent/vrforch.cpp @@ -14,7 +14,6 @@ using namespace std; using namespace swss; - extern sai_virtual_router_api_t* sai_virtual_router_api; extern sai_object_id_t gSwitchId; @@ -82,14 +81,17 @@ bool VRFOrch::addOperation(const Request& request) return false; } - vrf_table_[vrf_name] = router_id; + vrf_table_[vrf_name].vrf_id = router_id; + vrf_table_[vrf_name].ref_count = 0; + vrf_id_table_[router_id] = vrf_name; + m_stateVrfObjectTable.hset(vrf_name, "state", "ok"); SWSS_LOG_NOTICE("VRF '%s' was added", vrf_name.c_str()); } else { // Update an existing vrf - sai_object_id_t router_id = it->second; + sai_object_id_t router_id = it->second.vrf_id; for (const auto& attr: attrs) { @@ -118,7 +120,10 @@ bool VRFOrch::delOperation(const Request& request) return true; } - sai_object_id_t router_id = vrf_table_[vrf_name]; + if (vrf_table_[vrf_name].ref_count) + return false; + + sai_object_id_t router_id = vrf_table_[vrf_name].vrf_id; sai_status_t status = sai_virtual_router_api->remove_virtual_router(router_id); if (status != SAI_STATUS_SUCCESS) { @@ -127,6 +132,8 @@ bool VRFOrch::delOperation(const Request& request) } vrf_table_.erase(vrf_name); + vrf_id_table_.erase(router_id); + m_stateVrfObjectTable.del(vrf_name); SWSS_LOG_NOTICE("VRF '%s' was removed", vrf_name.c_str()); diff --git a/orchagent/vrforch.h b/orchagent/vrforch.h index 7512c51a41..41ad55a003 100644 --- a/orchagent/vrforch.h +++ b/orchagent/vrforch.h @@ -4,7 +4,15 @@ #include "request_parser.h" extern sai_object_id_t gVirtualRouterId; -typedef std::unordered_map VRFTable; + +struct VrfEntry +{ + sai_object_id_t vrf_id; + int ref_count; +}; + +typedef std::unordered_map VRFTable; +typedef std::unordered_map VRFIdNameTable; const request_description_t request_description = { { REQ_T_STRING }, @@ -30,7 +38,9 @@ class VRFRequest : public Request class VRFOrch : public Orch2 { public: - VRFOrch(swss::DBConnector *db, const std::string& tableName) : Orch2(db, tableName, request_) + VRFOrch(swss::DBConnector *appDb, const std::string& appTableName, swss::DBConnector *stateDb, const std::string& stateTableName) : + Orch2(appDb, appTableName, request_), + m_stateVrfObjectTable(stateDb, stateTableName) { } @@ -43,7 +53,7 @@ class VRFOrch : public Orch2 { if (vrf_table_.find(name) != std::end(vrf_table_)) { - return vrf_table_.at(name); + return vrf_table_.at(name).vrf_id; } else { @@ -51,12 +61,62 @@ class VRFOrch : public Orch2 } } + std::string getVRFname(sai_object_id_t vrf_id) const + { + if (vrf_id == gVirtualRouterId) + { + return std::string(""); + } + if (vrf_id_table_.find(vrf_id) != std::end(vrf_id_table_)) + { + return vrf_id_table_.at(vrf_id); + } + else + { + return std::string(""); + } + } + + void increaseVrfRefCount(const std::string& name) + { + if (vrf_table_.find(name) != std::end(vrf_table_)) + { + vrf_table_.at(name).ref_count++; + } + } + + void increaseVrfRefCount(sai_object_id_t vrf_id) + { + if (vrf_id != gVirtualRouterId) + { + increaseVrfRefCount(getVRFname(vrf_id)); + } + } + + void decreaseVrfRefCount(const std::string& name) + { + if (vrf_table_.find(name) != std::end(vrf_table_)) + { + vrf_table_.at(name).ref_count--; + } + } + + void decreaseVrfRefCount(sai_object_id_t vrf_id) + { + if (vrf_id != gVirtualRouterId) + { + decreaseVrfRefCount(getVRFname(vrf_id)); + } + } + private: virtual bool addOperation(const Request& request); virtual bool delOperation(const Request& request); VRFTable vrf_table_; + VRFIdNameTable vrf_id_table_; VRFRequest request_; + swss::Table m_stateVrfObjectTable; }; #endif // __VRFORCH_H diff --git a/tests/conftest.py b/tests/conftest.py index 7ae5792229..8fba25d8d8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -754,6 +754,7 @@ def remove_ip_address(self, interface, ip): tbl_name = "INTERFACE" tbl = swsscommon.Table(self.cdb, tbl_name) tbl._del(interface + "|" + ip); + tbl._del(interface); time.sleep(1) def set_mtu(self, interface, mtu): @@ -775,6 +776,11 @@ def add_neighbor(self, interface, ip, mac): tbl.set(interface + ":" + ip, fvs) time.sleep(1) + def remove_neighbor(self, interface, ip): + tbl = swsscommon.ProducerStateTable(self.pdb, "NEIGH_TABLE") + tbl._del(interface + ":" + ip) + time.sleep(1) + def setup_db(self): self.pdb = swsscommon.DBConnector(0, self.redis_sock, 0) self.adb = swsscommon.DBConnector(1, self.redis_sock, 0) diff --git a/tests/mock_tests/aclorch_ut.cpp b/tests/mock_tests/aclorch_ut.cpp index ae4267ddb5..c6541dbded 100644 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -352,7 +352,7 @@ namespace aclorch_test gCrmOrch = new CrmOrch(m_config_db.get(), CFG_CRM_TABLE_NAME); ASSERT_EQ(gVrfOrch, nullptr); - gVrfOrch = new VRFOrch(m_app_db.get(), APP_VRF_TABLE_NAME); + gVrfOrch = new VRFOrch(m_app_db.get(), APP_VRF_TABLE_NAME, m_state_db.get(), STATE_VRF_OBJECT_TABLE_NAME); ASSERT_EQ(gIntfsOrch, nullptr); gIntfsOrch = new IntfsOrch(m_app_db.get(), APP_INTF_TABLE_NAME, gVrfOrch); @@ -361,7 +361,7 @@ namespace aclorch_test gNeighOrch = new NeighOrch(m_app_db.get(), APP_NEIGH_TABLE_NAME, gIntfsOrch); ASSERT_EQ(gRouteOrch, nullptr); - gRouteOrch = new RouteOrch(m_app_db.get(), APP_ROUTE_TABLE_NAME, gNeighOrch); + gRouteOrch = new RouteOrch(m_app_db.get(), APP_ROUTE_TABLE_NAME, gNeighOrch, gIntfsOrch, gVrfOrch); TableConnector applDbFdb(m_app_db.get(), APP_FDB_TABLE_NAME); TableConnector stateDbFdb(m_state_db.get(), STATE_FDB_TABLE_NAME); diff --git a/tests/test_acl.py b/tests/test_acl.py index 5cfeb49b4e..83909b7c81 100644 --- a/tests/test_acl.py +++ b/tests/test_acl.py @@ -1215,6 +1215,94 @@ def test_AclRuleIcmpV6(self, dvs, testlog): self.remove_acl_table(acl_table) + def test_AclRuleRedirectToNexthop(self, dvs, testlog): + dvs.setup_db() + self.setup_db(dvs) + + # bring up interface + dvs.set_interface_status("Ethernet4", "up") + + # assign IP to interface + dvs.add_ip_address("Ethernet4", "10.0.0.1/24") + + # add neighbor + dvs.add_neighbor("Ethernet4", "10.0.0.2", "00:01:02:03:04:05") + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") + keys = atbl.getKeys() + assert len(keys) == 1 + nhi_oid = keys[0] + + # create ACL_TABLE in config db + tbl = swsscommon.Table(self.cdb, "ACL_TABLE") + fvs = swsscommon.FieldValuePairs([("policy_desc", "test"), ("type", "L3"), ("ports", "Ethernet0")]) + tbl.set("test_redirect", fvs) + + time.sleep(2) + + # create acl rule + tbl = swsscommon.Table(self.cdb, "ACL_RULE") + fvs = swsscommon.FieldValuePairs([ + ("priority", "100"), + ("L4_SRC_PORT", "65000"), + ("PACKET_ACTION", "REDIRECT:10.0.0.2@Ethernet4")]) + tbl.set("test_redirect|test_rule1", fvs) + + time.sleep(1) + + test_acl_table_id = self.get_acl_table_id(dvs) + + # check acl table in asic db + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_ENTRY") + keys = atbl.getKeys() + + acl_entry = [k for k in keys if k not in dvs.asicdb.default_acl_entries] + assert len(acl_entry) == 1 + + (status, fvs) = atbl.get(acl_entry[0]) + assert status == True + assert len(fvs) == 6 + for fv in fvs: + if fv[0] == "SAI_ACL_ENTRY_ATTR_TABLE_ID": + assert fv[1] == test_acl_table_id + elif fv[0] == "SAI_ACL_ENTRY_ATTR_ADMIN_STATE": + assert fv[1] == "true" + elif fv[0] == "SAI_ACL_ENTRY_ATTR_PRIORITY": + assert fv[1] == "100" + elif fv[0] == "SAI_ACL_ENTRY_ATTR_ACTION_COUNTER": + assert True + elif fv[0] == "SAI_ACL_ENTRY_ATTR_FIELD_L4_SRC_PORT": + assert fv[1] == "65000&mask:0xffff" + elif fv[0] == "SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT": + assert fv[1] == nhi_oid + else: + assert False + + # remove acl rule + tbl._del("test_redirect|test_rule1") + + time.sleep(1) + + (status, fvs) = atbl.get(acl_entry[0]) + assert status == False + + tbl = swsscommon.Table(self.cdb, "ACL_TABLE") + tbl._del("test_redirect") + + time.sleep(1) + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_TABLE") + keys = atbl.getKeys() + assert len(keys) >= 1 + + # remove neighbor + dvs.remove_neighbor("Ethernet4", "10.0.0.2") + + # remove interface ip + dvs.remove_ip_address("Ethernet4", "10.0.0.1/24") + + # bring down interface + dvs.set_interface_status("Ethernet4", "down") class TestAclRuleValidation(BaseTestAcl): """ Test class for cases that check if orchagent corectly validates diff --git a/tests/test_interface.py b/tests/test_interface.py index 0ff67dcc00..5abe7eb9d0 100644 --- a/tests/test_interface.py +++ b/tests/test_interface.py @@ -9,41 +9,134 @@ def setup_db(self, dvs): self.adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) self.cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) - def set_admin_status(self, interface, status): - tbl = swsscommon.Table(self.cdb, "PORT") + def set_admin_status(self, dvs, interface, status): + if interface.startswith("PortChannel"): + tbl_name = "PORTCHANNEL" + elif interface.startswith("Vlan"): + tbl_name = "VLAN" + else: + tbl_name = "PORT" + tbl = swsscommon.Table(self.cdb, tbl_name) fvs = swsscommon.FieldValuePairs([("admin_status", status)]) tbl.set(interface, fvs) time.sleep(1) + # when using FRR, route cannot be inserted if the neighbor is not + # connected. thus it is mandatory to force the interface up manually + if interface.startswith("PortChannel"): + dvs.runcmd("bash -c 'echo " + ("1" if status == "up" else "0") +\ + " > /sys/class/net/" + interface + "/carrier'") + time.sleep(1) + + def create_vrf(self, vrf_name): + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") + initial_entries = set(tbl.getKeys()) + + tbl = swsscommon.Table(self.cdb, "VRF") + fvs = swsscommon.FieldValuePairs([('empty', 'empty')]) + tbl.set(vrf_name, fvs) + time.sleep(1) + + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") + current_entries = set(tbl.getKeys()) + assert len(current_entries - initial_entries) == 1 + return list(current_entries - initial_entries)[0] + + def remove_vrf(self, vrf_name): + tbl = swsscommon.Table(self.cdb, "VRF") + tbl._del(vrf_name) + time.sleep(1) + + def create_l3_intf(self, interface, vrf_name): + if interface.startswith("PortChannel"): + tbl_name = "PORTCHANNEL_INTERFACE" + elif interface.startswith("Vlan"): + tbl_name = "VLAN_INTERFACE" + elif interface.startswith("Loopback"): + tbl_name = "LOOPBACK_INTERFACE" + else: + tbl_name = "INTERFACE" + if len(vrf_name) == 0: + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + else: + fvs = swsscommon.FieldValuePairs([("vrf_name", vrf_name)]) + tbl = swsscommon.Table(self.cdb, tbl_name) + tbl.set(interface, fvs) + time.sleep(1) + + def remove_l3_intf(self, interface): + if interface.startswith("PortChannel"): + tbl_name = "PORTCHANNEL_INTERFACE" + elif interface.startswith("Vlan"): + tbl_name = "VLAN_INTERFACE" + elif interface.startswith("Loopback"): + tbl_name = "LOOPBACK_INTERFACE" + else: + tbl_name = "INTERFACE" + tbl = swsscommon.Table(self.cdb, tbl_name) + tbl._del(interface) + time.sleep(1) + def add_ip_address(self, interface, ip): - tbl = swsscommon.Table(self.cdb, "INTERFACE") + if interface.startswith("PortChannel"): + tbl_name = "PORTCHANNEL_INTERFACE" + elif interface.startswith("Vlan"): + tbl_name = "VLAN_INTERFACE" + elif interface.startswith("Loopback"): + tbl_name = "LOOPBACK_INTERFACE" + else: + tbl_name = "INTERFACE" + tbl = swsscommon.Table(self.cdb, tbl_name) fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) tbl.set(interface + "|" + ip, fvs) - tbl.set(interface, fvs) - time.sleep(2) # IPv6 netlink message needs longer time + time.sleep(1) def remove_ip_address(self, interface, ip): - tbl = swsscommon.Table(self.cdb, "INTERFACE") - tbl._del(interface + "|" + ip); - tbl._del(interface); + if interface.startswith("PortChannel"): + tbl_name = "PORTCHANNEL_INTERFACE" + elif interface.startswith("Vlan"): + tbl_name = "VLAN_INTERFACE" + elif interface.startswith("Loopback"): + tbl_name = "LOOPBACK_INTERFACE" + else: + tbl_name = "INTERFACE" + tbl = swsscommon.Table(self.cdb, tbl_name) + tbl._del(interface + "|" + ip) time.sleep(1) def set_mtu(self, interface, mtu): - tbl = swsscommon.Table(self.cdb, "PORT") + if interface.startswith("PortChannel"): + tbl_name = "PORTCHANNEL" + elif interface.startswith("Vlan"): + tbl_name = "VLAN" + else: + tbl_name = "PORT" + tbl = swsscommon.Table(self.cdb, tbl_name) fvs = swsscommon.FieldValuePairs([("mtu", mtu)]) tbl.set(interface, fvs) time.sleep(1) - def test_InterfaceAddRemoveIpv6Address(self, dvs, testlog): + def test_PortInterfaceAddRemoveIpv6Address(self, dvs, testlog): self.setup_db(dvs) + # create interface + self.create_l3_intf("Ethernet8", "") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("Ethernet8") + assert status == True + for fv in fvs: + assert fv[0] != "vrf_name" + # bring up interface # NOTE: For IPv6, only when the interface is up will the netlink message # get generated. - self.set_admin_status("Ethernet8", "up") + self.set_admin_status(dvs, "Ethernet8", "up") # assign IP to interface self.add_ip_address("Ethernet8", "fc00::1/126") + time.sleep(2) # IPv6 netlink message needs longer time # check application database tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet8") @@ -94,14 +187,22 @@ def test_InterfaceAddRemoveIpv6Address(self, dvs, testlog): # remove IP from interface self.remove_ip_address("Ethernet8", "fc00::1/126") + # remove interface + self.remove_l3_intf("Ethernet8") + # bring down interface - self.set_admin_status("Ethernet8", "down") + self.set_admin_status(dvs, "Ethernet8", "down") # check application database tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet8") intf_entries = tbl.getKeys() assert len(intf_entries) == 0 + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Ethernet8" + # check ASIC database tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") for key in tbl.getKeys(): @@ -111,9 +212,22 @@ def test_InterfaceAddRemoveIpv6Address(self, dvs, testlog): if route["dest"] == "fc00::1/128": assert False - def test_InterfaceAddRemoveIpv4Address(self, dvs, testlog): + def test_PortInterfaceAddRemoveIpv4Address(self, dvs, testlog): self.setup_db(dvs) + # bring up interface + self.set_admin_status(dvs, "Ethernet8", "up") + + # create interface + self.create_l3_intf("Ethernet8", "") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("Ethernet8") + assert status == True + for fv in fvs: + assert fv[0] != "vrf_name" + # assign IP to interface self.add_ip_address("Ethernet8", "10.0.0.4/31") @@ -166,11 +280,22 @@ def test_InterfaceAddRemoveIpv4Address(self, dvs, testlog): # remove IP from interface self.remove_ip_address("Ethernet8", "10.0.0.4/31") + # remove interface + self.remove_l3_intf("Ethernet8") + + # bring down interface + self.set_admin_status(dvs, "Ethernet8", "down") + # check application database tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet8") intf_entries = tbl.getKeys() assert len(intf_entries) == 0 + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Ethernet8" + # check ASIC database tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") for key in tbl.getKeys(): @@ -180,9 +305,19 @@ def test_InterfaceAddRemoveIpv4Address(self, dvs, testlog): if route["dest"] == "10.0.0.4/32": assert False - def test_InterfaceSetMtu(self, dvs, testlog): + def test_PortInterfaceSetMtu(self, dvs, testlog): self.setup_db(dvs) + # create interface + self.create_l3_intf("Ethernet16", "") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("Ethernet16") + assert status == True + for fv in fvs: + assert fv[0] != "vrf_name" + # assign IP to interface self.add_ip_address("Ethernet16", "20.0.0.8/29") @@ -210,69 +345,65 @@ def test_InterfaceSetMtu(self, dvs, testlog): # remove IP from interface self.remove_ip_address("Ethernet16", "20.0.0.8/29") -class TestLagRouterInterfaceIpv4(object): - def setup_db(self, dvs): - self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) - self.adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) - self.cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) + # remove interface + self.remove_l3_intf("Ethernet16") - def create_port_channel(self, dvs, alias): - tbl = swsscommon.Table(self.cdb, "PORTCHANNEL") - fvs = swsscommon.FieldValuePairs([("admin_status", "up"), - ("mtu", "9100")]) - tbl.set(alias, fvs) - time.sleep(1) + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet16") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 - def remove_port_channel(self, dvs, alias): - tbl = swsscommon.Table(self.cdb, "PORTCHANNEL") - tbl._del(alias) - time.sleep(1) + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Ethernet16" - def add_port_channel_members(self, dvs, lag, members): - tbl = swsscommon.Table(self.cdb, "PORTCHANNEL_MEMBER") - fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) - for member in members: - tbl.set(lag + "|" + member, fvs) - time.sleep(1) + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "20.0.0.8/29": + assert False + if route["dest"] == "20.0.0.8/32": + assert False - def remove_port_channel_members(self, dvs, lag, members): - tbl = swsscommon.Table(self.cdb, "PORTCHANNEL_MEMBER") - for member in members: - tbl._del(lag + "|" + member) - time.sleep(1) + def test_PortInterfaceAddRemoveIpv6AddressWithVrf(self, dvs, testlog): + self.setup_db(dvs) - def add_ip_address(self, interface, ip): - tbl = swsscommon.Table(self.cdb, "PORTCHANNEL_INTERFACE") - fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) - tbl.set(interface + "|" + ip, fvs) - tbl.set(interface, fvs) - time.sleep(1) + # bring up interface + self.set_admin_status(dvs, "Ethernet8", "up") - def remove_ip_address(self, interface, ip): - tbl = swsscommon.Table(self.cdb, "PORTCHANNEL_INTERFACE") - tbl._del(interface + "|" + ip); - time.sleep(1) + # create vrf + vrf_oid = self.create_vrf("Vrf_0") - def set_mtu(self, interface, mtu): - tbl = swsscommon.Table(self.cdb, "PORTCHANNEL") - fvs = swsscommon.FieldValuePairs([("mtu", mtu)]) - tbl.set(interface, fvs) - time.sleep(1) + # create interface with vrf + self.create_l3_intf("Ethernet8", "Vrf_0") - def test_InterfaceAddRemoveIpv4Address(self, dvs, testlog): - self.setup_db(dvs) + # check interface's vrf + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("Ethernet8") + assert status == True + for fv in fvs: + if fv[0] == "vrf_name": + assert fv[1] == "Vrf_0" + vrf_found = True + break + assert vrf_found == True - # create port channel - self.create_port_channel(dvs, "PortChannel001") + # check linux kernel + (exitcode, result) = dvs.runcmd(['sh', '-c', "ip link show Ethernet8 | grep Vrf"]) + assert "Vrf_0" in result # assign IP to interface - self.add_ip_address("PortChannel001", "30.0.0.4/31") + self.add_ip_address("Ethernet8", "fc00::1/126") + time.sleep(2) # IPv6 netlink message needs longer time # check application database - tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet8") intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 - assert intf_entries[0] == "30.0.0.4/31" + assert intf_entries[0] == "fc00::1/126" (status, fvs) = tbl.get(tbl.getKeys()[0]) assert status == True @@ -281,7 +412,7 @@ def test_InterfaceAddRemoveIpv4Address(self, dvs, testlog): if fv[0] == "scope": assert fv[1] == "global" elif fv[0] == "family": - assert fv[1] == "IPv4" + assert fv[1] == "IPv6" else: assert False @@ -302,23 +433,49 @@ def test_InterfaceAddRemoveIpv4Address(self, dvs, testlog): # the default MTU without any configuration is 9100 if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": assert fv[1] == "9100" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID": + assert fv[1] == vrf_oid # check ASIC route database tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") for key in tbl.getKeys(): route = json.loads(key) - if route["dest"] == "30.0.0.4/31": + if route["dest"] == "fc00::/126": subnet_found = True - if route["dest"] == "30.0.0.4/32": + assert route["vr"] == vrf_oid + if route["dest"] == "fc00::1/128": ip2me_found = True + assert route["vr"] == vrf_oid assert subnet_found and ip2me_found # remove IP from interface - self.remove_ip_address("PortChannel001", "30.0.0.4/31") + self.remove_ip_address("Ethernet8", "fc00::1/126") + + # remove vrf from interface + self.remove_l3_intf("Ethernet8") + + # remove vrf + self.remove_vrf("Vrf_0") + + # bring down interface + self.set_admin_status(dvs, "Ethernet8", "down") # check application database - tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet8") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # check linux kernel + (exitcode, result) = dvs.runcmd(['sh', '-c', "ip link show Ethernet8 | grep Vrf"]) + assert "Vrf_0" not in result + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Ethernet8" + + tbl = swsscommon.Table(self.pdb, "VRF") intf_entries = tbl.getKeys() assert len(intf_entries) == 0 @@ -326,29 +483,54 @@ def test_InterfaceAddRemoveIpv4Address(self, dvs, testlog): tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") for key in tbl.getKeys(): route = json.loads(key) - if route["dest"] == "30.0.0.4/31": + if route["dest"] == "fc00::/126": assert False - if route["dest"] == "30.0.0.4/32": + if route["dest"] == "fc00::1/128": assert False - # remove port channel - self.remove_port_channel(dvs, "PortChannel001") - - def test_InterfaceSetMtu(self, dvs, testlog): + def test_PortInterfaceAddRemoveIpv4AddressWithVrf(self, dvs, testlog): self.setup_db(dvs) - # create port channel - self.create_port_channel(dvs, "PortChannel002") + # bring up interface + self.set_admin_status(dvs, "Ethernet8", "up") - # add port channel members - self.add_port_channel_members(dvs, "PortChannel002", ["Ethernet0", "Ethernet4"]) + # create vrf + vrf_oid = self.create_vrf("Vrf_0") + + # create interface with vrf + self.create_l3_intf("Ethernet8", "Vrf_0") + + # check interface's vrf + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("Ethernet8") + assert status == True + for fv in fvs: + if fv[0] == "vrf_name": + assert fv[1] == "Vrf_0" + vrf_found = True + break + assert vrf_found == True # assign IP to interface - self.add_ip_address("PortChannel002", "40.0.0.8/29") + self.add_ip_address("Ethernet8", "10.0.0.4/31") - # configure MTU to interface - self.set_mtu("PortChannel002", "8888") + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet8") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "10.0.0.4/31" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False # check ASIC router interface database tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") @@ -364,103 +546,1420 @@ def test_InterfaceSetMtu(self, dvs, testlog): for fv in fvs: if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" - # assert the new value set to the router interface + # the default MTU without any configuration is 9100 if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": - assert fv[1] == "8888" + assert fv[1] == "9100" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID": + assert fv[1] == vrf_oid - # check ASIC port database - tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") - port_entries = tbl.getKeys() + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "10.0.0.4/31": + subnet_found = True + assert route["vr"] == vrf_oid + if route["dest"] == "10.0.0.4/32": + ip2me_found = True + assert route["vr"] == vrf_oid - for key in port_entries: - (status, fvs) = tbl.get(key) - assert status == True - # a member port configured with MTU will have six field/value tuples - if len(fvs) == 6: - for fv in fvs: - # asser the new value 8888 + 22 = 8910 set to the port - if fv[0] == "SAI_PORT_ATTR_MTU": - assert fv[1] == "8910" + assert subnet_found and ip2me_found # remove IP from interface - self.remove_ip_address("PortChannel002", "40.0.0.8/29") - - # remove port channel members - self.remove_port_channel_members(dvs, "PortChannel002", ["Ethernet0", "Ethernet4"]) - - # remove port channel - self.remove_port_channel(dvs, "PortChannel002") - -class TestLoopbackRouterInterface(object): - def setup_db(self, dvs): - self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) - self.adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) - self.cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) - - def createLoIntf(self, interface, ip): - tbl = swsscommon.Table(self.cdb, "LOOPBACK_INTERFACE") - fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) - tbl.set(interface + "|" + ip, fvs) - time.sleep(1) + self.remove_ip_address("Ethernet8", "10.0.0.4/31") - def removeLoIntf(self, interface, ip): - tbl = swsscommon.Table(self.cdb, "LOOPBACK_INTERFACE") - tbl._del(interface + "|" + ip); - time.sleep(1) + # remove interface + self.remove_l3_intf("Ethernet8") - def test_InterfacesCreateRemove(self, dvs, testlog): - self.setup_db(dvs) + # remove vrf + self.remove_vrf("Vrf_0") - # Create loopback interfaces - self.createLoIntf("Loopback0", "10.1.0.1/32") - self.createLoIntf("Loopback1", "10.1.0.2/32") + # bring down interface + self.set_admin_status(dvs, "Ethernet8", "down") - # Check configuration database - tbl = swsscommon.Table(self.cdb, "LOOPBACK_INTERFACE") + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet8") intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 - assert len(intf_entries) == 2 - assert "Loopback0|10.1.0.1/32" in intf_entries - assert "Loopback1|10.1.0.2/32" in intf_entries - - # Check application database - tbl = swsscommon.Table(self.pdb, "INTF_TABLE:lo") + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Ethernet8" - assert len(intf_entries) == 2 - assert "10.1.0.1/32" in intf_entries - assert "10.1.0.2/32" in intf_entries + tbl = swsscommon.Table(self.pdb, "VRF") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 - # Check ASIC database + # check ASIC database tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") for key in tbl.getKeys(): route = json.loads(key) - if route["dest"] == "10.1.0.1/32": - lo0_ip2me_found = True - if route["dest"] == "10.1.0.2/32": - lo1_ip2me_found = True + if route["dest"] == "10.0.0.4/31": + assert False + if route["dest"] == "10.0.0.4/32": + assert False - assert lo0_ip2me_found and lo1_ip2me_found + def test_PortInterfaceAddSameIpv4AddressWithDiffVrf(self, dvs, testlog): + self.setup_db(dvs) + + for i in [0, 4]: + # record ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + old_intf_entries = set(tbl.getKeys()) + + # record ASIC router entry database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + old_route_entries = set(tbl.getKeys()) - # Remove lopback interfaces - self.removeLoIntf("Loopback0", "10.1.0.1/32") - self.removeLoIntf("Loopback1", "10.1.0.2/32") + intf_name = "Ethernet" + str(i) + vrf_name = "Vrf_" + str(i) - # Check configuration database - tbl = swsscommon.Table(self.cdb, "LOOPBACK_INTERFACE") + # bring up interface + self.set_admin_status(dvs, intf_name, "up") + + # create vrf + vrf_oid = self.create_vrf(vrf_name) + + # create interface with vrf + self.create_l3_intf(intf_name, vrf_name) + + # check interface's vrf + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get(intf_name) + assert status == True + for fv in fvs: + if fv[0] == "vrf_name": + assert fv[1] == vrf_name + vrf_found = True + break + assert vrf_found == True + + # assign IP to interface + self.add_ip_address(intf_name, "10.0.0.4/31") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:" + intf_name) + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "10.0.0.4/31" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + current_intf_entries = set(tbl.getKeys()) + intf_entries = list(current_intf_entries - old_intf_entries) + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a port based router interface has five field/value tuples + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" + # the default MTU without any configuration is 9100 + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": + assert fv[1] == "9100" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID": + assert fv[1] == vrf_oid + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + current_route_entries = set(tbl.getKeys()) + route_entries = list(current_route_entries - old_route_entries) + + for key in route_entries: + route = json.loads(key) + if route["dest"] == "10.0.0.4/31": + subnet_found = True + assert route["vr"] == vrf_oid + if route["dest"] == "10.0.0.4/32": + ip2me_found = True + assert route["vr"] == vrf_oid + + assert subnet_found and ip2me_found + + + for i in [0, 4]: + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + old_intf_entries = set(tbl.getKeys()) + + intf_name = "Ethernet" + str(i) + vrf_name = "Vrf_" + str(i) + + # remove IP from interface + self.remove_ip_address(intf_name, "10.0.0.4/31") + + # remove interface + self.remove_l3_intf(intf_name) + + # remove vrf + self.remove_vrf(vrf_name) + + # bring down interface + self.set_admin_status(dvs, intf_name, "down") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:" + intf_name) + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") intf_entries = tbl.getKeys() - assert len(intf_entries) == 0 + for entry in intf_entries: + assert entry[0] != "Ethernet0" and entry[0] != "Ethernet4" - # Check application database - tbl = swsscommon.Table(self.pdb, "INTF_TABLE:lo") + tbl = swsscommon.Table(self.pdb, "VRF") intf_entries = tbl.getKeys() assert len(intf_entries) == 0 - # Check ASIC database + # check ASIC database tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") for key in tbl.getKeys(): route = json.loads(key) - if route["dest"] == "10.1.0.1/32": + if route["dest"] == "10.0.0.4/31": assert False - if route["dest"] == "10.1.0.2/32": + if route["dest"] == "10.0.0.4/32": + assert False + + def create_port_channel(self, alias): + tbl = swsscommon.Table(self.cdb, "PORTCHANNEL") + fvs = swsscommon.FieldValuePairs([("admin_status", "up"), + ("mtu", "9100")]) + tbl.set(alias, fvs) + time.sleep(1) + + def remove_port_channel(self, alias): + tbl = swsscommon.Table(self.cdb, "PORTCHANNEL") + tbl._del(alias) + time.sleep(1) + + def add_port_channel_members(self, lag, members): + tbl = swsscommon.Table(self.cdb, "PORTCHANNEL_MEMBER") + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + for member in members: + tbl.set(lag + "|" + member, fvs) + time.sleep(1) + + def remove_port_channel_members(self, lag, members): + tbl = swsscommon.Table(self.cdb, "PORTCHANNEL_MEMBER") + for member in members: + tbl._del(lag + "|" + member) + time.sleep(1) + + def test_LagInterfaceAddRemoveIpv6Address(self, dvs, testlog): + self.setup_db(dvs) + + # create port channel + self.create_port_channel("PortChannel001") + + # bring up interface + self.set_admin_status(dvs, "PortChannel001", "up") + + # create l3 interface + self.create_l3_intf("PortChannel001", "") + + # assign IP to interface + self.add_ip_address("PortChannel001", "fc00::1/126") + time.sleep(2) # IPv6 netlink message needs longer time + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "fc00::1/126" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv6" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one port based router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a port based router interface has five field/value tuples + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" + # the default MTU without any configuration is 9100 + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": + assert fv[1] == "9100" + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::/126": + subnet_found = True + if route["dest"] == "fc00::1/128": + ip2me_found = True + + assert subnet_found and ip2me_found + + # remove IP from interface + self.remove_ip_address("PortChannel001", "fc00::1/126") + + # remove interface + self.remove_l3_intf("PortChannel001") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "PortChannel001" + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::/126": + assert False + if route["dest"] == "fc00::1/128": + assert False + + # remove port channel + self.remove_port_channel("PortChannel001") + + def test_LagInterfaceAddRemoveIpv4Address(self, dvs, testlog): + self.setup_db(dvs) + + # create port channel + self.create_port_channel("PortChannel001") + + # bring up interface + self.set_admin_status(dvs, "PortChannel001", "up") + + # create l3 interface + self.create_l3_intf("PortChannel001", "") + + # assign IP to interface + self.add_ip_address("PortChannel001", "30.0.0.4/31") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "30.0.0.4/31" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one port based router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a port based router interface has five field/value tuples + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" + # the default MTU without any configuration is 9100 + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": + assert fv[1] == "9100" + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "30.0.0.4/31": + subnet_found = True + if route["dest"] == "30.0.0.4/32": + ip2me_found = True + + assert subnet_found and ip2me_found + + # remove IP from interface + self.remove_ip_address("PortChannel001", "30.0.0.4/31") + + # remove l3 interface + self.remove_l3_intf("PortChannel001") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "PortChannel001" + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "30.0.0.4/31": + assert False + if route["dest"] == "30.0.0.4/32": + assert False + + # remove port channel + self.remove_port_channel("PortChannel001") + + + def test_LagInterfaceSetMtu(self, dvs, testlog): + self.setup_db(dvs) + + # create port channel + self.create_port_channel("PortChannel002") + + # add port channel members + self.add_port_channel_members("PortChannel002", ["Ethernet0", "Ethernet4"]) + + # create l3 interface + self.create_l3_intf("PortChannel002", "") + + # assign IP to interface + self.add_ip_address("PortChannel002", "40.0.0.8/29") + + # configure MTU to interface + self.set_mtu("PortChannel002", "8888") + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one port based router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a port based router interface has five field/value tuples + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" + # assert the new value set to the router interface + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": + assert fv[1] == "8888" + + # check ASIC port database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") + port_entries = tbl.getKeys() + + for key in port_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a member port configured with MTU will have six field/value tuples + if len(fvs) == 6: + for fv in fvs: + # asser the new value 8888 + 22 = 8910 set to the port + if fv[0] == "SAI_PORT_ATTR_MTU": + assert fv[1] == "8910" + + # remove IP from interface + self.remove_ip_address("PortChannel002", "40.0.0.8/29") + + # remove l3 interface + self.remove_l3_intf("PortChannel002") + + # remove port channel members + self.remove_port_channel_members("PortChannel002", ["Ethernet0", "Ethernet4"]) + + # remove port channel + self.remove_port_channel("PortChannel002") + + def test_LagInterfaceAddRemoveIpv6AddressWithVrf(self, dvs, testlog): + self.setup_db(dvs) + + # create vrf + vrf_oid = self.create_vrf("Vrf_0") + + # create port channel + self.create_port_channel("PortChannel001") + + # bring up interface + self.set_admin_status(dvs, "PortChannel001", "up") + + # create l3 interface + self.create_l3_intf("PortChannel001", "Vrf_0") + + # check interface's vrf + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("PortChannel001") + assert status == True + for fv in fvs: + if fv[0] == "vrf_name": + assert fv[1] == "Vrf_0" + vrf_found = True + break + assert vrf_found == True + + # assign IP to interface + self.add_ip_address("PortChannel001", "fc00::1/126") + time.sleep(2) # IPv6 netlink message needs longer time + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "fc00::1/126" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv6" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one port based router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a port based router interface has five field/value tuples + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" + # the default MTU without any configuration is 9100 + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": + assert fv[1] == "9100" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID": + assert fv[1] == vrf_oid + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::/126": + subnet_found = True + assert route["vr"] == vrf_oid + if route["dest"] == "fc00::1/128": + ip2me_found = True + assert route["vr"] == vrf_oid + + assert subnet_found and ip2me_found + + # remove IP from interface + self.remove_ip_address("PortChannel001", "fc00::1/126") + + # remove interface + self.remove_l3_intf("PortChannel001") + + # remove vrf + self.remove_vrf("Vrf_0") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "PortChannel001" + + tbl = swsscommon.Table(self.pdb, "VRF") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::1/126": + assert False + if route["dest"] == "fc00::1/128": + assert False + + # remove port channel + self.remove_port_channel("PortChannel001") + + + def test_LagInterfaceAddRemoveIpv4AddressWithVrf(self, dvs, testlog): + self.setup_db(dvs) + + # create port channel + self.create_port_channel("PortChannel001") + + # create vrf + vrf_oid = self.create_vrf("Vrf_0") + + # create interface with vrf + self.create_l3_intf("PortChannel001", "Vrf_0") + + # bring up interface + self.set_admin_status(dvs, "PortChannel001", "up") + + # check interface's vrf + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("PortChannel001") + assert status == True + for fv in fvs: + if fv[0] == "vrf_name": + assert fv[1] == "Vrf_0" + vrf_found = True + break + assert vrf_found == True + + # check linux kernel + (exitcode, result) = dvs.runcmd(['sh', '-c', "ip link show PortChannel001 | grep Vrf"]) + assert "Vrf_0" in result + + # assign IP to interface + self.add_ip_address("PortChannel001", "30.0.0.4/31") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "30.0.0.4/31" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one port based router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a port based router interface has five field/value tuples + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" + # the default MTU without any configuration is 9100 + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": + assert fv[1] == "9100" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID": + assert fv[1] == vrf_oid + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "30.0.0.4/31": + subnet_found = True + assert route["vr"] == vrf_oid + if route["dest"] == "30.0.0.4/32": + ip2me_found = True + assert route["vr"] == vrf_oid + + assert subnet_found and ip2me_found + + # remove IP from interface + self.remove_ip_address("PortChannel001", "30.0.0.4/31") + + # remove l3 interface + self.remove_l3_intf("PortChannel001") + + # check linux kernel + (exitcode, result) = dvs.runcmd(['sh', '-c', "ip link show PortChannel001 | grep Vrf"]) + assert "Vrf_0" not in result + + # remove vrf + self.remove_vrf("Vrf_0") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "PortChannel001" + + tbl = swsscommon.Table(self.pdb, "VRF") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "30.0.0.4/31": + assert False + if route["dest"] == "30.0.0.4/32": + assert False + + # remove port channel + self.remove_port_channel("PortChannel001") + + def create_vlan(self, vlan_id): + tbl = swsscommon.Table(self.cdb, "VLAN") + fvs = swsscommon.FieldValuePairs([("vlanid", vlan_id)]) + tbl.set("Vlan" + vlan_id, fvs) + time.sleep(1) + + def remove_vlan(self, vlan_id): + tbl = swsscommon.Table(self.cdb, "VLAN") + tbl._del("Vlan" + vlan_id) + time.sleep(1) + + def create_vlan_member(self, vlan_id, interface): + tbl = swsscommon.Table(self.cdb, "VLAN_MEMBER") + fvs = swsscommon.FieldValuePairs([("tagging_mode", "untagged")]) + tbl.set("Vlan" + vlan_id + "|" + interface, fvs) + time.sleep(1) + + def remove_vlan_member(self, vlan_id, interface): + tbl = swsscommon.Table(self.cdb, "VLAN_MEMBER") + tbl._del("Vlan" + vlan_id + "|" + interface) + time.sleep(1) + + def test_VLanInterfaceAddRemoveIpv6Address(self, dvs, testlog): + self.setup_db(dvs) + + # create vlan + self.create_vlan("10") + + # add vlan member + self.create_vlan_member("10", "Ethernet0") + + # bring up interface + self.set_admin_status(dvs, "Ethernet0", "up") + self.set_admin_status(dvs, "Vlan10", "up") + + # create vlan interface + self.create_l3_intf("Vlan10", "") + + # assign IP to interface + self.add_ip_address("Vlan10", "fc00::1/126") + time.sleep(2) # IPv6 netlink message needs longer time + + # check asic database and get vlan_oid + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + vlan_entries = [k for k in tbl.getKeys() if k != dvs.asicdb.default_vlan_id] + assert len(vlan_entries) == 1 + vlan_oid = vlan_entries[0] + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "fc00::1/126" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv6" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one vlan router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_VLAN" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VLAN_ID": + assert fv[1] == vlan_oid + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::/126": + subnet_found = True + if route["dest"] == "fc00::1/128": + ip2me_found = True + + assert subnet_found and ip2me_found + + # remove IP from interface + self.remove_ip_address("Vlan10", "fc00::1/126") + + # remove interface + self.remove_l3_intf("Vlan10") + + # remove vlan member + self.remove_vlan_member("10", "Ethernet0") + + # remove vlan + self.remove_vlan("10") + + # bring down interface + self.set_admin_status(dvs, "Ethernet0", "down") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Vlan10" + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::/126": + assert False + if route["dest"] == "fc00::1/128": + assert False + + def test_VLanInterfaceAddRemoveIpv4Address(self, dvs, testlog): + self.setup_db(dvs) + + # create vlan + self.create_vlan("10") + + # add vlan member + self.create_vlan_member("10", "Ethernet0") + + # bring up interface + self.set_admin_status(dvs, "Ethernet0", "up") + self.set_admin_status(dvs, "Vlan10", "up") + + #create vlan interface + self.create_l3_intf("Vlan10", "") + + # assign IP to interface + self.add_ip_address("Vlan10", "10.0.0.4/31") + + # check asic database and get vlan_oid + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + vlan_entries = [k for k in tbl.getKeys() if k != dvs.asicdb.default_vlan_id] + assert len(vlan_entries) == 1 + vlan_oid = vlan_entries[0] + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "10.0.0.4/31" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one vlan router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_VLAN" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VLAN_ID": + assert fv[1] == vlan_oid + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "10.0.0.4/31": + subnet_found = True + if route["dest"] == "10.0.0.4/32": + ip2me_found = True + + assert subnet_found and ip2me_found + + # remove IP from interface + self.remove_ip_address("Vlan10", "10.0.0.4/31") + + # remove vlan interface + self.remove_l3_intf("Vlan10") + + # remove vlan member + self.remove_vlan_member("10", "Ethernet0") + + # remove vlan + self.remove_vlan("10") + + # bring down interface + self.set_admin_status(dvs, "Ethernet0", "down") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "VLAN_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Vlan10" + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "10.0.0.4/31": + assert False + if route["dest"] == "10.0.0.4/32": + assert False + + def test_VLanInterfaceAddRemoveIpv6AddressWithVrf(self, dvs, testlog): + self.setup_db(dvs) + + # create vlan + self.create_vlan("10") + + # add vlan member + self.create_vlan_member("10", "Ethernet0") + + # bring up interface + self.set_admin_status(dvs, "Ethernet0", "up") + self.set_admin_status(dvs, "Vlan10", "up") + + # create vrf + vrf_oid = self.create_vrf("Vrf_0") + + # create vlan interface + self.create_l3_intf("Vlan10", "Vrf_0") + + # check linux kernel + (exitcode, result) = dvs.runcmd(['sh', '-c', "ip link show Vlan10 | grep Vrf"]) + assert "Vrf_0" in result + + # assign IP to interface + self.add_ip_address("Vlan10", "fc00::1/126") + time.sleep(2) # IPv6 netlink message needs longer time + + # check interface's vrf + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("Vlan10") + assert status == True + for fv in fvs: + if fv[0] == "vrf_name": + assert fv[1] == "Vrf_0" + vrf_found = True + break + assert vrf_found == True + + # check asic database and get vlan_oid + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + vlan_entries = [k for k in tbl.getKeys() if k != dvs.asicdb.default_vlan_id] + assert len(vlan_entries) == 1 + vlan_oid = vlan_entries[0] + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "fc00::1/126" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv6" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one vlan router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_VLAN" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VLAN_ID": + assert fv[1] == vlan_oid + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID": + assert fv[1] == vrf_oid + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::/126": + subnet_found = True + assert route["vr"] == vrf_oid + if route["dest"] == "fc00::1/128": + ip2me_found = True + assert route["vr"] == vrf_oid + + assert subnet_found and ip2me_found + + # remove IP from interface + self.remove_ip_address("Vlan10", "fc00::1/126") + + # remove vlan interface + self.remove_l3_intf("Vlan10") + + # check linux kernel + (exitcode, result) = dvs.runcmd(['sh', '-c', "ip link show Vlan10 | grep Vrf"]) + assert "Vrf_0" not in result + + # remove vlan member + self.remove_vlan_member("10", "Ethernet0") + + # remove vlan + self.remove_vlan("10") + + # bring down interface + self.set_admin_status(dvs, "Ethernet0", "down") + + # remove vrf + self.remove_vrf("Vrf_0") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Vlan10" + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::/126": + assert False + if route["dest"] == "fc00::1/128": + assert False + + def test_VLanInterfaceAddRemoveIpv4AddressWithVrf(self, dvs, testlog): + self.setup_db(dvs) + + # create vlan + self.create_vlan("10") + + # add vlan member + self.create_vlan_member("10", "Ethernet0") + + # bring up interface + self.set_admin_status(dvs, "Ethernet0", "up") + self.set_admin_status(dvs, "Vlan10", "up") + + # create vrf + vrf_oid = self.create_vrf("Vrf_0") + + # create vlan interface + self.create_l3_intf("Vlan10", "Vrf_0") + + # assign IP to interface + self.add_ip_address("Vlan10", "10.0.0.4/31") + + # check interface's vrf + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("Vlan10") + assert status == True + for fv in fvs: + if fv[0] == "vrf_name": + assert fv[1] == "Vrf_0" + vrf_found = True + break + assert vrf_found == True + + # check asic database and get vlan_oid + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + vlan_entries = [k for k in tbl.getKeys() if k != dvs.asicdb.default_vlan_id] + assert len(vlan_entries) == 1 + vlan_oid = vlan_entries[0] + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "10.0.0.4/31" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one vlan router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_VLAN" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VLAN_ID": + assert fv[1] == vlan_oid + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID": + assert fv[1] == vrf_oid + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "10.0.0.4/31": + subnet_found = True + assert route["vr"] == vrf_oid + if route["dest"] == "10.0.0.4/32": + ip2me_found = True + assert route["vr"] == vrf_oid + + assert subnet_found and ip2me_found + + # remove IP from interface + self.remove_ip_address("Vlan10", "10.0.0.4/31") + + # remove vlan interface + self.remove_l3_intf("Vlan10") + + # remove vlan member + self.remove_vlan_member("10", "Ethernet0") + + # remove vlan + self.remove_vlan("10") + + # bring down interface + self.set_admin_status(dvs, "Ethernet0", "down") + + # remove vrf + self.remove_vrf("Vrf_0") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "VLAN_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Vlan10" + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "10.0.0.4/31": + assert False + if route["dest"] == "10.0.0.4/32": + assert False + + def test_LoopbackInterfacesAddRemoveIpv4Address(self, dvs, testlog): + self.setup_db(dvs) + + # Create loopback interfaces + self.create_l3_intf("Loopback0", "") + self.create_l3_intf("Loopback1", "") + + # add ip address + self.add_ip_address("Loopback0", "10.1.0.1/32") + self.add_ip_address("Loopback1", "10.1.0.2/32") + + # Check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Loopback0") + intf_entries = tbl.getKeys() + assert intf_entries[0] == "10.1.0.1/32" + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Loopback1") + intf_entries = tbl.getKeys() + assert intf_entries[0] == "10.1.0.2/32" + + # Check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "10.1.0.1/32": + lo0_ip2me_found = True + if route["dest"] == "10.1.0.2/32": + lo1_ip2me_found = True + + assert lo0_ip2me_found and lo1_ip2me_found + + # Remove ip address + self.remove_ip_address("Loopback0", "10.1.0.1/32") + self.remove_ip_address("Loopback1", "10.1.0.2/32") + + # Remove interface + self.remove_l3_intf("Loopback0") + self.remove_l3_intf("Loopback1") + + # Check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Loopback0") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Loopback1") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # Check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "10.1.0.1/32": + assert False + if route["dest"] == "10.1.0.2/32": + assert False + + def test_LoopbackInterfacesAddRemoveIpv6Address(self, dvs, testlog): + self.setup_db(dvs) + + # Create loopback interfaces + self.create_l3_intf("Loopback0", "") + self.create_l3_intf("Loopback1", "") + + # add ip address + self.add_ip_address("Loopback0", "fc00::1/128") + self.add_ip_address("Loopback1", "fd00::1/128") + time.sleep(2) # IPv6 netlink message needs longer time + + # Check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Loopback0") + intf_entries = tbl.getKeys() + assert intf_entries[0] == "fc00::1/128" + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Loopback1") + intf_entries = tbl.getKeys() + assert intf_entries[0] == "fd00::1/128" + + # Check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::1/128": + lo0_ip2me_found = True + if route["dest"] == "fd00::1/128": + lo1_ip2me_found = True + + assert lo0_ip2me_found and lo1_ip2me_found + + # Remove ip address + self.remove_ip_address("Loopback0", "fc00::1/128") + self.remove_ip_address("Loopback1", "fd00::1/128") + + # Remove interface + self.remove_l3_intf("Loopback0") + self.remove_l3_intf("Loopback1") + + # Check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Loopback0") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Loopback1") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # Check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::1/128": + assert False + if route["dest"] == "fd00::1/128": + assert False + + def test_LoopbackInterfaceIpv4AddressWithVrf(self, dvs, testlog): + self.setup_db(dvs) + + for i in [0, 1]: + # record ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + old_intf_entries = set(tbl.getKeys()) + + # record ASIC router entry database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + old_route_entries = set(tbl.getKeys()) + + intf_name = "Loopback" + str(i) + vrf_name = "Vrf_" + str(i) + + # create vrf + vrf_oid = self.create_vrf(vrf_name) + + # create interface with vrf + self.create_l3_intf(intf_name, vrf_name) + + # check linux kernel + (exitcode, result) = dvs.runcmd(['sh', '-c', "ip link show %s | grep Vrf" % intf_name]) + assert "%s" % vrf_name in result + + # check interface's vrf + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get(intf_name) + assert status == True + for fv in fvs: + if fv[0] == "vrf_name": + assert fv[1] == vrf_name + vrf_found = True + break + assert vrf_found == True + + # assign IP to interface + self.add_ip_address(intf_name, "10.0.0.4/32") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:" + intf_name) + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "10.0.0.4/32" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + current_intf_entries = set(tbl.getKeys()) + intf_entries = list(current_intf_entries - old_intf_entries) + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a port based router interface has five field/value tuples + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" + # the default MTU without any configuration is 9100 + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": + assert fv[1] == "9100" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID": + assert fv[1] == vrf_oid + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + current_route_entries = set(tbl.getKeys()) + route_entries = list(current_route_entries - old_route_entries) + + for key in route_entries: + route = json.loads(key) + if route["dest"] == "10.0.0.4/32": + ip2me_found = True + assert route["vr"] == vrf_oid + + assert ip2me_found + + + for i in [0, 1]: + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + old_intf_entries = set(tbl.getKeys()) + + intf_name = "Loopback" + str(i) + vrf_name = "Vrf_" + str(i) + + # remove IP from interface + self.remove_ip_address(intf_name, "10.0.0.4/32") + + # remove interface + self.remove_l3_intf(intf_name) + + # remove vrf + self.remove_vrf(vrf_name) + + # check linux kernel + (exitcode, result) = dvs.runcmd(['sh', '-c', "ip link show %s | grep Vrf" % intf_name]) + assert "%s" % vrf_name not in result + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:" + intf_name) + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Loopback0" and entry[0] != "Loopback1" + + tbl = swsscommon.Table(self.pdb, "VRF") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "10.0.0.4/32": assert False diff --git a/tests/test_neighbor.py b/tests/test_neighbor.py new file mode 100644 index 0000000000..4cf2196334 --- /dev/null +++ b/tests/test_neighbor.py @@ -0,0 +1,390 @@ +from swsscommon import swsscommon + +import time +import json + +class TestNeighbor(object): + def setup_db(self, dvs): + self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) + self.adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) + self.cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) + + def set_admin_status(self, interface, status): + tbl = swsscommon.Table(self.cdb, "PORT") + fvs = swsscommon.FieldValuePairs([("admin_status", status)]) + tbl.set(interface, fvs) + time.sleep(1) + + def create_vrf(self, vrf_name): + tbl = swsscommon.Table(self.cdb, "VRF") + fvs = swsscommon.FieldValuePairs([('empty', 'empty')]) + tbl.set(vrf_name, fvs) + time.sleep(1) + + def remove_vrf(self, vrf_name): + tbl = swsscommon.Table(self.cdb, "VRF") + tbl._del(vrf_name) + time.sleep(1) + + def create_l3_intf(self, interface, vrf_name): + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + initial_entries = set(tbl.getKeys()) + + tbl = swsscommon.Table(self.cdb, "INTERFACE") + if len(vrf_name) == 0: + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + else: + fvs = swsscommon.FieldValuePairs([("vrf_name", vrf_name)]) + tbl.set(interface, fvs) + time.sleep(1) + + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + current_entries = set(tbl.getKeys()) + assert len(current_entries - initial_entries) == 1 + return list(current_entries - initial_entries)[0] + + def remove_l3_intf(self, interface): + tbl = swsscommon.Table(self.cdb, "INTERFACE") + tbl._del(interface) + time.sleep(1) + + def add_ip_address(self, interface, ip): + tbl = swsscommon.Table(self.cdb, "INTERFACE") + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + tbl.set(interface + "|" + ip, fvs) + time.sleep(2) # IPv6 netlink message needs longer time + + def remove_ip_address(self, interface, ip): + tbl = swsscommon.Table(self.cdb, "INTERFACE") + tbl._del(interface + "|" + ip) + time.sleep(1) + + def add_neighbor(self, interface, ip, mac): + tbl = swsscommon.Table(self.cdb, "NEIGH") + fvs = swsscommon.FieldValuePairs([("neigh", mac)]) + tbl.set(interface + "|" + ip, fvs) + time.sleep(1) + + def remove_neighbor(self, interface, ip): + tbl = swsscommon.Table(self.cdb, "NEIGH") + tbl._del(interface + "|" + ip) + time.sleep(1) + + def test_NeighborAddRemoveIpv6(self, dvs, testlog): + self.setup_db(dvs) + + # bring up interface + # NOTE: For IPv6, only when the interface is up will the netlink message + # get generated. + self.set_admin_status("Ethernet8", "up") + + # create interface and get rif_oid + rif_oid = self.create_l3_intf("Ethernet8", "") + + # assign IP to interface + self.add_ip_address("Ethernet8", "2000::1/64") + + # add neighbor + self.add_neighbor("Ethernet8", "2000::2", "00:01:02:03:04:05") + + # check application database + tbl = swsscommon.Table(self.pdb, "NEIGH_TABLE:Ethernet8") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "2000::2" + (status, fvs) = tbl.get(intf_entries[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "neigh": + assert fv[1] == "00:01:02:03:04:05" + elif fv[0] == "family": + assert fv[1] == "IPv6" + else: + assert False + + # check ASIC neighbor database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + route = json.loads(intf_entries[0]) + assert route["ip"] == "2000::2" + assert route["rif"] == rif_oid + + (status, fvs) = tbl.get(intf_entries[0]) + assert status == True + for fv in fvs: + if fv[0] == "SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS": + assert fv[1] == "00:01:02:03:04:05" + + # remove neighbor + self.remove_neighbor("Ethernet8", "2000::2") + + # remove IP from interface + self.remove_ip_address("Ethernet8", "2000::1/64") + + # remove interface + self.remove_l3_intf("Ethernet8") + + # bring down interface + self.set_admin_status("Ethernet8", "down") + + # check application database + tbl = swsscommon.Table(self.pdb, "NEIGH_TABLE:Ethernet8") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # check ASIC neighbor database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + + def test_NeighborAddRemoveIpv4(self, dvs, testlog): + self.setup_db(dvs) + + # bring up interface + self.set_admin_status("Ethernet8", "up") + + # create interface and get rif_oid + rif_oid = self.create_l3_intf("Ethernet8", "") + + # assign IP to interface + self.add_ip_address("Ethernet8", "10.0.0.1/24") + + # add neighbor + self.add_neighbor("Ethernet8", "10.0.0.2", "00:01:02:03:04:05") + + # check application database + tbl = swsscommon.Table(self.pdb, "NEIGH_TABLE:Ethernet8") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "10.0.0.2" + (status, fvs) = tbl.get(intf_entries[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "neigh": + assert fv[1] == "00:01:02:03:04:05" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False + + # check ASIC neighbor database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + route = json.loads(intf_entries[0]) + assert route["ip"] == "10.0.0.2" + assert route["rif"] == rif_oid + + (status, fvs) = tbl.get(intf_entries[0]) + assert status == True + for fv in fvs: + if fv[0] == "SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS": + assert fv[1] == "00:01:02:03:04:05" + + # remove neighbor + self.remove_neighbor("Ethernet8", "10.0.0.2") + + # remove IP from interface + self.remove_ip_address("Ethernet8", "10.0.0.1/24") + + # remove interface + self.remove_l3_intf("Ethernet8") + + # bring down interface + self.set_admin_status("Ethernet8", "down") + + # check application database + tbl = swsscommon.Table(self.pdb, "NEIGH_TABLE:Ethernet8") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # check ASIC neighbor database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + def test_NeighborAddRemoveIpv6WithVrf(self, dvs, testlog): + self.setup_db(dvs) + + for i in [0, 4]: + # record ASIC neighbor database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + old_neigh_entries = set(tbl.getKeys()) + + intf_name = "Ethernet" + str(i) + vrf_name = "Vrf_" + str(i) + + # bring up interface + self.set_admin_status(intf_name, "up") + + # create vrf + self.create_vrf(vrf_name) + + # create interface and get rif_oid + rif_oid = self.create_l3_intf(intf_name, vrf_name) + + # assign IP to interface + self.add_ip_address(intf_name, "2000::1/64") + + # add neighbor + self.add_neighbor(intf_name, "2000::2", "00:01:02:03:04:05") + + # check application database + tbl = swsscommon.Table(self.pdb, "NEIGH_TABLE:" + intf_name) + neigh_entries = tbl.getKeys() + assert len(neigh_entries) == 1 + assert neigh_entries[0] == "2000::2" + (status, fvs) = tbl.get(neigh_entries[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "neigh": + assert fv[1] == "00:01:02:03:04:05" + elif fv[0] == "family": + assert fv[1] == "IPv6" + else: + assert False + + # check ASIC neighbor interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + current_neigh_entries = set(tbl.getKeys()) + neigh_entries = list(current_neigh_entries - old_neigh_entries) + assert len(neigh_entries) == 1 + route = json.loads(neigh_entries[0]) + assert route["ip"] == "2000::2" + assert route["rif"] == rif_oid + + (status, fvs) = tbl.get(neigh_entries[0]) + assert status == True + for fv in fvs: + if fv[0] == "SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS": + assert fv[1] == "00:01:02:03:04:05" + + for i in [0, 4]: + # record ASIC neighbor database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + old_neigh_entries_cnt = len(tbl.getKeys()) + + intf_name = "Ethernet" + str(i) + vrf_name = "Vrf_" + str(i) + + # remove neighbor + self.remove_neighbor(intf_name, "2000::2") + + # remove IP from interface + self.remove_ip_address(intf_name, "2000::1/64") + + # remove interface + self.remove_l3_intf(intf_name) + + # remove vrf + self.remove_vrf(vrf_name) + + # bring down interface + self.set_admin_status(intf_name, "down") + + # check application database + tbl = swsscommon.Table(self.pdb, "NEIGH_TABLE:" + intf_name) + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # check ASIC neighbor interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + current_neigh_entries_cnt = len(tbl.getKeys()) + dec_neigh_entries_cnt = (old_neigh_entries_cnt - current_neigh_entries_cnt) + assert dec_neigh_entries_cnt == 1 + + def test_NeighborAddRemoveIpv4WithVrf(self, dvs, testlog): + self.setup_db(dvs) + + for i in [0, 4]: + # record ASIC neighbor database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + old_neigh_entries = set(tbl.getKeys()) + + intf_name = "Ethernet" + str(i) + vrf_name = "Vrf_" + str(i) + + # bring up interface + self.set_admin_status(intf_name, "up") + + # create vrf + self.create_vrf(vrf_name) + + # create interface and get rif_oid + rif_oid = self.create_l3_intf(intf_name, vrf_name) + + # assign IP to interface + self.add_ip_address(intf_name, "10.0.0.1/24") + + # add neighbor + self.add_neighbor(intf_name, "10.0.0.2", "00:01:02:03:04:05") + + # check application database + tbl = swsscommon.Table(self.pdb, "NEIGH_TABLE:" + intf_name) + neigh_entries = tbl.getKeys() + assert len(neigh_entries) == 1 + assert neigh_entries[0] == "10.0.0.2" + (status, fvs) = tbl.get(neigh_entries[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "neigh": + assert fv[1] == "00:01:02:03:04:05" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False + + # check ASIC neighbor interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + current_neigh_entries = set(tbl.getKeys()) + neigh_entries = list(current_neigh_entries - old_neigh_entries) + assert len(neigh_entries) == 1 + route = json.loads(neigh_entries[0]) + assert route["ip"] == "10.0.0.2" + assert route["rif"] == rif_oid + + (status, fvs) = tbl.get(neigh_entries[0]) + assert status == True + for fv in fvs: + if fv[0] == "SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS": + assert fv[1] == "00:01:02:03:04:05" + + for i in [0, 4]: + # record ASIC neighbor database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + old_neigh_entries_cnt = len(tbl.getKeys()) + + intf_name = "Ethernet" + str(i) + vrf_name = "Vrf_" + str(i) + + # remove neighbor + self.remove_neighbor(intf_name, "10.0.0.2") + + # remove IP from interface + self.remove_ip_address(intf_name, "10.0.0.1/24") + + # remove interface + self.remove_l3_intf(intf_name) + + # remove vrf + self.remove_vrf(vrf_name) + + # bring down interface + self.set_admin_status(intf_name, "down") + + # check application database + tbl = swsscommon.Table(self.pdb, "NEIGH_TABLE:" + intf_name) + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # check ASIC neighbor interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + current_neigh_entries_cnt = len(tbl.getKeys()) + dec_neigh_entries_cnt = (old_neigh_entries_cnt - current_neigh_entries_cnt) + assert dec_neigh_entries_cnt == 1 \ No newline at end of file diff --git a/tests/test_route.py b/tests/test_route.py index 12c30d99d7..e1c12857e3 100644 --- a/tests/test_route.py +++ b/tests/test_route.py @@ -5,49 +5,616 @@ import json class TestRoute(object): - def test_RouteAdd(self, dvs, testlog): - - config_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) - intf_tbl = swsscommon.Table(config_db, "INTERFACE") - fvs = swsscommon.FieldValuePairs([("NULL","NULL")]) - intf_tbl.set("Ethernet0|10.0.0.0/31", fvs) - intf_tbl.set("Ethernet4|10.0.0.2/31", fvs) - intf_tbl.set("Ethernet0", fvs) - intf_tbl.set("Ethernet4", fvs) - dvs.runcmd("ifconfig Ethernet0 up") - dvs.runcmd("ifconfig Ethernet4 up") - - dvs.servers[0].runcmd("ifconfig eth0 10.0.0.1/31") + def setup_db(self, dvs): + self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) + self.adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) + self.cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) + + def set_admin_status(self, interface, status): + tbl = swsscommon.Table(self.cdb, "PORT") + fvs = swsscommon.FieldValuePairs([("admin_status", status)]) + tbl.set(interface, fvs) + time.sleep(1) + + def create_vrf(self, vrf_name): + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") + initial_entries = set(tbl.getKeys()) + + tbl = swsscommon.Table(self.cdb, "VRF") + fvs = swsscommon.FieldValuePairs([('empty', 'empty')]) + tbl.set(vrf_name, fvs) + time.sleep(1) + + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") + current_entries = set(tbl.getKeys()) + assert len(current_entries - initial_entries) == 1 + return list(current_entries - initial_entries)[0] + + def remove_vrf(self, vrf_name): + tbl = swsscommon.Table(self.cdb, "VRF") + tbl._del(vrf_name) + time.sleep(1) + + def create_l3_intf(self, interface, vrf_name): + tbl = swsscommon.Table(self.cdb, "INTERFACE") + if len(vrf_name) == 0: + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + else: + fvs = swsscommon.FieldValuePairs([("vrf_name", vrf_name)]) + tbl.set(interface, fvs) + time.sleep(1) + + def remove_l3_intf(self, interface): + tbl = swsscommon.Table(self.cdb, "INTERFACE") + tbl._del(interface) + time.sleep(1) + + def add_ip_address(self, interface, ip): + tbl = swsscommon.Table(self.cdb, "INTERFACE") + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + tbl.set(interface + "|" + ip, fvs) + time.sleep(1) + + def remove_ip_address(self, interface, ip): + tbl = swsscommon.Table(self.cdb, "INTERFACE") + tbl._del(interface + "|" + ip) + time.sleep(1) + + def create_route_entry(self, key, pairs): + tbl = swsscommon.ProducerStateTable(self.pdb, "ROUTE_TABLE") + fvs = swsscommon.FieldValuePairs(pairs) + tbl.set(key, fvs) + time.sleep(1) + + def remove_route_entry(self, key): + tbl = swsscommon.ProducerStateTable(self.pdb, "ROUTE_TABLE") + tbl._del(key) + time.sleep(1) + + def clear_srv_config(self, dvs): + dvs.servers[0].runcmd("ip address flush dev eth0") + dvs.servers[1].runcmd("ip address flush dev eth0") + dvs.servers[2].runcmd("ip address flush dev eth0") + dvs.servers[3].runcmd("ip address flush dev eth0") + + def test_RouteAddRemoveIpv4Route(self, dvs, testlog): + self.setup_db(dvs) + + self.clear_srv_config(dvs) + + # create l3 interface + self.create_l3_intf("Ethernet0", "") + self.create_l3_intf("Ethernet4", "") + + # set ip address + self.add_ip_address("Ethernet0", "10.0.0.0/31") + self.add_ip_address("Ethernet4", "10.0.0.2/31") + + # bring up interface + self.set_admin_status("Ethernet0", "up") + self.set_admin_status("Ethernet4", "up") + + # set ip address and default route + dvs.servers[0].runcmd("ip address add 10.0.0.1/31 dev eth0") dvs.servers[0].runcmd("ip route add default via 10.0.0.0") - dvs.servers[1].runcmd("ifconfig eth0 10.0.0.3/31") + dvs.servers[1].runcmd("ip address add 10.0.0.3/31 dev eth0") dvs.servers[1].runcmd("ip route add default via 10.0.0.2") # get neighbor and arp entry dvs.servers[0].runcmd("ping -c 1 10.0.0.3") - db = swsscommon.DBConnector(0, dvs.redis_sock, 0) - ps = swsscommon.ProducerStateTable(db, "ROUTE_TABLE") - fvs = swsscommon.FieldValuePairs([("nexthop","10.0.0.1"), ("ifname", "Ethernet0")]) + # add route entry + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 2.2.2.0/24 10.0.0.1\"") + time.sleep(1) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE") + route_entries = tbl.getKeys() + assert "2.2.2.0/24" in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "2.2.2.0/24": + route_found = True + assert route_found == True + + # remove route entry + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ip route 2.2.2.0/24 10.0.0.1\"") + time.sleep(1) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE") + route_entries = tbl.getKeys() + assert "2.2.2.0/24" not in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + assert route["dest"] != "2.2.2.0/24" + + # remove ip address + self.remove_ip_address("Ethernet0", "10.0.0.0/31") + self.remove_ip_address("Ethernet4", "10.0.0.2/31") + + # remove l3 interface + self.remove_l3_intf("Ethernet0") + self.remove_l3_intf("Ethernet4") + + self.set_admin_status("Ethernet0", "down") + self.set_admin_status("Ethernet4", "down") + + # remove ip address and default route + dvs.servers[0].runcmd("ip route del default dev eth0") + dvs.servers[0].runcmd("ip address del 10.0.0.1/31 dev eth0") + + dvs.servers[1].runcmd("ip route del default dev eth0") + dvs.servers[1].runcmd("ip address del 10.0.0.3/31 dev eth0") + + def test_RouteAddRemoveIpv6Route(self, dvs, testlog): + self.setup_db(dvs) - pubsub = dvs.SubscribeAsicDbObject("SAI_OBJECT_TYPE_ROUTE_ENTRY") + # create l3 interface + self.create_l3_intf("Ethernet0", "") + self.create_l3_intf("Ethernet4", "") - ps.set("2.2.2.0/24", fvs) + # bring up interface + self.set_admin_status("Ethernet0", "up") + self.set_admin_status("Ethernet4", "up") - # check if route was propagated to ASIC DB + # set ip address + self.add_ip_address("Ethernet0", "2000::1/64") + self.add_ip_address("Ethernet4", "2001::1/64") + dvs.runcmd("sysctl -w net.ipv6.conf.all.forwarding=1") - (addobjs, delobjs) = dvs.GetSubscribedAsicDbObjects(pubsub) + # set ip address and default route + dvs.servers[0].runcmd("ip -6 address add 2000::2/64 dev eth0") + dvs.servers[0].runcmd("ip -6 route add default via 2000::1") - assert len(addobjs) >= 1 + dvs.servers[1].runcmd("ip -6 address add 2001::2/64 dev eth0") + dvs.servers[1].runcmd("ip -6 route add default via 2001::1") + time.sleep(2) - adb = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) - atbl = swsscommon.Table(adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") - keys = atbl.getKeys() - found = False + # get neighbor entry + dvs.servers[0].runcmd("ping -6 -c 1 2001::2") - for key in keys: + # add route entry + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ipv6 route 3000::0/64 2000::2\"") + time.sleep(2) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE") + route_entries = tbl.getKeys() + assert "3000::/64" in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "3000::/64": + route_found = True + assert route_found == True + + # remove route entry + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ipv6 route 3000::0/64 2000::2\"") + time.sleep(1) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE") + route_entries = tbl.getKeys() + assert "3000::/64" not in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): route = json.loads(key) - if route['dest'] == "2.2.2.0/24": - found = True + assert route["dest"] != "3000::/64" + + # remove ip address + self.remove_ip_address("Ethernet0", "2000::1/64") + self.remove_ip_address("Ethernet4", "2001::1/64") + + # remove l3 interface + self.remove_l3_intf("Ethernet0") + self.remove_l3_intf("Ethernet4") + + self.set_admin_status("Ethernet0", "down") + self.set_admin_status("Ethernet4", "down") + + # remove ip address and default route + dvs.servers[0].runcmd("ip -6 route del default dev eth0") + dvs.servers[0].runcmd("ip -6 address del 2000::2/64 dev eth0") + + dvs.servers[1].runcmd("ip -6 route del default dev eth0") + dvs.servers[1].runcmd("ip -6 address del 2001::2/64 dev eth0") + + def test_RouteAddRemoveIpv4RouteWithVrf(self, dvs, testlog): + self.setup_db(dvs) + + # create vrf + vrf_1_oid = self.create_vrf("Vrf_1") + vrf_2_oid = self.create_vrf("Vrf_2") + + # create l3 interface + self.create_l3_intf("Ethernet0", "Vrf_1") + self.create_l3_intf("Ethernet4", "Vrf_1") + self.create_l3_intf("Ethernet8", "Vrf_2") + self.create_l3_intf("Ethernet12", "Vrf_2") + + # set ip address + self.add_ip_address("Ethernet0", "10.0.0.0/31") + self.add_ip_address("Ethernet4", "10.0.0.2/31") + self.add_ip_address("Ethernet8", "10.0.0.0/31") + self.add_ip_address("Ethernet12", "10.0.0.2/31") + + # bring up interface + self.set_admin_status("Ethernet0", "up") + self.set_admin_status("Ethernet4", "up") + self.set_admin_status("Ethernet8", "up") + self.set_admin_status("Ethernet12", "up") + + # set ip address and default route + dvs.servers[0].runcmd("ip address add 10.0.0.1/31 dev eth0") + dvs.servers[0].runcmd("ip route add default via 10.0.0.0") + + dvs.servers[1].runcmd("ip address add 10.0.0.3/31 dev eth0") + dvs.servers[1].runcmd("ip route add default via 10.0.0.2") + + dvs.servers[2].runcmd("ip address add 10.0.0.1/31 dev eth0") + dvs.servers[2].runcmd("ip route add default via 10.0.0.0") + + dvs.servers[3].runcmd("ip address add 10.0.0.3/31 dev eth0") + dvs.servers[3].runcmd("ip route add default via 10.0.0.2") + + time.sleep(1) + + # get neighbor entry + dvs.servers[0].runcmd("ping -c 1 10.0.0.3") + dvs.servers[2].runcmd("ping -c 1 10.0.0.3") + + # add route + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 2.2.2.0/24 10.0.0.1 vrf Vrf_1\"") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 3.3.3.0/24 10.0.0.1 vrf Vrf_2\"") + time.sleep(1) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_1") + route_entries = tbl.getKeys() + assert "2.2.2.0/24" in route_entries + + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_2") + route_entries = tbl.getKeys() + assert "3.3.3.0/24" in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "2.2.2.0/24" and route["vr"] == vrf_1_oid: + route_Vrf_1_found = True + if route["dest"] == "3.3.3.0/24" and route["vr"] == vrf_2_oid: + route_Vrf_2_found = True + assert route_Vrf_1_found == True and route_Vrf_2_found == True + + # remove route + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ip route 2.2.2.0/24 10.0.0.1 vrf Vrf_1\"") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ip route 3.3.3.0/24 10.0.0.1 vrf Vrf_2\"") + time.sleep(1) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_1") + route_entries = tbl.getKeys() + assert "2.2.2.0/24" not in route_entries + + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_2") + route_entries = tbl.getKeys() + assert "3.3.3.0/24" not in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + assert route["dest"] != "2.2.2.0/24" and route["dest"] != "3.3.3.0/24" + + # remove ip address + self.remove_ip_address("Ethernet0", "10.0.0.0/31") + self.remove_ip_address("Ethernet4", "10.0.0.2/31") + self.remove_ip_address("Ethernet8", "10.0.0.0/31") + self.remove_ip_address("Ethernet12", "10.0.0.2/31") + + # remove l3 interface + self.remove_l3_intf("Ethernet0") + self.remove_l3_intf("Ethernet4") + self.remove_l3_intf("Ethernet8") + self.remove_l3_intf("Ethernet12") + + self.set_admin_status("Ethernet0", "down") + self.set_admin_status("Ethernet4", "down") + self.set_admin_status("Ethernet8", "down") + self.set_admin_status("Ethernet12", "down") + + # remove vrf + self.remove_vrf("Vrf_1") + self.remove_vrf("Vrf_2") + + # remove ip address and default route + dvs.servers[0].runcmd("ip route del default dev eth0") + dvs.servers[0].runcmd("ip address del 10.0.0.1/31 dev eth0") + dvs.servers[1].runcmd("ip route del default dev eth0") + dvs.servers[1].runcmd("ip address del 10.0.0.3/31 dev eth0") + dvs.servers[2].runcmd("ip route del default dev eth0") + dvs.servers[2].runcmd("ip address del 10.0.0.1/31 dev eth0") + dvs.servers[3].runcmd("ip route del default dev eth0") + dvs.servers[3].runcmd("ip address del 10.0.0.3/31 dev eth0") + + def test_RouteAddRemoveIpv6RouteWithVrf(self, dvs, testlog): + self.setup_db(dvs) + + # create vrf + vrf_1_oid = self.create_vrf("Vrf_1") + vrf_2_oid = self.create_vrf("Vrf_2") + + # create l3 interface + self.create_l3_intf("Ethernet0", "Vrf_1") + self.create_l3_intf("Ethernet4", "Vrf_1") + self.create_l3_intf("Ethernet8", "Vrf_2") + self.create_l3_intf("Ethernet12", "Vrf_2") + + # bring up interface + self.set_admin_status("Ethernet0", "up") + self.set_admin_status("Ethernet4", "up") + self.set_admin_status("Ethernet8", "up") + self.set_admin_status("Ethernet12", "up") + + # set ip address + self.add_ip_address("Ethernet0", "2000::1/64") + self.add_ip_address("Ethernet4", "2001::1/64") + self.add_ip_address("Ethernet8", "2000::1/64") + self.add_ip_address("Ethernet12", "2001::1/64") + + dvs.runcmd("sysctl -w net.ipv6.conf.all.forwarding=1") + + # set ip address and default route + dvs.servers[0].runcmd("ip -6 address add 2000::2/64 dev eth0") + dvs.servers[0].runcmd("ip -6 route add default via 2000::1") + dvs.servers[1].runcmd("ip -6 address add 2001::2/64 dev eth0") + dvs.servers[1].runcmd("ip -6 route add default via 2001::1") + dvs.servers[2].runcmd("ip -6 address add 2000::2/64 dev eth0") + dvs.servers[2].runcmd("ip -6 route add default via 2000::1") + dvs.servers[3].runcmd("ip -6 address add 2001::2/64 dev eth0") + dvs.servers[3].runcmd("ip -6 route add default via 2001::1") + time.sleep(2) + + # get neighbor entry + dvs.servers[0].runcmd("ping -6 -c 1 2001::2") + dvs.servers[2].runcmd("ping -6 -c 1 2001::2") + time.sleep(2) + + # add route + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ipv6 route 3000::0/64 2000::2 vrf Vrf_1\"") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ipv6 route 4000::0/64 2000::2 vrf Vrf_2\"") + time.sleep(2) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_1") + route_entries = tbl.getKeys() + assert "3000::/64" in route_entries + + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_2") + route_entries = tbl.getKeys() + assert "4000::/64" in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "3000::/64" and route["vr"] == vrf_1_oid: + route_Vrf_1_found = True + if route["dest"] == "4000::/64" and route["vr"] == vrf_2_oid: + route_Vrf_2_found = True + assert route_Vrf_1_found == True and route_Vrf_2_found == True + + + # remove route + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ipv6 route 3000::0/64 2000::2 vrf Vrf_1\"") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ipv6 route 4000::0/64 2000::2 vrf Vrf_2\"") + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_1") + route_entries = tbl.getKeys() + assert "3000::/64" not in route_entries + + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_2") + route_entries = tbl.getKeys() + assert "4000::/64" not in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + assert route["dest"] != "3000::/64" and route["dest"] != "4000::/64" + + # remove ip address + self.remove_ip_address("Ethernet0", "2000::1/64") + self.remove_ip_address("Ethernet4", "2001::1/64") + self.remove_ip_address("Ethernet8", "2000::1/64") + self.remove_ip_address("Ethernet12", "2001::1/64") + + # remove l3 interface + self.remove_l3_intf("Ethernet0") + self.remove_l3_intf("Ethernet4") + self.remove_l3_intf("Ethernet8") + self.remove_l3_intf("Ethernet12") + + # bring down interface + self.set_admin_status("Ethernet0", "down") + self.set_admin_status("Ethernet4", "down") + self.set_admin_status("Ethernet8", "down") + self.set_admin_status("Ethernet12", "down") + + # remove vrf + self.remove_vrf("Vrf_1") + self.remove_vrf("Vrf_2") + + # remove ip address and default route + dvs.servers[0].runcmd("ip -6 route del default dev eth0") + dvs.servers[0].runcmd("ip -6 address del 2000::2/64 dev eth0") + dvs.servers[1].runcmd("ip -6 route del default dev eth0") + dvs.servers[1].runcmd("ip -6 address del 2001::2/64 dev eth0") + dvs.servers[2].runcmd("ip -6 route del default dev eth0") + dvs.servers[2].runcmd("ip -6 address del 2000::2/64 dev eth0") + dvs.servers[3].runcmd("ip -6 route del default dev eth0") + dvs.servers[3].runcmd("ip -6 address del 2001::2/64 dev eth0") + + def test_RouteAndNexthopInDifferentVrf(self, dvs, testlog): + self.setup_db(dvs) + + # create vrf + vrf_1_oid = self.create_vrf("Vrf_1") + vrf_2_oid = self.create_vrf("Vrf_2") + + # create l3 interface + self.create_l3_intf("Ethernet0", "Vrf_1") + self.create_l3_intf("Ethernet4", "Vrf_1") + self.create_l3_intf("Ethernet8", "Vrf_2") + self.create_l3_intf("Ethernet12", "Vrf_2") + + # set ip address + self.add_ip_address("Ethernet0", "10.0.0.1/24") + self.add_ip_address("Ethernet4", "10.0.1.1/24") + self.add_ip_address("Ethernet8", "20.0.0.1/24") + self.add_ip_address("Ethernet12", "20.0.1.1/24") + + # bring up interface + self.set_admin_status("Ethernet0", "up") + self.set_admin_status("Ethernet4", "up") + self.set_admin_status("Ethernet8", "up") + self.set_admin_status("Ethernet12", "up") + + # set ip address and default route + dvs.servers[0].runcmd("ip address add 10.0.0.2/24 dev eth0") + dvs.servers[0].runcmd("ip route add default via 10.0.0.1") + + dvs.servers[1].runcmd("ip address add 10.0.1.2/24 dev eth0") + dvs.servers[1].runcmd("ip route add default via 10.0.1.1") + + dvs.servers[2].runcmd("ip address add 20.0.0.2/24 dev eth0") + dvs.servers[2].runcmd("ip route add default via 20.0.0.1") + + dvs.servers[3].runcmd("ip address add 20.0.1.2/24 dev eth0") + dvs.servers[3].runcmd("ip route add default via 20.0.1.1") + + time.sleep(1) + + # get neighbor entry + dvs.servers[0].runcmd("ping -c 1 10.0.1.2") + dvs.servers[2].runcmd("ping -c 1 20.0.1.2") + + # add route + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 20.0.1.2/32 20.0.1.2 vrf Vrf_1 nexthop-vrf Vrf_2\"") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 10.0.0.2/32 10.0.0.2 vrf Vrf_2 nexthop-vrf Vrf_1\"") + time.sleep(1) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_1") + route_entries = tbl.getKeys() + assert "20.0.1.2" in route_entries + + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_2") + route_entries = tbl.getKeys() + assert "10.0.0.2" in route_entries + + # check ASIC neighbor interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") + nexthop_entries = tbl.getKeys() + for key in nexthop_entries: + (status, fvs) = tbl.get(key) + assert status == True + for fv in fvs: + if fv[0] == "SAI_NEXT_HOP_ATTR_IP" and fv[1] == "20.0.1.2": + nexthop2_found = True + nexthop2_oid = key + if fv[0] == "SAI_NEXT_HOP_ATTR_IP" and fv[1] == "10.0.0.2": + nexthop1_found = True + nexthop1_oid = key + + assert nexthop1_found == True and nexthop2_found == True + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + route_entries = tbl.getKeys() + for key in route_entries: + route = json.loads(key) + if route["dest"] == "10.0.0.2/32" and route["vr"] == vrf_2_oid: + (status, fvs) = tbl.get(key) + assert status == True + for fv in fvs: + if fv[0] == "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID": + assert fv[1] == nexthop1_oid + route1_found = True + if route["dest"] == "20.0.1.2/32" and route["vr"] == vrf_1_oid: + (status, fvs) = tbl.get(key) + assert status == True + for fv in fvs: + if fv[0] == "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID": + assert fv[1] == nexthop2_oid + route2_found = True + assert route1_found == True and route2_found == True + + # Ping should work + ping_stats = dvs.servers[0].runcmd("ping -c 1 20.0.1.2") + assert ping_stats == 0 + + # remove route + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ip route 20.0.1.2/32 20.0.1.2 vrf Vrf_1 nexthop-vrf Vrf_2\"") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ip route 10.0.0.2/32 10.0.0.2 vrf Vrf_2 nexthop-vrf Vrf_1\"") + time.sleep(1) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_1") + route_entries = tbl.getKeys() + assert "20.0.1.2" not in route_entries + + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_2") + route_entries = tbl.getKeys() + assert "10.0.0.2" not in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + assert route["dest"] != "10.0.0.2/32" and route["dest"] != "20.0.1.2/32" + + # remove ip address + self.remove_ip_address("Ethernet0", "10.0.0.1/24") + self.remove_ip_address("Ethernet4", "10.0.1.1/24") + self.remove_ip_address("Ethernet8", "20.0.0.1/24") + self.remove_ip_address("Ethernet12", "20.0.1.1/24") + + # remove l3 interface + self.remove_l3_intf("Ethernet0") + self.remove_l3_intf("Ethernet4") + self.remove_l3_intf("Ethernet8") + self.remove_l3_intf("Ethernet12") + + self.set_admin_status("Ethernet0", "down") + self.set_admin_status("Ethernet4", "down") + self.set_admin_status("Ethernet8", "down") + self.set_admin_status("Ethernet12", "down") + + # remove vrf + self.remove_vrf("Vrf_1") + self.remove_vrf("Vrf_2") - assert found + # remove ip address and default route + dvs.servers[0].runcmd("ip route del default dev eth0") + dvs.servers[0].runcmd("ip address del 10.0.0.2/24 dev eth0") + dvs.servers[1].runcmd("ip route del default dev eth0") + dvs.servers[1].runcmd("ip address del 10.0.1.2/24 dev eth0") + dvs.servers[2].runcmd("ip route del default dev eth0") + dvs.servers[2].runcmd("ip address del 20.0.0.2/24 dev eth0") + dvs.servers[3].runcmd("ip route del default dev eth0") + dvs.servers[3].runcmd("ip address del 20.0.1.2/24 dev eth0") diff --git a/tests/test_vnet.py b/tests/test_vnet.py index 2870f5bd8e..76c8c27267 100644 --- a/tests/test_vnet.py +++ b/tests/test_vnet.py @@ -254,6 +254,10 @@ def delete_vlan_interface(dvs, ifname, ipaddr): time.sleep(2) + delete_entry_tbl(conf_db, "VLAN_INTERFACE", ifname) + + time.sleep(2) + def create_phy_interface(dvs, ifname, vnet_name, ipaddr): conf_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) @@ -296,6 +300,10 @@ def delete_phy_interface(dvs, ifname, ipaddr): time.sleep(2) + delete_entry_tbl(conf_db, "INTERFACE", ifname) + + time.sleep(2) + def create_vnet_entry(dvs, name, tunnel, vni, peer_list, scope=""): conf_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) @@ -544,8 +552,7 @@ def check_del_vnet_entry(self, dvs, name): def vnet_route_ids(self, dvs, name, local=False): vr_set = set() - if not local: - vr_set.add(self.vr_map[name].get('ing')) + vr_set.add(self.vr_map[name].get('ing')) try: for peer in self.vr_map[name].get('peer'): @@ -575,8 +582,8 @@ def check_router_interface(self, dvs, name, vlan_oid=0): new_rif = get_created_entry(asic_db, self.ASIC_RIF_TABLE, self.rifs) check_object(asic_db, self.ASIC_RIF_TABLE, new_rif, expected_attr) - #IP2ME and subnet routes will be created with every router interface - new_route = get_created_entries(asic_db, self.ASIC_ROUTE_ENTRY, self.routes, 2) + #IP2ME route will be created with every router interface + new_route = get_created_entries(asic_db, self.ASIC_ROUTE_ENTRY, self.routes, 1) self.rifs.add(new_rif) self.routes.update(new_route) diff --git a/tests/test_vrf.py b/tests/test_vrf.py index a53d498b98..d8d6c9595b 100644 --- a/tests/test_vrf.py +++ b/tests/test_vrf.py @@ -2,166 +2,174 @@ import time import json import random -import time from pprint import pprint +class TestVrf(object): + def setup_db(self, dvs): + self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) + self.adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) + self.cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) -def create_entry(tbl, key, pairs): - fvs = swsscommon.FieldValuePairs(pairs) - tbl.set(key, fvs) - - # FIXME: better to wait until DB create them - time.sleep(1) - - -def create_entry_tbl(db, table, key, pairs): - tbl = swsscommon.Table(db, table) - create_entry(tbl, key, pairs) - - -def create_entry_pst(db, table, key, pairs): - tbl = swsscommon.ProducerStateTable(db, table) - create_entry(tbl, key, pairs) - - -def delete_entry_tbl(db, table, key): - tbl = swsscommon.Table(db, table) - tbl._del(key) - time.sleep(1) - -def delete_entry_pst(db, table, key): - tbl = swsscommon.ProducerStateTable(db, table) - tbl._del(key) - time.sleep(1) + def create_entry(self, tbl, key, pairs): + fvs = swsscommon.FieldValuePairs(pairs) + tbl.set(key, fvs) + time.sleep(1) -def how_many_entries_exist(db, table): - tbl = swsscommon.Table(db, table) - return len(tbl.getKeys()) + def create_entry_tbl(self, db, table, key, pairs): + tbl = swsscommon.Table(db, table) + self.create_entry(tbl, key, pairs) + def delete_entry_tbl(self, db, table, key): + tbl = swsscommon.Table(db, table) + tbl._del(key) + time.sleep(1) -def entries(db, table): - tbl = swsscommon.Table(db, table) - return set(tbl.getKeys()) + def how_many_entries_exist(self, db, table): + tbl = swsscommon.Table(db, table) + return len(tbl.getKeys()) + def entries(self, db, table): + tbl = swsscommon.Table(db, table) + return set(tbl.getKeys()) -def is_vrf_attributes_correct(db, table, key, expected_attributes): - tbl = swsscommon.Table(db, table) - keys = set(tbl.getKeys()) - assert key in keys, "The created key wasn't found" + def is_vrf_attributes_correct(self, db, table, key, expected_attributes): + tbl = swsscommon.Table(db, table) + keys = set(tbl.getKeys()) + assert key in keys, "The created key wasn't found" - status, fvs = tbl.get(key) - assert status, "Got an error when get a key" + status, fvs = tbl.get(key) + assert status, "Got an error when get a key" - # filter the fake 'NULL' attribute out - fvs = filter(lambda x : x != ('NULL', 'NULL'), fvs) + # filter the fake 'NULL' attribute out + fvs = filter(lambda x : x != ('NULL', 'NULL'), fvs) - attr_keys = {entry[0] for entry in fvs} - assert attr_keys == set(expected_attributes.keys()) + attr_keys = {entry[0] for entry in fvs} + assert attr_keys == set(expected_attributes.keys()) - for name, value in fvs: - assert expected_attributes[name] == value, "Wrong value %s for the attribute %s = %s" % \ + for name, value in fvs: + assert expected_attributes[name] == value, "Wrong value %s for the attribute %s = %s" % \ (value, name, expected_attributes[name]) -def vrf_create(asic_db, appl_db, vrf_name, attributes, expected_attributes): - # check that the vrf wasn't exist before - assert how_many_entries_exist(asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") == 1, "The initial state is incorrect" + def vrf_create(self, dvs, vrf_name, attributes, expected_attributes): + # check that the vrf wasn't exist before + assert self.how_many_entries_exist(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") == 1, "The initial state is incorrect" - # read existing entries in the DB - initial_entries = entries(asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") + # read existing entries in the DB + initial_entries = self.entries(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") - # create a fake attribute if we don't have attributes in the request - if len(attributes) == 0: - attributes = [('empty', 'empty')] + # create a fake attribute if we don't have attributes in the request + if len(attributes) == 0: + attributes = [('empty', 'empty')] - # create the VRF entry in Config DB - create_entry_pst(appl_db, "VRF_TABLE", vrf_name, attributes) + # create the VRF entry in Config DB + self.create_entry_tbl(self.cdb, "VRF", vrf_name, attributes) - # check that the vrf entry was created - assert how_many_entries_exist(asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") == 2, "The vrf wasn't created" + # check vrf created in kernel + (status, rslt) = dvs.runcmd("ip link show " + vrf_name) + assert status == 0 - # find the id of the entry which was added - added_entry_id = list(entries(asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") - initial_entries)[0] + # check application database + tbl = swsscommon.Table(self.pdb, "VRF_TABLE") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == vrf_name + exp_attr = {} + for an in xrange(len(attributes)): + exp_attr[attributes[an][0]] = attributes[an][1] + self.is_vrf_attributes_correct(self.pdb, "VRF_TABLE", vrf_name, exp_attr) + + # check that the vrf entry was created + assert self.how_many_entries_exist(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") == 2, "The vrf wasn't created" + + # find the id of the entry which was added + added_entry_id = list(self.entries(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") - initial_entries)[0] + + # check correctness of the created attributes + self.is_vrf_attributes_correct( + self.adb, + "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", + added_entry_id, + expected_attributes, + ) - # check correctness of the created attributes - is_vrf_attributes_correct( - asic_db, - "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", - added_entry_id, - expected_attributes, - ) + state = { + 'initial_entries': initial_entries, + 'entry_id': added_entry_id, + } - state = { - 'initial_entries': initial_entries, - 'entry_id': added_entry_id, - } + return state - return state + def vrf_remove(self, dvs, vrf_name, state): + # delete the created vrf entry + self.delete_entry_tbl(self.cdb, "VRF", vrf_name) -def vrf_remove(asic_db, appl_db, vrf_name, state): - # delete the created vrf entry - delete_entry_pst(appl_db, "VRF_TABLE", vrf_name) + # check application database + tbl = swsscommon.Table(self.pdb, "VRF_TABLE") + intf_entries = tbl.getKeys() + assert vrf_name not in intf_entries - # check that the vrf entry was removed - assert how_many_entries_exist(asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") == 1, "The vrf wasn't removed" + # check that the vrf entry was removed + assert self.how_many_entries_exist(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") == 1, "The vrf wasn't removed" - # check that the correct vrf entry was removed - assert state['initial_entries'] == entries(asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER"), "The incorrect entry was removed" + # check that the correct vrf entry was removed + assert state['initial_entries'] == self.entries(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER"), "The incorrect entry was removed" + # check vrf was removed from kernel + (status, rslt) = dvs.runcmd("ip link show " + vrf_name) + assert status != 0 -def vrf_update(asic_db, appl_db, vrf_name, attributes, expected_attributes, state): - # update the VRF entry in Config DB - create_entry_pst(appl_db, "VRF_TABLE", vrf_name, attributes) + def vrf_update(self, vrf_name, attributes, expected_attributes, state): + # update the VRF entry in Config DB + self.create_entry_tbl(self.cdb, "VRF", vrf_name, attributes) - # check correctness of the created attributes - is_vrf_attributes_correct( - asic_db, - "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", - state['entry_id'], - expected_attributes, - ) + # check correctness of the created attributes + self.is_vrf_attributes_correct( + self.adb, + "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", + state['entry_id'], + expected_attributes, + ) -def boolean_gen(): - result = random.choice(['false', 'true']) - return result, result + def boolean_gen(self): + result = random.choice(['false', 'true']) + return result, result -def mac_addr_gen(): - ns = [random.randint(0, 255) for _ in xrange(6)] - ns[0] &= 0xfe - mac = ':'.join("%02x" % n for n in ns) - return mac, mac.upper() + def mac_addr_gen(self): + ns = [random.randint(0, 255) for _ in xrange(6)] + ns[0] &= 0xfe + mac = ':'.join("%02x" % n for n in ns) + return mac, mac.upper() -def packet_action_gen(): - values = [ - ("drop", "SAI_PACKET_ACTION_DROP"), - ("forward", "SAI_PACKET_ACTION_FORWARD"), - ("copy", "SAI_PACKET_ACTION_COPY"), - ("copy_cancel", "SAI_PACKET_ACTION_COPY_CANCEL"), - ("trap", "SAI_PACKET_ACTION_TRAP"), - ("log", "SAI_PACKET_ACTION_LOG"), - ("deny", "SAI_PACKET_ACTION_DENY"), - ("transit", "SAI_PACKET_ACTION_TRANSIT"), - ] + def packet_action_gen(self): + values = [ + ("drop", "SAI_PACKET_ACTION_DROP"), + ("forward", "SAI_PACKET_ACTION_FORWARD"), + ("copy", "SAI_PACKET_ACTION_COPY"), + ("copy_cancel", "SAI_PACKET_ACTION_COPY_CANCEL"), + ("trap", "SAI_PACKET_ACTION_TRAP"), + ("log", "SAI_PACKET_ACTION_LOG"), + ("deny", "SAI_PACKET_ACTION_DENY"), + ("transit", "SAI_PACKET_ACTION_TRANSIT"), + ] - r = random.choice(values) - return r[0], r[1] + r = random.choice(values) + return r[0], r[1] -class TestVrf(object): - def test_VRFOrch_Comprehensive(self, dvs, testlog): - asic_db = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) - appl_db = swsscommon.DBConnector(swsscommon.APPL_DB, dvs.redis_sock, 0) + def test_VRFMgr_Comprehensive(self, dvs, testlog): + self.setup_db(dvs) attributes = [ - ('v4', 'SAI_VIRTUAL_ROUTER_ATTR_ADMIN_V4_STATE', boolean_gen), - ('v6', 'SAI_VIRTUAL_ROUTER_ATTR_ADMIN_V6_STATE', boolean_gen), - ('src_mac', 'SAI_VIRTUAL_ROUTER_ATTR_SRC_MAC_ADDRESS', mac_addr_gen), - ('ttl_action', 'SAI_VIRTUAL_ROUTER_ATTR_VIOLATION_TTL1_PACKET_ACTION', packet_action_gen), - ('ip_opt_action', 'SAI_VIRTUAL_ROUTER_ATTR_VIOLATION_IP_OPTIONS_PACKET_ACTION', packet_action_gen), - ('l3_mc_action', 'SAI_VIRTUAL_ROUTER_ATTR_UNKNOWN_L3_MULTICAST_PACKET_ACTION', packet_action_gen), + ('v4', 'SAI_VIRTUAL_ROUTER_ATTR_ADMIN_V4_STATE', self.boolean_gen), + ('v6', 'SAI_VIRTUAL_ROUTER_ATTR_ADMIN_V6_STATE', self.boolean_gen), + ('src_mac', 'SAI_VIRTUAL_ROUTER_ATTR_SRC_MAC_ADDRESS', self.mac_addr_gen), + ('ttl_action', 'SAI_VIRTUAL_ROUTER_ATTR_VIOLATION_TTL1_PACKET_ACTION', self.packet_action_gen), + ('ip_opt_action', 'SAI_VIRTUAL_ROUTER_ATTR_VIOLATION_IP_OPTIONS_PACKET_ACTION', self.packet_action_gen), + ('l3_mc_action', 'SAI_VIRTUAL_ROUTER_ATTR_UNKNOWN_L3_MULTICAST_PACKET_ACTION', self.packet_action_gen), ] random.seed(int(time.clock())) @@ -170,7 +178,7 @@ def test_VRFOrch_Comprehensive(self, dvs, testlog): # generate testcases for all combinations of attributes req_attr = [] exp_attr = {} - vrf_name = "vrf_%d" % n + vrf_name = "Vrf_%d" % n bmask = 0x1 for an in xrange(len(attributes)): if (bmask & n) > 0: @@ -178,22 +186,22 @@ def test_VRFOrch_Comprehensive(self, dvs, testlog): req_attr.append((attributes[an][0], req_res)) exp_attr[attributes[an][1]] = exp_res bmask <<= 1 - state = vrf_create(asic_db, appl_db, vrf_name, req_attr, exp_attr) - vrf_remove(asic_db, appl_db, vrf_name, state) + state = self.vrf_create(dvs, vrf_name, req_attr, exp_attr) + self.vrf_remove(dvs, vrf_name, state) + + def test_VRFMgr(self, dvs, testlog): + self.setup_db(dvs) - def test_VRFOrch(self, dvs, testlog): - asic_db = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) - appl_db = swsscommon.DBConnector(swsscommon.APPL_DB, dvs.redis_sock, 0) - state = vrf_create(asic_db, appl_db, "vrf0", + state = self.vrf_create(dvs, "Vrf0", [ ], { } ) - vrf_remove(asic_db, appl_db, "vrf0", state) + self.vrf_remove(dvs, "Vrf0", state) - state = vrf_create(asic_db, appl_db, "vrf1", + state = self.vrf_create(dvs, "Vrf1", [ ('v4', 'true'), ('src_mac', '02:04:06:07:08:09'), @@ -203,24 +211,23 @@ def test_VRFOrch(self, dvs, testlog): 'SAI_VIRTUAL_ROUTER_ATTR_SRC_MAC_ADDRESS': '02:04:06:07:08:09', } ) - vrf_remove(asic_db, appl_db, "vrf1", state) + self.vrf_remove(dvs, "Vrf1", state) - def test_VRFOrch_Update(self, dvs, testlog): - asic_db = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) - appl_db = swsscommon.DBConnector(swsscommon.APPL_DB, dvs.redis_sock, 0) + def test_VRFMgr_Update(self, dvs, testlog): + self.setup_db(dvs) attributes = [ - ('v4', 'SAI_VIRTUAL_ROUTER_ATTR_ADMIN_V4_STATE', boolean_gen), - ('v6', 'SAI_VIRTUAL_ROUTER_ATTR_ADMIN_V6_STATE', boolean_gen), - ('src_mac', 'SAI_VIRTUAL_ROUTER_ATTR_SRC_MAC_ADDRESS', mac_addr_gen), - ('ttl_action', 'SAI_VIRTUAL_ROUTER_ATTR_VIOLATION_TTL1_PACKET_ACTION', packet_action_gen), - ('ip_opt_action', 'SAI_VIRTUAL_ROUTER_ATTR_VIOLATION_IP_OPTIONS_PACKET_ACTION', packet_action_gen), - ('l3_mc_action', 'SAI_VIRTUAL_ROUTER_ATTR_UNKNOWN_L3_MULTICAST_PACKET_ACTION', packet_action_gen), + ('v4', 'SAI_VIRTUAL_ROUTER_ATTR_ADMIN_V4_STATE', self.boolean_gen), + ('v6', 'SAI_VIRTUAL_ROUTER_ATTR_ADMIN_V6_STATE', self.boolean_gen), + ('src_mac', 'SAI_VIRTUAL_ROUTER_ATTR_SRC_MAC_ADDRESS', self.mac_addr_gen), + ('ttl_action', 'SAI_VIRTUAL_ROUTER_ATTR_VIOLATION_TTL1_PACKET_ACTION', self.packet_action_gen), + ('ip_opt_action', 'SAI_VIRTUAL_ROUTER_ATTR_VIOLATION_IP_OPTIONS_PACKET_ACTION', self.packet_action_gen), + ('l3_mc_action', 'SAI_VIRTUAL_ROUTER_ATTR_UNKNOWN_L3_MULTICAST_PACKET_ACTION', self.packet_action_gen), ] random.seed(int(time.clock())) - state = vrf_create(asic_db, appl_db, "vrf_a", + state = self.vrf_create(dvs, "Vrf_a", [ ], { @@ -234,6 +241,62 @@ def test_VRFOrch_Update(self, dvs, testlog): req_res, exp_res = attr[2]() req_attr.append((attr[0], req_res)) exp_attr[attr[1]] = exp_res - vrf_update(asic_db, appl_db, "vrf_a", req_attr, exp_attr, state) + self.vrf_update("Vrf_a", req_attr, exp_attr, state) + + self.vrf_remove(dvs, "Vrf_a", state) + + def test_VRFMgr_Capacity(self, dvs, testlog): + self.setup_db(dvs) + + initial_entries_cnt = self.how_many_entries_exist(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") + + maximum_vrf_cnt = 999 + + def create_entry(self, tbl, key, pairs): + fvs = swsscommon.FieldValuePairs(pairs) + tbl.set(key, fvs) + time.sleep(1) + + def create_entry_tbl(self, db, table, key, pairs): + tbl = swsscommon.Table(db, table) + self.create_entry(tbl, key, pairs) + + # create the VRF entry in Config DB + tbl = swsscommon.Table(self.cdb, "VRF") + fvs = swsscommon.FieldValuePairs([('empty', 'empty')]) + for i in range(maximum_vrf_cnt): + tbl.set("Vrf_%d" % i, fvs) + + # wait for all VRFs pushed to database and linux + time.sleep(30) + + # check app_db + intf_entries_cnt = self.how_many_entries_exist(self.pdb, "VRF_TABLE") + assert intf_entries_cnt == maximum_vrf_cnt + + # check asic_db + current_entries_cnt = self.how_many_entries_exist(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") + assert (current_entries_cnt - initial_entries_cnt) == maximum_vrf_cnt + + # check linux kernel + (exitcode, num) = dvs.runcmd(['sh', '-c', "ip link show | grep Vrf | wc -l"]) + assert num.strip() == str(maximum_vrf_cnt) + + # remove VRF from Config DB + for i in range(maximum_vrf_cnt): + tbl._del("Vrf_%d" % i) + + # wait for all VRFs deleted + time.sleep(120) + + # check app_db + intf_entries_cnt = self.how_many_entries_exist(self.pdb, "VRF_TABLE") + assert intf_entries_cnt == 0 + + # check asic_db + current_entries_cnt = self.how_many_entries_exist(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") + assert (current_entries_cnt - initial_entries_cnt) == 0 - vrf_remove(asic_db, appl_db, "vrf_a", state) + # check linux kernel + (exitcode, num) = dvs.runcmd(['sh', '-c', "ip link show | grep Vrf | wc -l"]) + assert num.strip() == '0' diff --git a/tests/test_warm_reboot.py b/tests/test_warm_reboot.py index 1b958a1d84..fc8ca3debd 100644 --- a/tests/test_warm_reboot.py +++ b/tests/test_warm_reboot.py @@ -2067,3 +2067,126 @@ def test_system_warmreboot_neighbor_syncup(self, dvs, testlog): intf_tbl._del("Ethernet{}|{}00::1/64".format(i*4, i*4)) intf_tbl._del("Ethernet{}".format(i*4, i*4)) intf_tbl._del("Ethernet{}".format(i*4, i*4)) + + def test_VrfMgrdWarmRestart(self, dvs, testlog): + + conf_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) + appl_db = swsscommon.DBConnector(swsscommon.APPL_DB, dvs.redis_sock, 0) + state_db = swsscommon.DBConnector(swsscommon.STATE_DB, dvs.redis_sock, 0) + + dvs.runcmd("config warm_restart enable swss") + + # bring up interface + dvs.runcmd("ifconfig Ethernet0 up") + dvs.runcmd("ifconfig Ethernet4 up") + + # create vrf + create_entry_tbl(conf_db, "VRF", "Vrf_1", [('empty', 'empty')]) + create_entry_tbl(conf_db, "VRF", "Vrf_2", [('empty', 'empty')]) + + intf_tbl = swsscommon.Table(conf_db, "INTERFACE") + fvs = swsscommon.FieldValuePairs([("vrf_name", "Vrf_1")]) + intf_tbl.set("Ethernet0", fvs) + intf_tbl.set("Ethernet4", fvs) + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + intf_tbl.set("Ethernet0|12.0.0.1/24", fvs) + intf_tbl.set("Ethernet4|13.0.0.1/24", fvs) + + time.sleep(1) + + dvs.servers[0].runcmd("ifconfig eth0 12.0.0.2/24") + dvs.servers[0].runcmd("ip route add default via 12.0.0.1") + + dvs.servers[1].runcmd("ifconfig eth0 13.0.0.2/24") + dvs.servers[1].runcmd("ip route add default via 13.0.0.1") + + time.sleep(1) + + # Ping should work between servers via vs port interfaces + ping_stats = dvs.servers[0].runcmd("ping -c 1 13.0.0.2") + assert ping_stats == 0 + time.sleep(1) + + tbl = swsscommon.Table(appl_db, "NEIGH_TABLE") + (status, fvs) = tbl.get("Ethernet0:12.0.0.2") + assert status == True + + (status, fvs) = tbl.get("Ethernet4:13.0.0.2") + assert status == True + + (exitcode, vrf_before) = dvs.runcmd(['sh', '-c', "ip link show | grep Vrf"]) + + dvs.runcmd(['sh', '-c', 'pkill -x vrfmgrd']) + + pubsub = dvs.SubscribeAsicDbObject("SAI_OBJECT_TYPE") + + dvs.runcmd(['sh', '-c', 'supervisorctl start vrfmgrd']) + time.sleep(2) + + # kernel vrf config should be kept the same + (exitcode, vrf_after) = dvs.runcmd(['sh', '-c', "ip link show | grep Vrf"]) + assert vrf_after == vrf_before + + # VIRTUAL_ROUTER/ROUTE_ENTRY/NEIGH_ENTRY should be kept the same + (nadd, ndel) = dvs.CountSubscribedObjects(pubsub, ignore=["SAI_OBJECT_TYPE_FDB_ENTRY"]) + assert nadd == 0 + assert ndel == 0 + + # new ip on server 1 + dvs.servers[1].runcmd("ifconfig eth0 13.0.0.3/24") + dvs.servers[1].runcmd("ip route add default via 13.0.0.1") + + # Ping should work between servers via vs port interfaces + ping_stats = dvs.servers[0].runcmd("ping -c 1 13.0.0.3") + assert ping_stats == 0 + + # new neighbor learn on vs + (status, fvs) = tbl.get("Ethernet4:13.0.0.3") + assert status == True + + # flush all neigh entries + dvs.runcmd("ip link set group default arp off") + dvs.runcmd("ip link set group default arp on") + + # remove interface Ethernet4 from vrf_1, add it to vrf_2 + intf_tbl._del("Ethernet4|13.0.0.1/24") + intf_tbl._del("Ethernet4") + time.sleep(1) + + intf_tbl = swsscommon.Table(conf_db, "INTERFACE") + fvs = swsscommon.FieldValuePairs([("vrf_name", "Vrf_2")]) + intf_tbl.set("Ethernet4", fvs) + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + intf_tbl.set("Ethernet4|13.0.0.1/24", fvs) + time.sleep(1) + + # Ping should not work + ping_stats = dvs.servers[0].runcmd("ping -c 1 13.0.0.3") + assert ping_stats != 0 + + # remove interface Ethernet0 from vrf_1, add it to vrf_2 + intf_tbl._del("Ethernet0|12.0.0.1/24") + intf_tbl._del("Ethernet0") + time.sleep(1) + fvs = swsscommon.FieldValuePairs([("vrf_name", "Vrf_2")]) + intf_tbl.set("Ethernet0", fvs) + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + intf_tbl.set("Ethernet0|12.0.0.1/24", fvs) + time.sleep(1) + + # Ping should work between servers via vs port interfaces + ping_stats = dvs.servers[0].runcmd("ping -c 1 13.0.0.3") + assert ping_stats == 0 + + (status, fvs) = tbl.get("Ethernet4:13.0.0.3") + assert status == True + + intf_tbl._del("Ethernet0|12.0.0.1/24") + intf_tbl._del("Ethernet4|13.0.0.1/24") + intf_tbl._del("Ethernet0") + intf_tbl._del("Ethernet4") + del_entry_tbl(conf_db, "VRF", "Vrf_1") + del_entry_tbl(conf_db, "VRF", "Vrf_2") + dvs.servers[0].runcmd("ifconfig eth0 0") + dvs.servers[1].runcmd("ifconfig eth0 0") + time.sleep(2) From c57fc34b10689fef48ed9131bd0b4088ade92c33 Mon Sep 17 00:00:00 2001 From: Vitaliy Senchyshyn <43479243+vsenchyshyn@users.noreply.github.com> Date: Thu, 7 Nov 2019 10:58:14 -0800 Subject: [PATCH 15/63] [bufferorch] Fixed buffer and buffer profile attributes types accoring to changes in SAI 1.5 (#1120) --- orchagent/bufferorch.cpp | 12 ++++++------ orchagent/pfcactionhandler.cpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/orchagent/bufferorch.cpp b/orchagent/bufferorch.cpp index 20944304b8..ce2f2f2d1e 100644 --- a/orchagent/bufferorch.cpp +++ b/orchagent/bufferorch.cpp @@ -264,7 +264,7 @@ task_process_status BufferOrch::processBufferPool(Consumer &consumer) if (field == buffer_size_field_name) { attr.id = SAI_BUFFER_POOL_ATTR_SIZE; - attr.value.u32 = (uint32_t)stoul(value); + attr.value.u64 = (uint64_t)stoul(value); attribs.push_back(attr); } else if (field == buffer_pool_type_field_name) @@ -307,7 +307,7 @@ task_process_status BufferOrch::processBufferPool(Consumer &consumer) } else if (field == buffer_pool_xoff_field_name) { - attr.value.u32 = (uint32_t)stoul(value); + attr.value.u64 = (uint64_t)stoul(value); attr.id = SAI_BUFFER_POOL_ATTR_XOFF_SIZE; attribs.push_back(attr); } @@ -414,19 +414,19 @@ task_process_status BufferOrch::processBufferProfile(Consumer &consumer) } else if (field == buffer_xon_field_name) { - attr.value.u32 = (uint32_t)stoul(value); + attr.value.u64 = (uint64_t)stoul(value); attr.id = SAI_BUFFER_PROFILE_ATTR_XON_TH; attribs.push_back(attr); } else if (field == buffer_xon_offset_field_name) { - attr.value.u32 = (uint32_t)stoul(value); + attr.value.u64 = (uint64_t)stoul(value); attr.id = SAI_BUFFER_PROFILE_ATTR_XON_OFFSET_TH; attribs.push_back(attr); } else if (field == buffer_xoff_field_name) { - attr.value.u32 = (uint32_t)stoul(value); + attr.value.u64 = (uint64_t)stoul(value); attr.id = SAI_BUFFER_PROFILE_ATTR_XOFF_TH; attribs.push_back(attr); } @@ -453,7 +453,7 @@ task_process_status BufferOrch::processBufferProfile(Consumer &consumer) attribs.push_back(attr); attr.id = SAI_BUFFER_PROFILE_ATTR_SHARED_STATIC_TH; - attr.value.u32 = (uint32_t)stoul(value); + attr.value.u64 = (uint64_t)stoul(value); attribs.push_back(attr); } else diff --git a/orchagent/pfcactionhandler.cpp b/orchagent/pfcactionhandler.cpp index df0977cb66..b78b9e993e 100644 --- a/orchagent/pfcactionhandler.cpp +++ b/orchagent/pfcactionhandler.cpp @@ -579,7 +579,7 @@ void PfcWdZeroBufferHandler::ZeroBufferProfile::createZeroBufferProfile(bool ing // Create zero pool attr.id = SAI_BUFFER_POOL_ATTR_SIZE; - attr.value.u32 = 0; + attr.value.u64 = 0; attribs.push_back(attr); attr.id = SAI_BUFFER_POOL_ATTR_TYPE; From 56d66a188229cd8d2104313ab3dd08a5d5e2e90a Mon Sep 17 00:00:00 2001 From: Wenda Ni Date: Thu, 7 Nov 2019 17:13:21 -0800 Subject: [PATCH 16/63] Sub port interface implementation (#969) IntfMgrd: Listen to CONFIG_DB VLAN_SUB_INTERFACE table. Process host sub port interface: create, set (admin_status), and remove using ip link and ip address system call, and update the STATE_DB Relay the VLAN_SUB_INTERFACE table operation and field-value content to APPL_DB INTF_TABLE. IntfOrch: Listen to APPL_DB INTF_TABLE. Process sub port interface on physical port: create (a Port object for sub port interface), set (admin_status), and remove. PortsOrch: Add member function to create a Port object for sub port interface vs unit test: sub port interface creation sub port interface add IP addresses sub port interface admin status change sub port interface remove IP addresses sub port interface removal Signed-off-by: Wenda Ni --- cfgmgr/intfmgr.cpp | 256 +++++++++++++++++++++++- cfgmgr/intfmgr.h | 13 +- cfgmgr/intfmgrd.cpp | 1 + orchagent/bufferorch.cpp | 2 +- orchagent/intfsorch.cpp | 168 +++++++++++++++- orchagent/intfsorch.h | 5 +- orchagent/orch.h | 1 + orchagent/port.h | 3 + orchagent/portsorch.cpp | 96 +++++++++ orchagent/portsorch.h | 3 + orchagent/vnetorch.cpp | 4 +- orchagent/vnetorch.h | 2 +- tests/test_sub_port_intf.py | 381 ++++++++++++++++++++++++++++++++++++ 13 files changed, 908 insertions(+), 27 deletions(-) create mode 100644 tests/test_sub_port_intf.py diff --git a/cfgmgr/intfmgr.cpp b/cfgmgr/intfmgr.cpp index a84fe92a9e..3d1915c1b8 100644 --- a/cfgmgr/intfmgr.cpp +++ b/cfgmgr/intfmgr.cpp @@ -15,14 +15,13 @@ using namespace swss; #define LAG_PREFIX "PortChannel" #define LOOPBACK_PREFIX "Loopback" #define VNET_PREFIX "Vnet" +#define MTU_INHERITANCE "0" #define VRF_PREFIX "Vrf" #define LOOPBACK_DEFAULT_MTU_STR "65536" IntfMgr::IntfMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, const vector &tableNames) : Orch(cfgDb, tableNames), - m_cfgIntfTable(cfgDb, CFG_INTF_TABLE_NAME), - m_cfgVlanIntfTable(cfgDb, CFG_VLAN_INTF_TABLE_NAME), m_statePortTable(stateDb, STATE_PORT_TABLE_NAME), m_stateLagTable(stateDb, STATE_LAG_TABLE_NAME), m_stateVlanTable(stateDb, STATE_VLAN_TABLE_NAME), @@ -167,6 +166,157 @@ bool IntfMgr::isIntfChangeVrf(const string &alias, const string &vrfName) return false; } +void IntfMgr::addHostSubIntf(const string&intf, const string &subIntf, const string &vlan) +{ + // TODO: remove when validation check at mgmt is in place + for (const auto &c : intf) + { + if (!isalnum(c)) + { + SWSS_LOG_ERROR("Invalid parent port name %s for host sub interface %s", intf.c_str(), subIntf.c_str()); + return; + } + } + for (const auto &c : vlan) + { + if (!isdigit(c)) + { + SWSS_LOG_ERROR("Invalid vlan id %s for host sub interface %s", vlan.c_str(), subIntf.c_str()); + return; + } + } + + stringstream cmd; + string res; + + cmd << IP_CMD << " link add link " << intf << " name " << subIntf << " type vlan id " << vlan; + EXEC_WITH_ERROR_THROW(cmd.str(), res); +} + +void IntfMgr::setHostSubIntfMtu(const string &subIntf, const string &mtu) +{ + // TODO: remove when validation check at mgmt is in place + size_t found = subIntf.find(VLAN_SUB_INTERFACE_SEPARATOR); + if (found == string::npos) + { + SWSS_LOG_ERROR("Invalid host sub interface name: %s", subIntf.c_str()); + return; + } + size_t i = 0; + for (const auto &c : subIntf) + { + if (i < found && !isalnum(c)) + { + SWSS_LOG_ERROR("Invalid host sub interface name: %s", subIntf.c_str()); + return; + } + else if (i > found && !isdigit(c)) + { + SWSS_LOG_ERROR("Invalid host sub interface name: %s", subIntf.c_str()); + return; + } + i++; + } + + stringstream cmd; + string res; + + cmd << IP_CMD << " link set " << subIntf << " mtu " << mtu; + EXEC_WITH_ERROR_THROW(cmd.str(), res); +} + +void IntfMgr::setHostSubIntfAdminStatus(const string &subIntf, const string &adminStatus) +{ + // TODO: remove when validation check at mgmt is in place + size_t found = subIntf.find(VLAN_SUB_INTERFACE_SEPARATOR); + if (found == string::npos) + { + SWSS_LOG_ERROR("Invalid host sub interface name: %s", subIntf.c_str()); + return; + } + size_t i = 0; + for (const auto &c : subIntf) + { + if (i < found && !isalnum(c)) + { + SWSS_LOG_ERROR("Invalid host sub interface name: %s", subIntf.c_str()); + return; + } + else if (i > found && !isdigit(c)) + { + SWSS_LOG_ERROR("Invalid host sub interface name: %s", subIntf.c_str()); + return; + } + i++; + } + + stringstream cmd; + string res; + + cmd << IP_CMD << " link set " << subIntf << " " << adminStatus; + EXEC_WITH_ERROR_THROW(cmd.str(), res); +} + +void IntfMgr::removeHostSubIntf(const string &subIntf) +{ + // TODO: remove when validation check at mgmt is in place + size_t found = subIntf.find(VLAN_SUB_INTERFACE_SEPARATOR); + if (found == string::npos) + { + SWSS_LOG_ERROR("Invalid host sub interface name: %s", subIntf.c_str()); + return; + } + size_t i = 0; + for (const auto &c : subIntf) + { + if (i < found && !isalnum(c)) + { + SWSS_LOG_ERROR("Invalid host sub interface name: %s", subIntf.c_str()); + return; + } + else if (i > found && !isdigit(c)) + { + SWSS_LOG_ERROR("Invalid host sub interface name: %s", subIntf.c_str()); + return; + } + i++; + } + + stringstream cmd; + string res; + + cmd << IP_CMD << " link del " << subIntf; + EXEC_WITH_ERROR_THROW(cmd.str(), res); +} + +void IntfMgr::setSubIntfStateOk(const string &alias) +{ + vector fvTuples = {{"state", "ok"}}; + + if (!alias.compare(0, strlen(LAG_PREFIX), LAG_PREFIX)) + { + m_stateLagTable.set(alias, fvTuples); + } + else + { + // EthernetX using PORT_TABLE + m_statePortTable.set(alias, fvTuples); + } +} + +void IntfMgr::removeSubIntfState(const string &alias) +{ + if (!alias.compare(0, strlen(LAG_PREFIX), LAG_PREFIX)) + { + m_stateLagTable.del(alias); + } + else + { + // EthernetX using PORT_TABLE + m_statePortTable.del(alias); + } +} + bool IntfMgr::isIntfStateOk(const string &alias) { vector temp; @@ -217,23 +367,43 @@ bool IntfMgr::isIntfStateOk(const string &alias) } bool IntfMgr::doIntfGeneralTask(const vector& keys, - const vector& data, + vector data, const string& op) { SWSS_LOG_ENTER(); string alias(keys[0]); - string vrf_name = ""; + string vlanId; + string subIntfAlias; + size_t found = alias.find(VLAN_SUB_INTERFACE_SEPARATOR); + if (found != string::npos) + { + // This is a sub interface + // subIntfAlias holds the complete sub interface name + // while alias becomes the parent interface + subIntfAlias = alias; + vlanId = alias.substr(found + 1); + alias = alias.substr(0, found); + } bool is_lo = !alias.compare(0, strlen(LOOPBACK_PREFIX), LOOPBACK_PREFIX); + string vrf_name = ""; + string mtu = ""; + string adminStatus = ""; for (auto idx : data) { const auto &field = fvField(idx); const auto &value = fvValue(idx); + if (field == "vnet_name" || field == "vrf_name") { vrf_name = value; } + + if (field == "admin_status") + { + adminStatus = value; + } } if (op == SET_COMMAND) @@ -256,16 +426,73 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, SWSS_LOG_ERROR("%s can not change to %s directly, skipping", alias.c_str(), vrf_name.c_str()); return true; } + if (is_lo) { addLoopbackIntf(alias); } + if (!vrf_name.empty()) { setIntfVrf(alias, vrf_name); } - m_appIntfTableProducer.set(alias, data); - m_stateIntfTable.hset(alias, "vrf", vrf_name); + + if (!subIntfAlias.empty()) + { + if (m_subIntfList.find(subIntfAlias) == m_subIntfList.end()) + { + try + { + addHostSubIntf(alias, subIntfAlias, vlanId); + } + catch (const std::runtime_error &e) + { + SWSS_LOG_NOTICE("Sub interface ip link add failure. Runtime error: %s", e.what()); + return false; + } + + m_subIntfList.insert(subIntfAlias); + } + + if (!mtu.empty()) + { + try + { + setHostSubIntfMtu(subIntfAlias, mtu); + } + catch (const std::runtime_error &e) + { + SWSS_LOG_NOTICE("Sub interface ip link set mtu failure. Runtime error: %s", e.what()); + return false; + } + } + else + { + FieldValueTuple fvTuple("mtu", MTU_INHERITANCE); + data.push_back(fvTuple); + } + + if (adminStatus.empty()) + { + adminStatus = "up"; + FieldValueTuple fvTuple("admin_status", adminStatus); + data.push_back(fvTuple); + } + try + { + setHostSubIntfAdminStatus(subIntfAlias, adminStatus); + } + catch (const std::runtime_error &e) + { + SWSS_LOG_NOTICE("Sub interface ip link set admin status %s failure. Runtime error: %s", adminStatus.c_str(), e.what()); + return false; + } + + // set STATE_DB port state + setSubIntfStateOk(subIntfAlias); + } + m_appIntfTableProducer.set(subIntfAlias.empty() ? alias : subIntfAlias, data); + m_stateIntfTable.hset(subIntfAlias.empty() ? alias : subIntfAlias, "vrf", vrf_name); } else if (op == DEL_COMMAND) { @@ -275,13 +502,24 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, { return false; } + setIntfVrf(alias, ""); + if (is_lo) { delLoopbackIntf(alias); } - m_appIntfTableProducer.del(alias); - m_stateIntfTable.del(alias); + + if (!subIntfAlias.empty()) + { + removeHostSubIntf(subIntfAlias); + m_subIntfList.erase(subIntfAlias); + + removeSubIntfState(subIntfAlias); + } + + m_appIntfTableProducer.del(subIntfAlias.empty() ? alias : subIntfAlias); + m_stateIntfTable.del(subIntfAlias.empty() ? alias : subIntfAlias); } else { @@ -304,7 +542,7 @@ bool IntfMgr::doIntfAddrTask(const vector& keys, if (op == SET_COMMAND) { /* - * Don't proceed if port/LAG/VLAN and intfGeneral are not ready yet. + * Don't proceed if port/LAG/VLAN/subport and intfGeneral is not ready yet. * The pending task will be checked periodically and retried. */ if (!isIntfStateOk(alias) || !isIntfCreated(alias)) diff --git a/cfgmgr/intfmgr.h b/cfgmgr/intfmgr.h index 4e82baef5e..7aab9fe447 100644 --- a/cfgmgr/intfmgr.h +++ b/cfgmgr/intfmgr.h @@ -7,6 +7,7 @@ #include #include +#include namespace swss { @@ -18,12 +19,13 @@ class IntfMgr : public Orch private: ProducerStateTable m_appIntfTableProducer; - Table m_cfgIntfTable, m_cfgVlanIntfTable; Table m_statePortTable, m_stateLagTable, m_stateVlanTable, m_stateVrfTable, m_stateIntfTable; + std::set m_subIntfList; + void setIntfIp(const std::string &alias, const std::string &opCmd, const IpPrefix &ipPrefix); void setIntfVrf(const std::string &alias, const std::string &vrfName); - bool doIntfGeneralTask(const std::vector& keys, const std::vector& data, const std::string& op); + bool doIntfGeneralTask(const std::vector& keys, std::vector data, const std::string& op); bool doIntfAddrTask(const std::vector& keys, const std::vector& data, const std::string& op); void doTask(Consumer &consumer); bool isIntfStateOk(const std::string &alias); @@ -32,6 +34,13 @@ class IntfMgr : public Orch int getIntfIpCount(const std::string &alias); void addLoopbackIntf(const std::string &alias); void delLoopbackIntf(const std::string &alias); + + void addHostSubIntf(const std::string&intf, const std::string &subIntf, const std::string &vlan); + void setHostSubIntfMtu(const std::string &subIntf, const std::string &mtu); + void setHostSubIntfAdminStatus(const std::string &subIntf, const std::string &admin_status); + void removeHostSubIntf(const std::string &subIntf); + void setSubIntfStateOk(const std::string &alias); + void removeSubIntfState(const std::string &alias); }; } diff --git a/cfgmgr/intfmgrd.cpp b/cfgmgr/intfmgrd.cpp index 54f16861a4..891373dcc7 100644 --- a/cfgmgr/intfmgrd.cpp +++ b/cfgmgr/intfmgrd.cpp @@ -45,6 +45,7 @@ int main(int argc, char **argv) CFG_LAG_INTF_TABLE_NAME, CFG_VLAN_INTF_TABLE_NAME, CFG_LOOPBACK_INTERFACE_TABLE_NAME, + CFG_VLAN_SUB_INTF_TABLE_NAME, }; DBConnector cfgDb(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); diff --git a/orchagent/bufferorch.cpp b/orchagent/bufferorch.cpp index ce2f2f2d1e..ee1de1899d 100644 --- a/orchagent/bufferorch.cpp +++ b/orchagent/bufferorch.cpp @@ -598,7 +598,7 @@ task_process_status BufferOrch::processQueue(Consumer &consumer) } /* -Input sample "BUFFER_PG_TABLE|Ethernet4,Ethernet45|10-15" +Input sample "BUFFER_PG|Ethernet4,Ethernet45|10-15" */ task_process_status BufferOrch::processPriorityGroup(Consumer &consumer) { diff --git a/orchagent/intfsorch.cpp b/orchagent/intfsorch.cpp index fc84a39752..f540224129 100644 --- a/orchagent/intfsorch.cpp +++ b/orchagent/intfsorch.cpp @@ -144,7 +144,7 @@ void IntfsOrch::decreaseRouterIntfsRefCount(const string &alias) alias.c_str(), m_syncdIntfses[alias].ref_count); } -bool IntfsOrch::setRouterIntfsMtu(Port &port) +bool IntfsOrch::setRouterIntfsMtu(const Port &port) { SWSS_LOG_ENTER(); @@ -165,6 +165,36 @@ bool IntfsOrch::setRouterIntfsMtu(Port &port) return true; } +bool IntfsOrch::setRouterIntfsAdminStatus(const Port &port) +{ + SWSS_LOG_ENTER(); + + sai_attribute_t attr; + attr.value.booldata = port.m_admin_state_up; + + attr.id = SAI_ROUTER_INTERFACE_ATTR_ADMIN_V4_STATE; + sai_status_t status = sai_router_intfs_api-> + set_router_interface_attribute(port.m_rif_id, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set router interface %s V4 admin status to %s, rv:%d", + port.m_alias.c_str(), port.m_admin_state_up == true ? "up" : "down", status); + return false; + } + + attr.id = SAI_ROUTER_INTERFACE_ATTR_ADMIN_V6_STATE; + status = sai_router_intfs_api-> + set_router_interface_attribute(port.m_rif_id, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set router interface %s V6 admin status to %s, rv:%d", + port.m_alias.c_str(), port.m_admin_state_up == true ? "up" : "down", status); + return false; + } + + return true; +} + set IntfsOrch:: getSubnetRoutes() { SWSS_LOG_ENTER(); @@ -182,7 +212,7 @@ set IntfsOrch:: getSubnetRoutes() return subnet_routes; } -bool IntfsOrch::setIntf(const string& alias, sai_object_id_t vrf_id, const IpPrefix *ip_prefix) +bool IntfsOrch::setIntf(const string& alias, sai_object_id_t vrf_id, const IpPrefix *ip_prefix, const bool adminUp, const uint32_t mtu) { SWSS_LOG_ENTER(); @@ -206,6 +236,35 @@ bool IntfsOrch::setIntf(const string& alias, sai_object_id_t vrf_id, const IpPre return false; } } + else + { + if (!ip_prefix && port.m_type == Port::SUBPORT) + { + // port represents a sub interface + // Change sub interface config at run time + bool attrChanged = false; + if (mtu && port.m_mtu != mtu) + { + port.m_mtu = mtu; + attrChanged = true; + + setRouterIntfsMtu(port); + } + + if (port.m_admin_state_up != adminUp) + { + port.m_admin_state_up = adminUp; + attrChanged = true; + + setRouterIntfsAdminStatus(port); + } + + if (attrChanged) + { + gPortsOrch->setPort(alias, port); + } + } + } if (!ip_prefix || m_syncdIntfses[alias].ip_addresses.count(*ip_prefix)) { @@ -290,6 +349,15 @@ bool IntfsOrch::removeIntf(const string& alias, sai_object_id_t vrf_id, const Ip gPortsOrch->decreasePortRefCount(alias); m_syncdIntfses.erase(alias); m_vrfOrch->decreaseVrfRefCount(vrf_id); + + if (port.m_type == Port::SUBPORT) + { + if (!gPortsOrch->removeSubPort(alias)) + { + return false; + } + } + return true; } else @@ -317,6 +385,14 @@ void IntfsOrch::doTask(Consumer &consumer) vector keys = tokenize(kfvKey(t), ':'); string alias(keys[0]); + + bool isSubIntf = false; + size_t found = alias.find(VLAN_SUB_INTERFACE_SEPARATOR); + if (found != string::npos) + { + isSubIntf = true; + } + IpPrefix ip_prefix; bool ip_prefix_in_key = false; bool is_lo = !alias.compare(0, strlen(LOOPBACK_PREFIX), LOOPBACK_PREFIX); @@ -329,7 +405,8 @@ void IntfsOrch::doTask(Consumer &consumer) const vector& data = kfvFieldsValues(t); string vrf_name = "", vnet_name = ""; - + uint32_t mtu; + bool adminUp; for (auto idx : data) { const auto &field = fvField(idx); @@ -342,6 +419,39 @@ void IntfsOrch::doTask(Consumer &consumer) { vnet_name = value; } + else if (field == "mtu") + { + try + { + mtu = static_cast(stoul(value)); + } + catch (const std::invalid_argument &e) + { + SWSS_LOG_ERROR("Invalid argument %s to %s()", value.c_str(), e.what()); + continue; + } + catch (const std::out_of_range &e) + { + SWSS_LOG_ERROR("Out of range argument %s to %s()", value.c_str(), e.what()); + continue; + } + } + else if (field == "admin_status") + { + if (value == "up") + { + adminUp = true; + } + else + { + adminUp = false; + + if (value != "down") + { + SWSS_LOG_WARN("Sub interface %s unknown admin status %s", alias.c_str(), value.c_str()); + } + } + } } if (alias == "eth0" || alias == "docker0") @@ -398,9 +508,20 @@ void IntfsOrch::doTask(Consumer &consumer) Port port; if (!gPortsOrch->getPort(alias, port)) { - /* TODO: Resolve the dependency relationship and add ref_count to port */ - it++; - continue; + if (isSubIntf) + { + if (!gPortsOrch->addSubPort(port, alias, adminUp, mtu)) + { + it++; + continue; + } + } + else + { + /* TODO: Resolve the dependency relationship and add ref_count to port */ + it++; + continue; + } } if (m_vnetInfses.find(alias) != m_vnetInfses.end()) @@ -416,7 +537,7 @@ void IntfsOrch::doTask(Consumer &consumer) it++; continue; } - if (!vnet_orch->setIntf(alias, vnet_name, ip_prefix_in_key ? &ip_prefix : nullptr)) + if (!vnet_orch->setIntf(alias, vnet_name, ip_prefix_in_key ? &ip_prefix : nullptr, adminUp, mtu)) { it++; continue; @@ -429,7 +550,7 @@ void IntfsOrch::doTask(Consumer &consumer) } else { - if (!setIntf(alias, vrf_id, ip_prefix_in_key ? &ip_prefix : nullptr)) + if (!setIntf(alias, vrf_id, ip_prefix_in_key ? &ip_prefix : nullptr, adminUp, mtu)) { it++; continue; @@ -560,35 +681,59 @@ bool IntfsOrch::addRouterIntfs(sai_object_id_t vrf_id, Port &port) case Port::PHY: case Port::LAG: attr.value.s32 = SAI_ROUTER_INTERFACE_TYPE_PORT; + attrs.push_back(attr); break; case Port::VLAN: attr.value.s32 = SAI_ROUTER_INTERFACE_TYPE_VLAN; + attrs.push_back(attr); + break; + case Port::SUBPORT: + attr.value.s32 = SAI_ROUTER_INTERFACE_TYPE_SUB_PORT; + attrs.push_back(attr); break; default: SWSS_LOG_ERROR("Unsupported port type: %d", port.m_type); break; } - attrs.push_back(attr); switch(port.m_type) { case Port::PHY: attr.id = SAI_ROUTER_INTERFACE_ATTR_PORT_ID; attr.value.oid = port.m_port_id; + attrs.push_back(attr); break; case Port::LAG: attr.id = SAI_ROUTER_INTERFACE_ATTR_PORT_ID; attr.value.oid = port.m_lag_id; + attrs.push_back(attr); break; case Port::VLAN: attr.id = SAI_ROUTER_INTERFACE_ATTR_VLAN_ID; attr.value.oid = port.m_vlan_info.vlan_oid; + attrs.push_back(attr); + break; + case Port::SUBPORT: + attr.id = SAI_ROUTER_INTERFACE_ATTR_PORT_ID; + attr.value.oid = port.m_parent_port_id; + attrs.push_back(attr); + + attr.id = SAI_ROUTER_INTERFACE_ATTR_OUTER_VLAN_ID; + attr.value.u16 = port.m_vlan_info.vlan_id; + attrs.push_back(attr); + + attr.id = SAI_ROUTER_INTERFACE_ATTR_ADMIN_V4_STATE; + attr.value.booldata = port.m_admin_state_up; + attrs.push_back(attr); + + attr.id = SAI_ROUTER_INTERFACE_ATTR_ADMIN_V6_STATE; + attr.value.booldata = port.m_admin_state_up; + attrs.push_back(attr); break; default: SWSS_LOG_ERROR("Unsupported port type: %d", port.m_type); break; } - attrs.push_back(attr); attr.id = SAI_ROUTER_INTERFACE_ATTR_MTU; attr.value.u32 = port.m_mtu; @@ -850,6 +995,9 @@ void IntfsOrch::doTask(SelectableTimer &timer) case Port::VLAN: type = "SAI_ROUTER_INTERFACE_TYPE_VLAN"; break; + case Port::SUBPORT: + type = "SAI_ROUTER_INTERFACE_TYPE_SUB_PORT"; + break; default: SWSS_LOG_ERROR("Unsupported port type: %d", it->m_type); type = ""; diff --git a/orchagent/intfsorch.h b/orchagent/intfsorch.h index 5ac0d047ed..22e41a4945 100644 --- a/orchagent/intfsorch.h +++ b/orchagent/intfsorch.h @@ -39,14 +39,15 @@ class IntfsOrch : public Orch void increaseRouterIntfsRefCount(const string&); void decreaseRouterIntfsRefCount(const string&); - bool setRouterIntfsMtu(Port &port); + bool setRouterIntfsMtu(const Port &port); + bool setRouterIntfsAdminStatus(const Port &port); std::set getSubnetRoutes(); void generateInterfaceMap(); void addRifToFlexCounter(const string&, const string&, const string&); void removeRifFromFlexCounter(const string&, const string&); - bool setIntf(const string& alias, sai_object_id_t vrf_id = gVirtualRouterId, const IpPrefix *ip_prefix = nullptr); + bool setIntf(const string& alias, sai_object_id_t vrf_id = gVirtualRouterId, const IpPrefix *ip_prefix = nullptr, const bool adminUp = true, const uint32_t mtu = 0); bool removeIntf(const string& alias, sai_object_id_t vrf_id = gVirtualRouterId, const IpPrefix *ip_prefix = nullptr); void addIp2MeRoute(sai_object_id_t vrf_id, const IpPrefix &ip_prefix); diff --git a/orchagent/orch.h b/orchagent/orch.h index 202aa91e21..1ea75b19d3 100644 --- a/orchagent/orch.h +++ b/orchagent/orch.h @@ -38,6 +38,7 @@ const char state_db_key_delimiter = '|'; #define CONFIGDB_KEY_SEPARATOR "|" #define DEFAULT_KEY_SEPARATOR ":" +#define VLAN_SUB_INTERFACE_SEPARATOR "." const int default_orch_pri = 0; diff --git a/orchagent/port.h b/orchagent/port.h index ad264a1db5..8950985523 100644 --- a/orchagent/port.h +++ b/orchagent/port.h @@ -44,6 +44,7 @@ class Port LOOPBACK, VLAN, LAG, + SUBPORT, UNKNOWN } ; @@ -86,8 +87,10 @@ class Port sai_object_id_t m_ingress_acl_table_group_id = 0; sai_object_id_t m_egress_acl_table_group_id = 0; vlan_members_t m_vlan_members; + sai_object_id_t m_parent_port_id = 0; sai_port_oper_status_t m_oper_status = SAI_PORT_OPER_STATUS_UNKNOWN; std::set m_members; + std::set m_child_ports; std::vector m_queue_ids; std::vector m_priority_group_ids; sai_port_priority_flow_control_mode_t m_pfc_asym = SAI_PORT_PRIORITY_FLOW_CONTROL_MODE_COMBINED; diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index 8d12d1ff91..16ea7099e8 100644 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -40,6 +40,7 @@ extern BufferOrch *gBufferOrch; #define VLAN_PREFIX "Vlan" #define DEFAULT_VLAN_ID 1 +#define MAX_VALID_VLAN_ID 4094 #define PORT_FLEX_STAT_COUNTER_POLL_MSECS "1000" #define QUEUE_FLEX_STAT_COUNTER_POLL_MSECS "10000" #define QUEUE_WATERMARK_FLEX_STAT_COUNTER_POLL_MSECS "10000" @@ -548,6 +549,101 @@ bool PortsOrch::getAclBindPortId(string alias, sai_object_id_t &port_id) } } +bool PortsOrch::addSubPort(Port &port, const string &alias, const bool &adminUp, const uint32_t &mtu) +{ + size_t found = alias.find(VLAN_SUB_INTERFACE_SEPARATOR); + if (found == string::npos) + { + SWSS_LOG_ERROR("%s is not a sub interface", alias.c_str()); + return false; + } + string parentAlias = alias.substr(0, found); + string vlanId = alias.substr(found + 1); + sai_vlan_id_t vlan_id; + try + { + vlan_id = static_cast(stoul(vlanId)); + } + catch (const std::invalid_argument &e) + { + SWSS_LOG_ERROR("Invalid argument %s to %s()", vlanId.c_str(), e.what()); + return false; + } + catch (const std::out_of_range &e) + { + SWSS_LOG_ERROR("Out of range argument %s to %s()", vlanId.c_str(), e.what()); + return false; + } + if (vlan_id > MAX_VALID_VLAN_ID) + { + SWSS_LOG_ERROR("sub interface %s Port object creation: invalid VLAN id %u", alias.c_str(), vlan_id); + return false; + } + + auto it = m_portList.find(parentAlias); + if (it == m_portList.end()) + { + SWSS_LOG_NOTICE("Sub interface %s Port object creation: parent port %s is not ready", alias.c_str(), parentAlias.c_str()); + return false; + } + Port &parentPort = it->second; + + Port p(alias, Port::SUBPORT); + + p.m_admin_state_up = adminUp; + + if (mtu) + { + p.m_mtu = mtu; + } + else + { + SWSS_LOG_NOTICE("Sub interface %s inherits mtu size %u from parent port %s", alias.c_str(), parentPort.m_mtu, parentAlias.c_str()); + p.m_mtu = parentPort.m_mtu; + } + + p.m_parent_port_id = parentPort.m_port_id; + p.m_vlan_info.vlan_id = vlan_id; + + parentPort.m_child_ports.insert(p.m_alias); + + m_portList[alias] = p; + port = p; + return true; +} + +bool PortsOrch::removeSubPort(const string &alias) +{ + auto it = m_portList.find(alias); + if (it == m_portList.end()) + { + SWSS_LOG_WARN("Sub interface %s Port object not found", alias.c_str()); + return false; + } + Port &port = it->second; + + if (port.m_type != Port::SUBPORT) + { + SWSS_LOG_ERROR("Sub interface %s not of type sub port", alias.c_str()); + return false; + } + + Port parentPort; + if (!getPort(port.m_parent_port_id, parentPort)) + { + SWSS_LOG_WARN("Sub interface %s: parent Port object not found", alias.c_str()); + } + + if (!parentPort.m_child_ports.erase(alias)) + { + SWSS_LOG_WARN("Sub interface %s not associated to parent port %s", alias.c_str(), parentPort.m_alias.c_str()); + } + m_portList[parentPort.m_alias] = parentPort; + + m_portList.erase(it); + return true; +} + void PortsOrch::setPort(string alias, Port p) { m_portList[alias] = p; diff --git a/orchagent/portsorch.h b/orchagent/portsorch.h index 75831fdaf9..0fe3bad480 100644 --- a/orchagent/portsorch.h +++ b/orchagent/portsorch.h @@ -88,6 +88,9 @@ class PortsOrch : public Orch, public Subject void refreshPortStatus(); bool removeAclTableGroup(const Port &p); + + bool addSubPort(Port &port, const string &alias, const bool &adminUp = true, const uint32_t &mtu = 0); + bool removeSubPort(const string &alias); private: unique_ptr m_counterTable; unique_ptr
m_portTable; diff --git a/orchagent/vnetorch.cpp b/orchagent/vnetorch.cpp index 6552f79cc5..0dae814e86 100644 --- a/orchagent/vnetorch.cpp +++ b/orchagent/vnetorch.cpp @@ -1338,7 +1338,7 @@ VNetOrch::VNetOrch(DBConnector *db, const std::string& tableName, VNET_EXEC op) } } -bool VNetOrch::setIntf(const string& alias, const string name, const IpPrefix *prefix) +bool VNetOrch::setIntf(const string& alias, const string name, const IpPrefix *prefix, const bool adminUp, const uint32_t mtu) { SWSS_LOG_ENTER(); @@ -1353,7 +1353,7 @@ bool VNetOrch::setIntf(const string& alias, const string name, const IpPrefix *p auto *vnet_obj = getTypePtr(name); sai_object_id_t vrf_id = vnet_obj->getVRidIngress(); - return gIntfsOrch->setIntf(alias, vrf_id, prefix); + return gIntfsOrch->setIntf(alias, vrf_id, prefix, adminUp, mtu); } else { diff --git a/orchagent/vnetorch.h b/orchagent/vnetorch.h index 49f3413d1f..965b8cadb8 100644 --- a/orchagent/vnetorch.h +++ b/orchagent/vnetorch.h @@ -305,7 +305,7 @@ class VNetOrch : public Orch2 public: VNetOrch(DBConnector *db, const std::string&, VNET_EXEC op = VNET_EXEC::VNET_EXEC_VRF); - bool setIntf(const string& alias, const string name, const IpPrefix *prefix = nullptr); + bool setIntf(const string& alias, const string name, const IpPrefix *prefix = nullptr, const bool adminUp = true, const uint32_t mtu = 0); bool delIntf(const string& alias, const string name, const IpPrefix *prefix = nullptr); bool isVnetExists(const std::string& name) const diff --git a/tests/test_sub_port_intf.py b/tests/test_sub_port_intf.py new file mode 100644 index 0000000000..36268e8f80 --- /dev/null +++ b/tests/test_sub_port_intf.py @@ -0,0 +1,381 @@ +import pytest +import time +import json +from swsscommon import swsscommon + +CFG_VLAN_SUB_INTF_TABLE_NAME = "VLAN_SUB_INTERFACE" +CFG_PORT_TABLE_NAME = "PORT" + +STATE_PORT_TABLE_NAME = "PORT_TABLE" +STATE_INTERFACE_TABLE_NAME = "INTERFACE_TABLE" + +APP_INTF_TABLE_NAME = "INTF_TABLE" + +ASIC_RIF_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE" +ASIC_ROUTE_ENTRY_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY" + +ADMIN_STATUS = "admin_status" + + +class TestSubPortIntf(object): + PHYSICAL_PORT_UNDER_TEST = "Ethernet64" + SUB_PORT_INTERFACE_UNDER_TEST = "Ethernet64.10" + + IPV4_ADDR_UNDER_TEST = "10.0.0.33/31" + IPV4_TOME_UNDER_TEST = "10.0.0.33/32" + IPV4_SUBNET_UNDER_TEST = "10.0.0.32/31" + + IPV6_ADDR_UNDER_TEST = "fc00::41/126" + IPV6_TOME_UNDER_TEST = "fc00::41/128" + IPV6_SUBNET_UNDER_TEST = "fc00::40/126" + + def connect_dbs(self, dvs): + self.config_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) + self.state_db = swsscommon.DBConnector(swsscommon.STATE_DB, dvs.redis_sock, 0) + self.appl_db = swsscommon.DBConnector(swsscommon.APPL_DB, dvs.redis_sock, 0) + self.asic_db = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) + + def set_parent_port_admin_status(self, port_name, status): + fvs = swsscommon.FieldValuePairs([(ADMIN_STATUS, status)]) + + tbl = swsscommon.Table(self.config_db, CFG_PORT_TABLE_NAME) + tbl.set(port_name, fvs) + + time.sleep(1) + + def create_sub_port_intf_profile(self, sub_port_intf_name): + fvs = swsscommon.FieldValuePairs([(ADMIN_STATUS, "up")]) + + tbl = swsscommon.Table(self.config_db, CFG_VLAN_SUB_INTF_TABLE_NAME) + tbl.set(sub_port_intf_name, fvs) + + time.sleep(1) + + def add_sub_port_intf_ip_addr(self, sub_port_intf_name, ip_addr): + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + + tbl = swsscommon.Table(self.config_db, CFG_VLAN_SUB_INTF_TABLE_NAME) + tbl.set(sub_port_intf_name + "|" + ip_addr, fvs) + + time.sleep(2) + + def set_sub_port_intf_admin_status(self, sub_port_intf_name, status): + fvs = swsscommon.FieldValuePairs([(ADMIN_STATUS, status)]) + + tbl = swsscommon.Table(self.config_db, CFG_VLAN_SUB_INTF_TABLE_NAME) + tbl.set(sub_port_intf_name, fvs) + + time.sleep(1) + + def remove_sub_port_intf_profile(self, sub_port_intf_name): + tbl = swsscommon.Table(self.config_db, CFG_VLAN_SUB_INTF_TABLE_NAME) + tbl._del(sub_port_intf_name) + + time.sleep(1) + + def remove_sub_port_intf_ip_addr(self, sub_port_intf_name, ip_addr): + tbl = swsscommon.Table(self.config_db, CFG_VLAN_SUB_INTF_TABLE_NAME) + tbl._del(sub_port_intf_name + "|" + ip_addr) + + time.sleep(1) + + def get_oids(self, table): + tbl = swsscommon.Table(self.asic_db, table) + return set(tbl.getKeys()) + + def get_newly_created_oid(self, table, old_oids): + new_oids = self.get_oids(table) + oid = list(new_oids - old_oids) + assert len(oid) == 1, "Wrong # of newly created oids: %d, expected #: 1." % (len(oid)) + return oid[0] + + def check_sub_port_intf_key_existence(self, db, table_name, key): + tbl = swsscommon.Table(db, table_name) + + keys = tbl.getKeys() + assert key in keys, "Key %s not exist" % (key) + + def check_sub_port_intf_fvs(self, db, table_name, key, fv_dict): + tbl = swsscommon.Table(db, table_name) + + keys = tbl.getKeys() + assert key in keys + + (status, fvs) = tbl.get(key) + assert status == True + assert len(fvs) >= len(fv_dict) + + for field, value in fvs: + if field in fv_dict: + assert fv_dict[field] == value, \ + "Wrong value for field %s: %s, expected value: %s" % (field, value, fv_dict[field]) + + def check_sub_port_intf_route_entries(self): + ipv4_ip2me_found = False + ipv4_subnet_found = False + ipv6_ip2me_found = False + ipv6_subnet_found = False + + tbl = swsscommon.Table(self.asic_db, ASIC_ROUTE_ENTRY_TABLE) + raw_route_entries = tbl.getKeys() + + for raw_route_entry in raw_route_entries: + route_entry = json.loads(raw_route_entry) + if route_entry["dest"] == self.IPV4_TOME_UNDER_TEST: + ipv4_ip2me_found = True + elif route_entry["dest"] == self.IPV4_SUBNET_UNDER_TEST: + ipv4_subnet_found = True + elif route_entry["dest"] == self.IPV6_TOME_UNDER_TEST: + ipv6_ip2me_found = True + elif route_entry["dest"] == self.IPV6_SUBNET_UNDER_TEST: + ipv6_subnet_found = True + + assert ipv4_ip2me_found and ipv4_subnet_found and ipv6_ip2me_found and ipv6_subnet_found + + def check_sub_port_intf_key_removal(self, db, table_name, key): + tbl = swsscommon.Table(db, table_name) + + keys = tbl.getKeys() + assert key not in keys, "Key %s not removed" % (key) + + def check_sub_port_intf_route_entries_removal(self, removed_route_entries): + tbl = swsscommon.Table(self.asic_db, ASIC_ROUTE_ENTRY_TABLE) + raw_route_entries = tbl.getKeys() + for raw_route_entry in raw_route_entries: + route_entry = json.loads(raw_route_entry) + assert route_entry["dest"] not in removed_route_entries + + def test_sub_port_intf_creation(self, dvs): + self.connect_dbs(dvs) + + old_rif_oids = self.get_oids(ASIC_RIF_TABLE) + + self.set_parent_port_admin_status(self.PHYSICAL_PORT_UNDER_TEST, "up") + self.create_sub_port_intf_profile(self.SUB_PORT_INTERFACE_UNDER_TEST) + + # Verify that sub port interface state ok is pushed to STATE_DB by Intfmgrd + fv_dict = { + "state": "ok", + } + self.check_sub_port_intf_fvs(self.state_db, STATE_PORT_TABLE_NAME, self.SUB_PORT_INTERFACE_UNDER_TEST, fv_dict) + + # Verify that sub port interface configuration is synced to APPL_DB INTF_TABLE by Intfmgrd + fv_dict = { + ADMIN_STATUS: "up", + } + self.check_sub_port_intf_fvs(self.appl_db, APP_INTF_TABLE_NAME, self.SUB_PORT_INTERFACE_UNDER_TEST, fv_dict) + + # Verify that a sub port router interface entry is created in ASIC_DB + fv_dict = { + "SAI_ROUTER_INTERFACE_ATTR_TYPE": "SAI_ROUTER_INTERFACE_TYPE_SUB_PORT", + "SAI_ROUTER_INTERFACE_ATTR_OUTER_VLAN_ID": "10", + "SAI_ROUTER_INTERFACE_ATTR_ADMIN_V4_STATE": "true", + "SAI_ROUTER_INTERFACE_ATTR_ADMIN_V6_STATE": "true", + "SAI_ROUTER_INTERFACE_ATTR_MTU": "9100", + } + rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) + self.check_sub_port_intf_fvs(self.asic_db, ASIC_RIF_TABLE, rif_oid, fv_dict) + + # Remove a sub port interface + self.remove_sub_port_intf_profile(self.SUB_PORT_INTERFACE_UNDER_TEST) + + def test_sub_port_intf_add_ip_addrs(self, dvs): + self.connect_dbs(dvs) + + old_rif_oids = self.get_oids(ASIC_RIF_TABLE) + + self.set_parent_port_admin_status(self.PHYSICAL_PORT_UNDER_TEST, "up") + self.create_sub_port_intf_profile(self.SUB_PORT_INTERFACE_UNDER_TEST) + + self.add_sub_port_intf_ip_addr(self.SUB_PORT_INTERFACE_UNDER_TEST, self.IPV4_ADDR_UNDER_TEST) + self.add_sub_port_intf_ip_addr(self.SUB_PORT_INTERFACE_UNDER_TEST, self.IPV6_ADDR_UNDER_TEST) + + rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) + + # Verify that ip address state ok is pushed to STATE_DB INTERFACE_TABLE by Intfmgrd + fv_dict = { + "state": "ok", + } + self.check_sub_port_intf_fvs(self.state_db, STATE_INTERFACE_TABLE_NAME, \ + self.SUB_PORT_INTERFACE_UNDER_TEST + "|" + self.IPV4_ADDR_UNDER_TEST, fv_dict) + self.check_sub_port_intf_fvs(self.state_db, STATE_INTERFACE_TABLE_NAME, \ + self.SUB_PORT_INTERFACE_UNDER_TEST + "|" + self.IPV6_ADDR_UNDER_TEST, fv_dict) + + # Verify that ip address configuration is synced to APPL_DB INTF_TABLE by Intfmgrd + fv_dict = { + "scope": "global", + "family": "IPv4", + } + self.check_sub_port_intf_fvs(self.appl_db, APP_INTF_TABLE_NAME, \ + self.SUB_PORT_INTERFACE_UNDER_TEST + ":" + self.IPV4_ADDR_UNDER_TEST, fv_dict) + fv_dict["family"] = "IPv6" + self.check_sub_port_intf_fvs(self.appl_db, APP_INTF_TABLE_NAME, \ + self.SUB_PORT_INTERFACE_UNDER_TEST + ":" + self.IPV6_ADDR_UNDER_TEST, fv_dict) + + # Verify that an IPv4 ip2me route entry is created in ASIC_DB + # Verify that an IPv4 subnet route entry is created in ASIC_DB + # Verify that an IPv6 ip2me route entry is created in ASIC_DB + # Verify that an IPv6 subnet route entry is created in ASIC_DB + self.check_sub_port_intf_route_entries() + + # Remove IP addresses + self.remove_sub_port_intf_ip_addr(self.SUB_PORT_INTERFACE_UNDER_TEST, self.IPV4_ADDR_UNDER_TEST) + self.remove_sub_port_intf_ip_addr(self.SUB_PORT_INTERFACE_UNDER_TEST, self.IPV6_ADDR_UNDER_TEST) + # Remove a sub port interface + self.remove_sub_port_intf_profile(self.SUB_PORT_INTERFACE_UNDER_TEST) + + def test_sub_port_intf_admin_status_change(self, dvs): + self.connect_dbs(dvs) + + old_rif_oids = self.get_oids(ASIC_RIF_TABLE) + + self.set_parent_port_admin_status(self.PHYSICAL_PORT_UNDER_TEST, "up") + self.create_sub_port_intf_profile(self.SUB_PORT_INTERFACE_UNDER_TEST) + + self.add_sub_port_intf_ip_addr(self.SUB_PORT_INTERFACE_UNDER_TEST, self.IPV4_ADDR_UNDER_TEST) + self.add_sub_port_intf_ip_addr(self.SUB_PORT_INTERFACE_UNDER_TEST, self.IPV6_ADDR_UNDER_TEST) + + fv_dict = { + ADMIN_STATUS: "up", + } + self.check_sub_port_intf_fvs(self.appl_db, APP_INTF_TABLE_NAME, self.SUB_PORT_INTERFACE_UNDER_TEST, fv_dict) + + fv_dict = { + "SAI_ROUTER_INTERFACE_ATTR_ADMIN_V4_STATE": "true", + "SAI_ROUTER_INTERFACE_ATTR_ADMIN_V6_STATE": "true", + "SAI_ROUTER_INTERFACE_ATTR_MTU": "9100", + } + rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) + self.check_sub_port_intf_fvs(self.asic_db, ASIC_RIF_TABLE, rif_oid, fv_dict) + + # Change sub port interface admin status to down + self.set_sub_port_intf_admin_status(self.SUB_PORT_INTERFACE_UNDER_TEST, "down") + + # Verify that sub port interface admin status change is synced to APPL_DB INTF_TABLE by Intfmgrd + fv_dict = { + ADMIN_STATUS: "down", + } + self.check_sub_port_intf_fvs(self.appl_db, APP_INTF_TABLE_NAME, self.SUB_PORT_INTERFACE_UNDER_TEST, fv_dict) + + # Verify that sub port router interface entry in ASIC_DB has the updated admin status + fv_dict = { + "SAI_ROUTER_INTERFACE_ATTR_ADMIN_V4_STATE": "false", + "SAI_ROUTER_INTERFACE_ATTR_ADMIN_V6_STATE": "false", + "SAI_ROUTER_INTERFACE_ATTR_MTU": "9100", + } + rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) + self.check_sub_port_intf_fvs(self.asic_db, ASIC_RIF_TABLE, rif_oid, fv_dict) + + # Change sub port interface admin status to up + self.set_sub_port_intf_admin_status(self.SUB_PORT_INTERFACE_UNDER_TEST, "up") + + # Verify that sub port interface admin status change is synced to APPL_DB INTF_TABLE by Intfmgrd + fv_dict = { + ADMIN_STATUS: "up", + } + self.check_sub_port_intf_fvs(self.appl_db, APP_INTF_TABLE_NAME, self.SUB_PORT_INTERFACE_UNDER_TEST, fv_dict) + + # Verify that sub port router interface entry in ASIC_DB has the updated admin status + fv_dict = { + "SAI_ROUTER_INTERFACE_ATTR_ADMIN_V4_STATE": "true", + "SAI_ROUTER_INTERFACE_ATTR_ADMIN_V6_STATE": "true", + "SAI_ROUTER_INTERFACE_ATTR_MTU": "9100", + } + rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) + self.check_sub_port_intf_fvs(self.asic_db, ASIC_RIF_TABLE, rif_oid, fv_dict) + + # Remove IP addresses + self.remove_sub_port_intf_ip_addr(self.SUB_PORT_INTERFACE_UNDER_TEST, self.IPV4_ADDR_UNDER_TEST) + self.remove_sub_port_intf_ip_addr(self.SUB_PORT_INTERFACE_UNDER_TEST, self.IPV6_ADDR_UNDER_TEST) + # Remove a sub port interface + self.remove_sub_port_intf_profile(self.SUB_PORT_INTERFACE_UNDER_TEST) + + def test_sub_port_intf_remove_ip_addrs(self, dvs): + self.connect_dbs(dvs) + + old_rif_oids = self.get_oids(ASIC_RIF_TABLE) + + self.set_parent_port_admin_status(self.PHYSICAL_PORT_UNDER_TEST, "up") + self.create_sub_port_intf_profile(self.SUB_PORT_INTERFACE_UNDER_TEST) + + self.add_sub_port_intf_ip_addr(self.SUB_PORT_INTERFACE_UNDER_TEST, self.IPV4_ADDR_UNDER_TEST) + self.add_sub_port_intf_ip_addr(self.SUB_PORT_INTERFACE_UNDER_TEST, self.IPV6_ADDR_UNDER_TEST) + + rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) + + # Remove IPv4 address + self.remove_sub_port_intf_ip_addr(self.SUB_PORT_INTERFACE_UNDER_TEST, self.IPV4_ADDR_UNDER_TEST) + + # Verify that IPv4 address state ok is removed from STATE_DB INTERFACE_TABLE by Intfmgrd + self.check_sub_port_intf_key_removal(self.state_db, STATE_INTERFACE_TABLE_NAME, \ + self.SUB_PORT_INTERFACE_UNDER_TEST + "|" + self.IPV4_ADDR_UNDER_TEST) + + # Verify that IPv4 address configuration is removed from APPL_DB INTF_TABLE by Intfmgrd + self.check_sub_port_intf_key_removal(self.appl_db, APP_INTF_TABLE_NAME, \ + self.SUB_PORT_INTERFACE_UNDER_TEST + ":" + self.IPV4_ADDR_UNDER_TEST) + + # Verify that IPv4 subnet route entry is removed from ASIC_DB + # Verify that IPv4 ip2me route entry is removed from ASIC_DB + removed_route_entries = set([self.IPV4_TOME_UNDER_TEST, self.IPV4_SUBNET_UNDER_TEST]) + self.check_sub_port_intf_route_entries_removal(removed_route_entries) + + # Remove IPv6 address + self.remove_sub_port_intf_ip_addr(self.SUB_PORT_INTERFACE_UNDER_TEST, self.IPV6_ADDR_UNDER_TEST) + + # Verify that IPv6 address state ok is removed from STATE_DB INTERFACE_TABLE by Intfmgrd + self.check_sub_port_intf_key_removal(self.state_db, STATE_INTERFACE_TABLE_NAME, \ + self.SUB_PORT_INTERFACE_UNDER_TEST + "|" + self.IPV6_ADDR_UNDER_TEST) + + # Verify that IPv6 address configuration is removed from APPL_DB INTF_TABLE by Intfmgrd + self.check_sub_port_intf_key_removal(self.appl_db, APP_INTF_TABLE_NAME, \ + self.SUB_PORT_INTERFACE_UNDER_TEST + ":" + self.IPV6_ADDR_UNDER_TEST) + + # Verify that IPv6 subnet route entry is removed from ASIC_DB + # Verify that IPv6 ip2me route entry is removed from ASIC_DB + removed_route_entries.update([self.IPV6_TOME_UNDER_TEST, self.IPV6_SUBNET_UNDER_TEST]) + self.check_sub_port_intf_route_entries_removal(removed_route_entries) + + # Verify that sub port router interface entry still exists in ASIC_DB + self.check_sub_port_intf_key_existence(self.asic_db, ASIC_RIF_TABLE, rif_oid) + + # Remove a sub port interface + self.remove_sub_port_intf_profile(self.SUB_PORT_INTERFACE_UNDER_TEST) + + def test_sub_port_intf_removal(self, dvs): + self.connect_dbs(dvs) + + old_rif_oids = self.get_oids(ASIC_RIF_TABLE) + + self.set_parent_port_admin_status(self.PHYSICAL_PORT_UNDER_TEST, "up") + self.create_sub_port_intf_profile(self.SUB_PORT_INTERFACE_UNDER_TEST) + + self.add_sub_port_intf_ip_addr(self.SUB_PORT_INTERFACE_UNDER_TEST, self.IPV4_ADDR_UNDER_TEST) + self.add_sub_port_intf_ip_addr(self.SUB_PORT_INTERFACE_UNDER_TEST, self.IPV6_ADDR_UNDER_TEST) + + rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) + + fv_dict = { + "state": "ok", + } + self.check_sub_port_intf_fvs(self.state_db, STATE_PORT_TABLE_NAME, self.SUB_PORT_INTERFACE_UNDER_TEST, fv_dict) + + fv_dict = { + ADMIN_STATUS: "up", + } + self.check_sub_port_intf_fvs(self.appl_db, APP_INTF_TABLE_NAME, self.SUB_PORT_INTERFACE_UNDER_TEST, fv_dict) + + # Remove IP addresses + self.remove_sub_port_intf_ip_addr(self.SUB_PORT_INTERFACE_UNDER_TEST, self.IPV4_ADDR_UNDER_TEST) + self.remove_sub_port_intf_ip_addr(self.SUB_PORT_INTERFACE_UNDER_TEST, self.IPV6_ADDR_UNDER_TEST) + + # Remove a sub port interface + self.remove_sub_port_intf_profile(self.SUB_PORT_INTERFACE_UNDER_TEST) + + # Verify that sub port interface state ok is removed from STATE_DB by Intfmgrd + self.check_sub_port_intf_key_removal(self.state_db, STATE_PORT_TABLE_NAME, self.SUB_PORT_INTERFACE_UNDER_TEST) + + # Verify that sub port interface configuration is removed from APPL_DB INTF_TABLE by Intfmgrd + self.check_sub_port_intf_key_removal(self.appl_db, APP_INTF_TABLE_NAME, self.SUB_PORT_INTERFACE_UNDER_TEST) + + # Verify that sub port router interface entry is removed from ASIC_DB + self.check_sub_port_intf_key_removal(self.asic_db, ASIC_RIF_TABLE, rif_oid) From f3547983a1e2c67bdacb4e87593e65f20b483d8e Mon Sep 17 00:00:00 2001 From: Stepan Blyshchak <38952541+stepanblyschak@users.noreply.github.com> Date: Sat, 9 Nov 2019 03:36:23 +0200 Subject: [PATCH 17/63] [tests] fix build agains real SAI (#1123) Signed-off-by: Stepan Blyschak --- tests/Makefile.am | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Makefile.am b/tests/Makefile.am index 0196e0a1a2..0b6831be97 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -2,7 +2,9 @@ CFLAGS_SAI = -I /usr/include/sai TESTS = tests +if !HAVE_SAI SUBDIRS = mock_tests +endif noinst_PROGRAMS = tests From 5d4e8bb1eaaa57427fc8ee080c49710f90b57528 Mon Sep 17 00:00:00 2001 From: Ze Gan Date: Tue, 12 Nov 2019 10:52:46 +0800 Subject: [PATCH 18/63] Fix traceroute issue (#1113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add commands to ensure that the ‘mac’ doesn’t change if an interface is enslaved to this master --- cfgmgr/vxlanmgr.cpp | 47 +++++++++++++++++++++++++++++++++++++++++++++ cfgmgr/vxlanmgr.h | 5 ++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/cfgmgr/vxlanmgr.cpp b/cfgmgr/vxlanmgr.cpp index 6706407bdb..e06321d516 100644 --- a/cfgmgr/vxlanmgr.cpp +++ b/cfgmgr/vxlanmgr.cpp @@ -24,6 +24,9 @@ using namespace swss; #define VXLAN "vxlan" #define VXLAN_IF "vxlan_if" +#define SWITCH "switch" +#define VXLAN_ROUTER_MAC "vxlan_router_mac" + #define VXLAN_NAME_PREFIX "Vxlan" #define VXLAN_IF_NAME_PREFIX "Brvxlan" @@ -86,6 +89,16 @@ static int cmdAddVxlanIntoVxlanIf(const swss::VxlanMgr::VxlanInfo & info, std::s << shellquote(info.m_vxlanIf) << " " << shellquote(info.m_vxlan); + if (!info.m_macAddress.empty()) + { + // Change the MAC address of Vxlan bridge interface to ensure it's same with switch's. + // Otherwise it will not response traceroute packets. + // ip link set dev {{VXLAN_IF}} address {{MAC_ADDRESS}} + cmd << " && " IP_CMD " link set dev " + << shellquote(info.m_vxlanIf) + << " address " + << shellquote(info.m_macAddress); + } return swss::exec(cmd.str(), res); } @@ -155,6 +168,7 @@ VxlanMgr::VxlanMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, Orch(cfgDb, tables), m_appVxlanTunnelTable(appDb, APP_VXLAN_TUNNEL_TABLE_NAME), m_appVxlanTunnelMapTable(appDb, APP_VXLAN_TUNNEL_MAP_TABLE_NAME), + m_appSwitchTable(appDb, APP_SWITCH_TABLE_NAME), m_cfgVxlanTunnelTable(cfgDb, CFG_VXLAN_TUNNEL_TABLE_NAME), m_cfgVnetTable(cfgDb, CFG_VNET_TABLE_NAME), m_stateVrfTable(stateDb, STATE_VRF_TABLE_NAME), @@ -282,6 +296,16 @@ bool VxlanMgr::doVxlanCreateTask(const KeyOpFieldsValuesTuple & t) // Suspend this message util the vrf is created return false; } + + // If the mac address has been set + auto macAddress = getVxlanRouterMacAddress(); + if (macAddress.first) + { + SWSS_LOG_DEBUG("Mac address is not ready"); + // Suspend this message util the mac address is set + return false; + } + info.m_macAddress = macAddress.second; auto sourceIp = std::find_if( it->second.begin(), @@ -432,6 +456,29 @@ bool VxlanMgr::isVxlanStateOk(const std::string & vxlanName) return false; } +std::pair VxlanMgr::getVxlanRouterMacAddress() +{ + std::vector temp; + + if (m_appSwitchTable.get(SWITCH, temp)) + { + auto itr = std::find_if( + temp.begin(), + temp.end(), + [](const FieldValueTuple &fvt) { return fvt.first == VXLAN_ROUTER_MAC; }); + if (itr != temp.end() && !(itr->second.empty())) + { + SWSS_LOG_DEBUG("Mac address %s is ready", itr->second.c_str()); + return std::make_pair(true, itr->second); + } + SWSS_LOG_DEBUG("Mac address will be automatically set"); + return std::make_pair(true, ""); + } + + SWSS_LOG_DEBUG("Mac address is not ready"); + return std::make_pair(false, ""); +} + bool VxlanMgr::createVxlan(const VxlanInfo & info) { SWSS_LOG_ENTER(); diff --git a/cfgmgr/vxlanmgr.h b/cfgmgr/vxlanmgr.h index a916bf8bdd..d4a1fb0dc2 100644 --- a/cfgmgr/vxlanmgr.h +++ b/cfgmgr/vxlanmgr.h @@ -8,6 +8,7 @@ #include #include #include +#include namespace swss { @@ -25,6 +26,7 @@ class VxlanMgr : public Orch std::string m_vni; std::string m_vxlan; std::string m_vxlanIf; + std::string m_macAddress; } VxlanInfo; ~VxlanMgr(); private: @@ -47,6 +49,7 @@ class VxlanMgr : public Orch */ bool isVrfStateOk(const std::string & vrfName); bool isVxlanStateOk(const std::string & vxlanName); + std::pair getVxlanRouterMacAddress(); bool createVxlan(const VxlanInfo & info); bool deleteVxlan(const VxlanInfo & info); @@ -54,7 +57,7 @@ class VxlanMgr : public Orch void clearAllVxlanDevices(); ProducerStateTable m_appVxlanTunnelTable,m_appVxlanTunnelMapTable; - Table m_cfgVxlanTunnelTable,m_cfgVnetTable,m_stateVrfTable,m_stateVxlanTable; + Table m_cfgVxlanTunnelTable,m_cfgVnetTable,m_stateVrfTable,m_stateVxlanTable, m_appSwitchTable; /* * Vxlan Tunnel Cache From 190373a7c5a5ba0aebe27106b7656c8b83230c49 Mon Sep 17 00:00:00 2001 From: shine4chen <37530989+shine4chen@users.noreply.github.com> Date: Wed, 13 Nov 2019 00:32:33 +0800 Subject: [PATCH 19/63] [portsorch]: add support to set mac-address learning attribute on bridge-port (#809) * add support to set mac-address learning attribute on bridge-port Signed-off-by: shine.chen --- cfgmgr/portmgr.cpp | 23 ++++- cfgmgr/portmgr.h | 1 + cfgmgr/teammgr.cpp | 23 +++++ cfgmgr/teammgr.h | 1 + orchagent/port.h | 1 + orchagent/portsorch.cpp | 121 ++++++++++++++++++++++- orchagent/portsorch.h | 3 +- tests/test_port_mac_learn.py | 181 +++++++++++++++++++++++++++++++++++ 8 files changed, 350 insertions(+), 4 deletions(-) create mode 100644 tests/test_port_mac_learn.py diff --git a/cfgmgr/portmgr.cpp b/cfgmgr/portmgr.cpp index 765198833a..94a1994310 100644 --- a/cfgmgr/portmgr.cpp +++ b/cfgmgr/portmgr.cpp @@ -55,6 +55,17 @@ bool PortMgr::setPortAdminStatus(const string &alias, const bool up) return true; } +bool PortMgr::setPortLearnMode(const string &alias, const string &learn_mode) +{ + // Set the port MAC learn mode in application database + vector fvs; + FieldValueTuple fv("learn_mode", learn_mode); + fvs.push_back(fv); + m_appPortTable.set(alias, fvs); + + return true; +} + bool PortMgr::isPortStateOk(const string &alias) { vector temp; @@ -91,7 +102,7 @@ void PortMgr::doTask(Consumer &consumer) continue; } - string admin_status, mtu; + string admin_status, mtu, learn_mode; bool configured = (m_portList.find(alias) != m_portList.end()); @@ -116,6 +127,10 @@ void PortMgr::doTask(Consumer &consumer) { admin_status = fvValue(i); } + else if (fvField(i) == "learn_mode") + { + learn_mode = fvValue(i); + } } if (!mtu.empty()) @@ -129,6 +144,12 @@ void PortMgr::doTask(Consumer &consumer) setPortAdminStatus(alias, admin_status == "up"); SWSS_LOG_NOTICE("Configure %s admin status to %s", alias.c_str(), admin_status.c_str()); } + + if (!learn_mode.empty()) + { + setPortLearnMode(alias, learn_mode); + SWSS_LOG_NOTICE("Configure %s MAC learn mode to %s", alias.c_str(), learn_mode.c_str()); + } } it = consumer.m_toSync.erase(it); diff --git a/cfgmgr/portmgr.h b/cfgmgr/portmgr.h index 971f00024c..a9724365a7 100644 --- a/cfgmgr/portmgr.h +++ b/cfgmgr/portmgr.h @@ -31,6 +31,7 @@ class PortMgr : public Orch void doTask(Consumer &consumer); bool setPortMtu(const std::string &alias, const std::string &mtu); bool setPortAdminStatus(const std::string &alias, const bool up); + bool setPortLearnMode(const std::string &alias, const std::string &learn_mode); bool isPortStateOk(const std::string &alias); }; diff --git a/cfgmgr/teammgr.cpp b/cfgmgr/teammgr.cpp index 2149e1ce5f..8071d44ada 100644 --- a/cfgmgr/teammgr.cpp +++ b/cfgmgr/teammgr.cpp @@ -126,6 +126,7 @@ void TeamMgr::doLagTask(Consumer &consumer) bool fallback = false; string admin_status = DEFAULT_ADMIN_STATUS_STR; string mtu = DEFAULT_MTU_STR; + string learn_mode; for (auto i : kfvFieldsValues(t)) { @@ -153,6 +154,12 @@ void TeamMgr::doLagTask(Consumer &consumer) mtu = fvValue(i); SWSS_LOG_INFO("Get MTU %s", mtu.c_str()); } + else if (fvField(i) == "learn_mode") + { + learn_mode = fvValue(i); + SWSS_LOG_INFO("Get learn_mode %s", + learn_mode.c_str()); + } } if (m_lagList.find(alias) == m_lagList.end()) @@ -168,6 +175,11 @@ void TeamMgr::doLagTask(Consumer &consumer) setLagAdminStatus(alias, admin_status); setLagMtu(alias, mtu); + if (!learn_mode.empty()) + { + setLagLearnMode(alias, learn_mode); + SWSS_LOG_NOTICE("Configure %s MAC learn mode to %s", alias.c_str(), learn_mode.c_str()); + } } else if (op == DEL_COMMAND) { @@ -365,6 +377,17 @@ bool TeamMgr::setLagMtu(const string &alias, const string &mtu) return true; } +bool TeamMgr::setLagLearnMode(const string &alias, const string &learn_mode) +{ + // Set the port MAC learn mode in application database + vector fvs; + FieldValueTuple fv("learn_mode", learn_mode); + fvs.push_back(fv); + m_appLagTable.set(alias, fvs); + + return true; +} + task_process_status TeamMgr::addLag(const string &alias, int min_links, bool fallback) { SWSS_LOG_ENTER(); diff --git a/cfgmgr/teammgr.h b/cfgmgr/teammgr.h index e0e0410db1..db3b033844 100644 --- a/cfgmgr/teammgr.h +++ b/cfgmgr/teammgr.h @@ -44,6 +44,7 @@ class TeamMgr : public Orch bool setLagAdminStatus(const std::string &alias, const std::string &admin_status); bool setLagMtu(const std::string &alias, const std::string &mtu); + bool setLagLearnMode(const std::string &alias, const std::string &learn_mode); bool isPortEnslaved(const std::string &); bool findPortMaster(std::string &, const std::string &); diff --git a/orchagent/port.h b/orchagent/port.h index 8950985523..fac1cae2b6 100644 --- a/orchagent/port.h +++ b/orchagent/port.h @@ -72,6 +72,7 @@ class Port int m_index = 0; // PHY_PORT: index uint32_t m_mtu = DEFAULT_MTU; uint32_t m_speed = 0; // Mbps + std::string m_learn_mode = "hardware"; bool m_autoneg = false; bool m_admin_state_up = false; sai_object_id_t m_port_id = 0; diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index 16ea7099e8..586a81f755 100644 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -60,6 +60,16 @@ static map pfc_asym_map = { "off", SAI_PORT_PRIORITY_FLOW_CONTROL_MODE_COMBINED } }; +static map learn_mode_map = +{ + { "drop", SAI_BRIDGE_PORT_FDB_LEARNING_MODE_DROP }, + { "disable", SAI_BRIDGE_PORT_FDB_LEARNING_MODE_DISABLE }, + { "hardware", SAI_BRIDGE_PORT_FDB_LEARNING_MODE_HW }, + { "cpu_trap", SAI_BRIDGE_PORT_FDB_LEARNING_MODE_CPU_TRAP}, + { "cpu_log", SAI_BRIDGE_PORT_FDB_LEARNING_MODE_CPU_LOG}, + { "notification", SAI_BRIDGE_PORT_FDB_LEARNING_MODE_FDB_NOTIFICATION} +}; + const vector portStatIds = { SAI_PORT_STAT_IF_IN_OCTETS, @@ -1671,6 +1681,7 @@ void PortsOrch::doPortTask(Consumer &consumer) string pfc_asym; uint32_t mtu = 0; uint32_t speed = 0; + string learn_mode; int an = -1; for (auto i : kfvFieldsValues(t)) @@ -1713,6 +1724,12 @@ void PortsOrch::doPortTask(Consumer &consumer) fec_mode = fvValue(i); } + /* Get port fdb learn mode*/ + if (fvField(i) == "learn_mode") + { + learn_mode = fvValue(i); + } + /* Set port asymmetric PFC */ if (fvField(i) == "pfc_asym") pfc_asym = fvValue(i); @@ -1988,6 +2005,32 @@ void PortsOrch::doPortTask(Consumer &consumer) } } + if (!learn_mode.empty() && (p.m_learn_mode != learn_mode)) + { + if (p.m_bridge_port_id != SAI_NULL_OBJECT_ID) + { + if(setBridgePortLearnMode(p, learn_mode)) + { + p.m_learn_mode = learn_mode; + m_portList[alias] = p; + SWSS_LOG_NOTICE("Set port %s learn mode to %s", alias.c_str(), learn_mode.c_str()); + } + else + { + SWSS_LOG_ERROR("Failed to set port %s learn mode to %s", alias.c_str(), learn_mode.c_str()); + it++; + continue; + } + } + else + { + p.m_learn_mode = learn_mode; + m_portList[alias] = p; + + SWSS_LOG_NOTICE("Saved to set port %s learn mode %s", alias.c_str(), learn_mode.c_str()); + } + } + if (pfc_asym != "") { if (setPortPfcAsym(p, pfc_asym)) @@ -2298,13 +2341,19 @@ void PortsOrch::doLagTask(Consumer &consumer) { // Retrieve attributes uint32_t mtu = 0; + string learn_mode; + for (auto i : kfvFieldsValues(t)) { if (fvField(i) == "mtu") { mtu = (uint32_t)stoul(fvValue(i)); } - if (fvField(i) == "oper_status") + else if (fvField(i) == "learn_mode") + { + learn_mode = fvValue(i); + } + else if (fvField(i) == "oper_status") { if (fvValue(i) == "down") { @@ -2344,6 +2393,32 @@ void PortsOrch::doLagTask(Consumer &consumer) gIntfsOrch->setRouterIntfsMtu(l); } } + + if (!learn_mode.empty() && (l.m_learn_mode != learn_mode)) + { + if (l.m_bridge_port_id != SAI_NULL_OBJECT_ID) + { + if(setBridgePortLearnMode(l, learn_mode)) + { + l.m_learn_mode = learn_mode; + m_portList[alias] = l; + SWSS_LOG_NOTICE("Set port %s learn mode to %s", alias.c_str(), learn_mode.c_str()); + } + else + { + SWSS_LOG_ERROR("Failed to set port %s learn mode to %s", alias.c_str(), learn_mode.c_str()); + it++; + continue; + } + } + else + { + l.m_learn_mode = learn_mode; + m_portList[alias] = l; + + SWSS_LOG_NOTICE("Saved to set port %s learn mode %s", alias.c_str(), learn_mode.c_str()); + } + } } it = consumer.m_toSync.erase(it); @@ -2758,7 +2833,15 @@ bool PortsOrch::addBridgePort(Port &port) /* And with hardware FDB learning mode set to HW (explicit default value) */ attr.id = SAI_BRIDGE_PORT_ATTR_FDB_LEARNING_MODE; - attr.value.s32 = SAI_BRIDGE_PORT_FDB_LEARNING_MODE_HW; + auto found = learn_mode_map.find(port.m_learn_mode); + if (found == learn_mode_map.end()) + { + attr.value.s32 = SAI_BRIDGE_PORT_FDB_LEARNING_MODE_HW; + } + else + { + attr.value.s32 = found->second; + } attrs.push_back(attr); sai_status_t status = sai_bridge_api->create_bridge_port(&port.m_bridge_port_id, gSwitchId, (uint32_t)attrs.size(), attrs.data()); @@ -2829,6 +2912,40 @@ bool PortsOrch::removeBridgePort(Port &port) return true; } +bool PortsOrch::setBridgePortLearnMode(Port &port, string learn_mode) +{ + SWSS_LOG_ENTER(); + + if (port.m_bridge_port_id == SAI_NULL_OBJECT_ID) + { + return true; + } + + auto found = learn_mode_map.find(learn_mode); + if (found == learn_mode_map.end()) + { + SWSS_LOG_ERROR("Incorrect MAC learn mode: %s", learn_mode.c_str()); + return false; + } + + /* Set bridge port learning mode */ + sai_attribute_t attr; + attr.id = SAI_BRIDGE_PORT_ATTR_FDB_LEARNING_MODE; + attr.value.s32 = found->second; + + sai_status_t status = sai_bridge_api->set_bridge_port_attribute(port.m_bridge_port_id, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set bridge port %s learning mode, rv:%d", + port.m_alias.c_str(), status); + return false; + } + + SWSS_LOG_NOTICE("Set bridge port %s learning mode %s", port.m_alias.c_str(), learn_mode.c_str()); + + return true; +} + bool PortsOrch::addVlan(string vlan_alias) { SWSS_LOG_ENTER(); diff --git a/orchagent/portsorch.h b/orchagent/portsorch.h index 0fe3bad480..6f314d6bff 100644 --- a/orchagent/portsorch.h +++ b/orchagent/portsorch.h @@ -133,9 +133,9 @@ class PortsOrch : public Orch, public Subject map, tuple> m_lanesAliasSpeedMap; map m_portList; map m_port_ref_count; - unordered_set m_pendingPortSet; + NotificationConsumer* m_portStatusNotificationConsumer; void doTask(Consumer &consumer); @@ -159,6 +159,7 @@ class PortsOrch : public Orch, public Subject bool addBridgePort(Port &port); bool removeBridgePort(Port &port); + bool setBridgePortLearnMode(Port &port, string learn_mode); bool addVlan(string vlan); bool removeVlan(Port vlan); diff --git a/tests/test_port_mac_learn.py b/tests/test_port_mac_learn.py new file mode 100644 index 0000000000..2abf4291ac --- /dev/null +++ b/tests/test_port_mac_learn.py @@ -0,0 +1,181 @@ +from swsscommon import swsscommon + +import time +import os + +class TestPortMacLearn(object): + def setup_db(self, dvs): + self.pdb = swsscommon.DBConnector(swsscommon.APPL_DB, dvs.redis_sock, 0) + self.adb = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) + self.cdb = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) + self.cntdb = swsscommon.DBConnector(swsscommon.COUNTERS_DB, dvs.redis_sock, 0) + + def get_learn_mode_map(self): + learn_mode_map = { "drop": "SAI_BRIDGE_PORT_FDB_LEARNING_MODE_DROP", + "disable": "SAI_BRIDGE_PORT_FDB_LEARNING_MODE_DISABLE", + "hardware": "SAI_BRIDGE_PORT_FDB_LEARNING_MODE_HW", + "cpu_trap": "SAI_BRIDGE_PORT_FDB_LEARNING_MODE_CPU_TRAP", + "cpu_log": "SAI_BRIDGE_PORT_FDB_LEARNING_MODE_CPU_LOG", + "notification": "SAI_BRIDGE_PORT_FDB_LEARNING_MODE_FDB_NOTIFICATION"} + return learn_mode_map + + def get_port_oid(self, port_name): + port_map_tbl = swsscommon.Table(self.cntdb, 'COUNTERS_PORT_NAME_MAP') + for k in port_map_tbl.get('')[1]: + if k[0] == port_name: + return k[1] + return None + + def get_bridge_port_oid(self, port_oid): + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT") + for key in tbl.getKeys(): + status, data = tbl.get(key) + assert status + values = dict(data) + if port_oid == values["SAI_BRIDGE_PORT_ATTR_PORT_ID"]: + return key + return None + + def check_learn_mode_in_appdb(self, table, interface, learn_mode): + (status, fvs) = table.get(interface) + assert status == True + for fv in fvs: + if fv[0] == "learn_mode": + if fv[1] == learn_mode: + return True + return False + + def check_learn_mode_in_asicdb(self, interface_oid, learn_mode): + # Get bridge port oid + bridge_port_oid = self.get_bridge_port_oid(interface_oid) + assert bridge_port_oid is not None + + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT") + (status, fvs) = tbl.get(bridge_port_oid) + assert status == True + values = dict(fvs) + if values["SAI_BRIDGE_PORT_ATTR_FDB_LEARNING_MODE"] == learn_mode: + return True + else: + return False + + def test_PortMacLearnMode(self, dvs, testlog): + self.setup_db(dvs) + + # create vlan + tbl = swsscommon.Table(self.cdb, "VLAN") + fvs = swsscommon.FieldValuePairs([("vlanid", "2")]) + tbl.set("Vlan2", fvs) + time.sleep(1) + + # create vlan member entry in application db + tbl = swsscommon.Table(self.cdb, "VLAN_MEMBER") + fvs = swsscommon.FieldValuePairs([("tagging_mode", "untagged")]) + tbl.set("Vlan2|Ethernet8", fvs) + time.sleep(1) + + # get port oid + port_oid = self.get_port_oid("Ethernet8") + assert port_oid is not None + + # check asicdb before setting mac learn mode; The default learn_mode value is SAI_BRIDGE_PORT_FDB_LEARNING_MODE_HW. + status = self.check_learn_mode_in_asicdb(port_oid, "SAI_BRIDGE_PORT_FDB_LEARNING_MODE_HW") + assert status == True + + learn_mode_map = self.get_learn_mode_map() + for key, value in learn_mode_map.items(): + # set MAC learn mode to port + tbl = swsscommon.Table(self.cdb, "PORT") + fvs = swsscommon.FieldValuePairs([("learn_mode", key)]) + tbl.set("Ethernet8", fvs) + time.sleep(1) + + # check application database + tbl = swsscommon.Table(self.pdb, "PORT_TABLE") + status = self.check_learn_mode_in_appdb(tbl, "Ethernet8", key) + assert status == True + + # check ASIC bridge port database + status = self.check_learn_mode_in_asicdb(port_oid, value) + assert status == True + + # set default learn mode for Ethernet8 + tbl = swsscommon.Table(self.cdb, "PORT") + fvs = swsscommon.FieldValuePairs([("learn_mode", "hardware")]) + tbl.set("Ethernet8", fvs) + time.sleep(1) + + # remove vlan member + tbl = swsscommon.Table(self.cdb, "VLAN_MEMBER") + tbl._del("Vlan2|Ethernet8") + time.sleep(1) + + # remove vlan + tbl = swsscommon.Table(self.cdb, "VLAN") + tbl._del("Vlan2") + time.sleep(1) + + def test_PortchannelMacLearnMode(self, dvs, testlog): + self.setup_db(dvs) + + #create portchannel + tbl = swsscommon.Table(self.cdb, "PORTCHANNEL") + fvs = swsscommon.FieldValuePairs([("admin_status", "up"), + ("mtu", "9100")]) + tbl.set("PortChannel001", fvs) + time.sleep(1) + + # create vlan + tbl = swsscommon.Table(self.cdb, "VLAN") + fvs = swsscommon.FieldValuePairs([("vlanid", "3")]) + tbl.set("Vlan3", fvs) + time.sleep(1) + + # create vlan member entry in application db + tbl = swsscommon.Table(self.cdb, "VLAN_MEMBER") + fvs = swsscommon.FieldValuePairs([("tagging_mode", "untagged")]) + tbl.set("Vlan3|PortChannel001", fvs) + time.sleep(1) + + # get PortChannel oid; When sonic-swss pr885 is complete, you can get oid directly from COUNTERS_LAG_NAME_MAP, which would be better. + lag_tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_LAG") + lag_entries = lag_tbl.getKeys() + # At this point there should be only one lag in the system, which is PortChannel001. + assert len(lag_entries) == 1 + lag_oid = lag_entries[0] + + # check asicdb before setting mac learn mode; The default learn_mode value is SAI_BRIDGE_PORT_FDB_LEARNING_MODE_HW. + status = self.check_learn_mode_in_asicdb(lag_oid, "SAI_BRIDGE_PORT_FDB_LEARNING_MODE_HW") + assert status == True + + learn_mode_map = self.get_learn_mode_map() + for key, value in learn_mode_map.items(): + # set mac learn mode to PortChannel + tbl = swsscommon.Table(self.cdb, "PORTCHANNEL") + fvs = swsscommon.FieldValuePairs([("learn_mode", key)]) + tbl.set("PortChannel001", fvs) + time.sleep(1) + + # check application database + tbl = swsscommon.Table(self.pdb, "LAG_TABLE") + status = self.check_learn_mode_in_appdb(tbl, "PortChannel001", key) + assert status == True + + # check ASIC bridge port database + status = self.check_learn_mode_in_asicdb(lag_oid, value) + assert status == True + + # remove vlan member + tbl = swsscommon.Table(self.cdb, "VLAN_MEMBER") + tbl._del("Vlan3|PortChannel001") + time.sleep(1) + + # create vlan + tbl = swsscommon.Table(self.cdb, "VLAN") + tbl._del("Vlan3") + time.sleep(1) + + # remove PortChannel + tbl = swsscommon.Table(self.cdb, "PORTCHANNEL") + tbl._del("PortChannel001") + time.sleep(1) From c7d8c2b77171b0dcbbb962306697601d0c2b73fa Mon Sep 17 00:00:00 2001 From: Danny Allen Date: Wed, 13 Nov 2019 14:05:22 -0800 Subject: [PATCH 20/63] [utilities] Create utility classes for interacting with flex counters (#1093) * [utilities] Create utility classes for interacting with flex counters - Adds utility classes for interacting with flex counters - Updates makefiles to support new utilities Signed-off-by: Danny Allen * Reorganize helper classes * Respond to PR feedback * Fix stray includes * Strip comma from end of string instead of beginning * Fix build issue * Fix style issues * Add workaround for flex counter behavior --- orchagent/Makefile.am | 4 +- .../flex_counter/flex_counter_manager.cpp | 225 ++++++++++++++++++ orchagent/flex_counter/flex_counter_manager.h | 77 ++++++ .../flex_counter_stat_manager.cpp | 93 ++++++++ .../flex_counter/flex_counter_stat_manager.h | 33 +++ 5 files changed, 431 insertions(+), 1 deletion(-) create mode 100644 orchagent/flex_counter/flex_counter_manager.cpp create mode 100644 orchagent/flex_counter/flex_counter_manager.h create mode 100644 orchagent/flex_counter/flex_counter_stat_manager.cpp create mode 100644 orchagent/flex_counter/flex_counter_stat_manager.h diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index d7a705a490..429d82e5fe 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -1,4 +1,4 @@ -INCLUDES = -I $(top_srcdir) -I $(top_srcdir)/warmrestart +INCLUDES = -I $(top_srcdir) -I $(top_srcdir)/warmrestart -I flex_counter CFLAGS_SAI = -I /usr/include/sai @@ -56,6 +56,8 @@ orchagent_SOURCES = \ sfloworch.cpp \ chassisorch.cpp +orchagent_SOURCES += flex_counter/flex_counter_manager.cpp flex_counter/flex_counter_stat_manager.cpp + orchagent_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) orchagent_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) orchagent_LDADD = -lnl-3 -lnl-route-3 -lpthread -lsairedis -lswsscommon -lsaimeta -lsaimetadata diff --git a/orchagent/flex_counter/flex_counter_manager.cpp b/orchagent/flex_counter/flex_counter_manager.cpp new file mode 100644 index 0000000000..0b0eddd8c4 --- /dev/null +++ b/orchagent/flex_counter/flex_counter_manager.cpp @@ -0,0 +1,225 @@ +#include "flex_counter_manager.h" + +#include + +#include "schema.h" +#include "rediscommand.h" +#include "logger.h" +#include "sai_serialize.h" + +using std::shared_ptr; +using std::string; +using std::unordered_map; +using std::unordered_set; +using std::vector; +using swss::DBConnector; +using swss::FieldValueTuple; +using swss::ProducerTable; + +const string FLEX_COUNTER_ENABLE("enable"); +const string FLEX_COUNTER_DISABLE("disable"); + +const unordered_map FlexCounterManager::stats_mode_lookup = +{ + { StatsMode::READ, STATS_MODE_READ }, +}; + +const unordered_map FlexCounterManager::status_lookup = +{ + { false, FLEX_COUNTER_DISABLE }, + { true, FLEX_COUNTER_ENABLE } +}; + +const unordered_map FlexCounterManager::counter_id_field_lookup = +{ + { CounterType::PORT_DEBUG, PORT_DEBUG_COUNTER_ID_LIST }, + { CounterType::SWITCH_DEBUG, SWITCH_DEBUG_COUNTER_ID_LIST }, +}; + +// This constructor will create a group that is disabled by default. +FlexCounterManager::FlexCounterManager( + const string& group_name, + const StatsMode stats_mode, + const uint polling_interval) : + group_name(group_name), + stats_mode(stats_mode), + polling_interval(polling_interval), + enabled(false), + flex_counter_db(new DBConnector(FLEX_COUNTER_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)), + flex_counter_group_table(new ProducerTable(flex_counter_db.get(), FLEX_COUNTER_GROUP_TABLE)), + flex_counter_table(new ProducerTable(flex_counter_db.get(), FLEX_COUNTER_TABLE)) +{ + SWSS_LOG_ENTER(); + + applyGroupConfiguration(); + + SWSS_LOG_DEBUG("Initialized flex counter group '%s'.", group_name.c_str()); +} + +FlexCounterManager::~FlexCounterManager() +{ + SWSS_LOG_ENTER(); + + for (const auto& counter: installed_counters) + { + flex_counter_table->del(getFlexCounterTableKey(group_name, counter)); + } + + flex_counter_group_table->del(group_name); + + SWSS_LOG_DEBUG("Deleted flex counter group '%s'.", group_name.c_str()); +} + +void FlexCounterManager::applyGroupConfiguration() +{ + vector field_values = + { + FieldValueTuple(STATS_MODE_FIELD, stats_mode_lookup.at(stats_mode)), + FieldValueTuple(POLL_INTERVAL_FIELD, std::to_string(polling_interval)), + FieldValueTuple(FLEX_COUNTER_STATUS_FIELD, status_lookup.at(enabled)) + }; + + flex_counter_group_table->set(group_name, field_values); +} + +void FlexCounterManager::updateGroupPollingInterval( + const uint polling_interval) +{ + SWSS_LOG_ENTER(); + + vector field_values = + { + FieldValueTuple(POLL_INTERVAL_FIELD, std::to_string(polling_interval)) + }; + flex_counter_group_table->set(group_name, field_values); + + SWSS_LOG_DEBUG("Set polling interval for flex counter group '%s' to %d ms.", + group_name.c_str(), polling_interval); +} + +// enableFlexCounterGroup will do nothing if the flex counter group is already +// enabled. +void FlexCounterManager::enableFlexCounterGroup() +{ + SWSS_LOG_ENTER(); + + if (enabled) + { + return; + } + + vector field_values = + { + FieldValueTuple(FLEX_COUNTER_STATUS_FIELD, FLEX_COUNTER_ENABLE) + }; + flex_counter_group_table->set(group_name, field_values); + enabled = true; + + SWSS_LOG_DEBUG("Enabling flex counters for group '%s'.", + group_name.c_str()); +} + +// disableFlexCounterGroup will do nothing if the flex counter group has been +// disabled. +void FlexCounterManager::disableFlexCounterGroup() +{ + SWSS_LOG_ENTER(); + + if (!enabled) + { + return; + } + + vector field_values = + { + FieldValueTuple(FLEX_COUNTER_STATUS_FIELD, FLEX_COUNTER_DISABLE) + }; + flex_counter_group_table->set(group_name, field_values); + enabled = false; + + SWSS_LOG_DEBUG("Disabling flex counters for group '%s'.", + group_name.c_str()); +} + +// setCounterIdList configures a flex counter to poll the set of provided stats +// that are associated with the given object. +void FlexCounterManager::setCounterIdList( + const sai_object_id_t object_id, + const CounterType counter_type, + const unordered_set& counter_stats) +{ + SWSS_LOG_ENTER(); + + auto counter_type_it = counter_id_field_lookup.find(counter_type); + if (counter_type_it == counter_id_field_lookup.end()) + { + SWSS_LOG_ERROR("Could not update flex counter id list for group '%s': counter type not found.", + group_name.c_str()); + return; + } + + std::vector field_values = + { + FieldValueTuple(counter_type_it->second, serializeCounterStats(counter_stats)) + }; + flex_counter_table->set(getFlexCounterTableKey(group_name, object_id), field_values); + installed_counters.insert(object_id); + + SWSS_LOG_DEBUG("Updated flex counter id list for object '%lu' in group '%s'.", + object_id, + group_name.c_str()); +} + +// clearCounterIdList clears all stats that are currently being polled from +// the given object. +void FlexCounterManager::clearCounterIdList(const sai_object_id_t object_id) +{ + SWSS_LOG_ENTER(); + + auto counter_it = installed_counters.find(object_id); + if (counter_it == installed_counters.end()) + { + SWSS_LOG_WARN("No counters found on object '%lu' in group '%s'.", + object_id, + group_name.c_str()); + return; + } + + flex_counter_table->del(getFlexCounterTableKey(group_name, object_id)); + installed_counters.erase(counter_it); + + SWSS_LOG_DEBUG("Cleared flex counter id list for object '%lu' in group '%s'.", + object_id, + group_name.c_str()); +} + +string FlexCounterManager::getFlexCounterTableKey( + const string& group_name, + const sai_object_id_t object_id) const +{ + SWSS_LOG_ENTER(); + + return group_name + flex_counter_table->getTableNameSeparator() + sai_serialize_object_id(object_id); +} + +// serializeCounterStats turns a set of stats into a format suitable for FLEX_COUNTER_DB. +string FlexCounterManager::serializeCounterStats( + const unordered_set& counter_stats) const +{ + SWSS_LOG_ENTER(); + + string stats_string; + for (const auto& stat : counter_stats) + { + stats_string.append(stat); + stats_string.append(","); + } + + if (!stats_string.empty()) + { + // Fence post: remove the trailing comma + stats_string.pop_back(); + } + + return stats_string; +} \ No newline at end of file diff --git a/orchagent/flex_counter/flex_counter_manager.h b/orchagent/flex_counter/flex_counter_manager.h new file mode 100644 index 0000000000..0d444d57ed --- /dev/null +++ b/orchagent/flex_counter/flex_counter_manager.h @@ -0,0 +1,77 @@ +#ifndef ORCHAGENT_FLEX_COUNTER_MANAGER_H +#define ORCHAGENT_FLEX_COUNTER_MANAGER_H + +#include +#include +#include +#include "dbconnector.h" +#include "producertable.h" + +extern "C" { +#include "sai.h" +} + +enum class StatsMode +{ + READ +}; + +enum class CounterType +{ + PORT_DEBUG, + SWITCH_DEBUG +}; + +// FlexCounterManager allows users to manage a group of flex counters. +// +// TODO: FlexCounterManager doesn't currently support the full range of +// flex counter features. In particular, support for standard (i.e. non-debug) +// counters and support for plugins needs to be added. +class FlexCounterManager +{ + public: + FlexCounterManager( + const std::string& group_name, + const StatsMode stats_mode, + const uint polling_interval); + + FlexCounterManager(const FlexCounterManager&) = delete; + FlexCounterManager& operator=(const FlexCounterManager&) = delete; + virtual ~FlexCounterManager(); + + void updateGroupPollingInterval(const uint polling_interval); + void enableFlexCounterGroup(); + void disableFlexCounterGroup(); + + void setCounterIdList( + const sai_object_id_t object_id, + const CounterType counter_type, + const std::unordered_set& counter_stats); + void clearCounterIdList(const sai_object_id_t object_id); + + protected: + void applyGroupConfiguration(); + + private: + std::string getFlexCounterTableKey( + const std::string& group_name, + const sai_object_id_t object_id) const; + std::string serializeCounterStats( + const std::unordered_set& counter_stats) const; + + std::string group_name; + StatsMode stats_mode; + uint polling_interval; + bool enabled; + std::unordered_set installed_counters; + + std::shared_ptr flex_counter_db = nullptr; + std::shared_ptr flex_counter_group_table = nullptr; + std::shared_ptr flex_counter_table = nullptr; + + static const std::unordered_map stats_mode_lookup; + static const std::unordered_map status_lookup; + static const std::unordered_map counter_id_field_lookup; +}; + +#endif // ORCHAGENT_FLEX_COUNTER_MANAGER_H diff --git a/orchagent/flex_counter/flex_counter_stat_manager.cpp b/orchagent/flex_counter/flex_counter_stat_manager.cpp new file mode 100644 index 0000000000..4f6cab843e --- /dev/null +++ b/orchagent/flex_counter/flex_counter_stat_manager.cpp @@ -0,0 +1,93 @@ +#include "flex_counter_stat_manager.h" + +#include "schema.h" +#include "rediscommand.h" +#include "logger.h" +#include "sai_serialize.h" + +using std::string; +using std::unordered_map; +using std::unordered_set; +using swss::FieldValueTuple; + +FlexCounterStatManager::FlexCounterStatManager( + const string& group_name, + const StatsMode stats_mode, + const int polling_interval) : + FlexCounterManager(group_name, stats_mode, polling_interval) +{ + SWSS_LOG_ENTER(); +} + +FlexCounterStatManager::~FlexCounterStatManager() +{ + SWSS_LOG_ENTER(); +} + +// addFlexCounterStat will add a new stat for the given object to poll. +void FlexCounterStatManager::addFlexCounterStat( + const sai_object_id_t object_id, + const CounterType counter_type, + const string& counter_stat) +{ + SWSS_LOG_ENTER(); + + auto counter_stats = object_stats.find(object_id); + if (counter_stats == object_stats.end()) + { + unordered_set new_stats = { counter_stat }; + counter_stats = object_stats.emplace(object_id, new_stats).first; + } + else + { + counter_stats->second.emplace(counter_stat); + } + + // FIXME: Currently the state of the flex counter group is lost if all + // stats are removed from a flex counter group. This will be fixed once + // syncd flex counters are refactored. For now, we can workaround this + // by re-applying the group configuration when we set the counter id list. + FlexCounterManager::applyGroupConfiguration(); + + FlexCounterManager::setCounterIdList(object_id, counter_type, counter_stats->second); + + SWSS_LOG_DEBUG("Added flex stat '%s' to object '%s'", counter_stat.c_str(), sai_serialize_object_id(object_id).c_str()); +} + +// removeFlexCounterStat will remove a stat from the set of stats the given +// object are polling. +void FlexCounterStatManager::removeFlexCounterStat( + const sai_object_id_t object_id, + const CounterType counter_type, + const string& counter_stat) +{ + SWSS_LOG_ENTER(); + + auto counter_stats = object_stats.find(object_id); + if (counter_stats == object_stats.end()) + { + SWSS_LOG_WARN("Could not find flex stat '%s' on object '%s'", + counter_stat.c_str(), sai_serialize_object_id(object_id).c_str()); + return; + } + + counter_stats->second.erase(counter_stat); + + // If we don't have any stats left for this object, delete the flex + // counter entirely. + if (counter_stats->second.empty()) + { + object_stats.erase(counter_stats); + FlexCounterManager::clearCounterIdList(object_id); + + SWSS_LOG_DEBUG("Flex stat is empty, removing flex counter from object '%s'", + sai_serialize_object_id(object_id).c_str()); + return; + } + + FlexCounterManager::setCounterIdList(object_id, counter_type, counter_stats->second); + + SWSS_LOG_DEBUG("Removing flex stat '%s' from object '%s'", + counter_stat.c_str(), + sai_serialize_object_id(object_id).c_str()); +} diff --git a/orchagent/flex_counter/flex_counter_stat_manager.h b/orchagent/flex_counter/flex_counter_stat_manager.h new file mode 100644 index 0000000000..250adb724c --- /dev/null +++ b/orchagent/flex_counter/flex_counter_stat_manager.h @@ -0,0 +1,33 @@ +#ifndef ORCHAGENT_FLEX_COUNTER_STAT_MANAGER_H +#define ORCHAGENT_FLEX_COUNTER_STAT_MANAGER_H + +#include "flex_counter_manager.h" + +// FlexCounterStatManager allows users to manage a group of flex counters +// where the objects have highly variable sets of stats to track. +class FlexCounterStatManager : public FlexCounterManager +{ + public: + FlexCounterStatManager( + const std::string& group_name, + const StatsMode stats_mode, + const int polling_interval); + + FlexCounterStatManager(const FlexCounterStatManager&) = delete; + FlexCounterStatManager& operator=(const FlexCounterStatManager&) = delete; + ~FlexCounterStatManager(); + + void addFlexCounterStat( + const sai_object_id_t object_id, + const CounterType counter_type, + const std::string& counter_stat); + void removeFlexCounterStat( + const sai_object_id_t object_id, + const CounterType counter_type, + const std::string& counter_stat); + + private: + std::unordered_map> object_stats; +}; + +#endif // ORCHAGENT_FLEX_COUNTER_STAT_MANAGER_H From 457ea7e4b4e6889927cca8ec21ba2e4c2a3afb55 Mon Sep 17 00:00:00 2001 From: "arheneus@marvell.com" <51254330+antony-rheneus@users.noreply.github.com> Date: Tue, 19 Nov 2019 12:01:59 +0530 Subject: [PATCH 21/63] [orchagent] warning fixes for 32bit arch compilation (#1129) Signed-off-by: Antony Rheneus --- orchagent/aclorch.cpp | 2 +- orchagent/qosorch.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index c3d5fd49a8..fbafdecb37 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -559,7 +559,7 @@ bool AclRule::isActionSupported(sai_acl_entry_attr_t action) const const auto* pTable = m_pAclOrch->getTableByOid(m_tableOid); if (pTable == nullptr) { - SWSS_LOG_THROW("ACL table does not exist for oid %lu", m_tableOid); + SWSS_LOG_THROW("ACL table does not exist for oid %" PRIu64, m_tableOid); } return m_pAclOrch->isAclActionSupported(pTable->stage, action_type); } diff --git a/orchagent/qosorch.cpp b/orchagent/qosorch.cpp index 4e92275d79..a3a317a986 100644 --- a/orchagent/qosorch.cpp +++ b/orchagent/qosorch.cpp @@ -278,7 +278,7 @@ sai_object_id_t Dot1pToTcMapHandler::addQosItem(const vector &a SWSS_LOG_ERROR("Failed to create dot1p_to_tc map. status: %s", sai_serialize_status(sai_status).c_str()); return SAI_NULL_OBJECT_ID; } - SWSS_LOG_DEBUG("created QosMap object: 0x%lx", object_id); + SWSS_LOG_DEBUG("created QosMap object: 0x%" PRIx64, object_id); return object_id; } From c3b8fe10b1568022fa025ef4be9cb063bfe49df4 Mon Sep 17 00:00:00 2001 From: Danny Allen Date: Tue, 19 Nov 2019 15:10:48 -0800 Subject: [PATCH 22/63] [orchagent] Adds swss support for drop counters (#1075) * [orchagent] Adds swss support for drop counters - Creates a new orchestrator for managing debug counters - Adds utility functions for managing flex counters - Adds utility functions for managing debug counters Signed-off-by: Danny Allen * Fix build issues * Fix log levels * Cast drop reasons before serializing * Fix testing issues * Clean up flex counter utilities * Expose switch ids in redis * Clean-up comments and namespace usage * Add basic drop counter vswitch tests * Add drop reasons * Fix rebase merge conflicts * Move flex counter and switch related work to different PRs * Fix first round of feedback comments * Fix free counters review comments * Reorganize helper classes * Delete extra makefile * Only put useful capabilities in STATE DB * Use new flex counter manager naming * Update mock tests to build with debug counter orch * Add remaining ingress drop reasons * Fix reference style issue * Update tests to match virtual switch implementation * Respond to C++ style issues * Improve documentation for vs tests * Add test case for removing all drop reasons from a counter * Specify exception type being caught * Add range check for drop counter indices * Undo range check --- orchagent/Makefile.am | 6 +- orchagent/debug_counter/debug_counter.cpp | 112 ++++ orchagent/debug_counter/debug_counter.h | 64 ++ orchagent/debug_counter/drop_counter.cpp | 372 ++++++++++++ orchagent/debug_counter/drop_counter.h | 54 ++ orchagent/debug_counter/drop_reasons.h | 65 ++ orchagent/debugcounterorch.cpp | 588 ++++++++++++++++++ orchagent/debugcounterorch.h | 97 +++ orchagent/flexcounterorch.cpp | 3 + orchagent/orchdaemon.cpp | 10 +- orchagent/orchdaemon.h | 1 + orchagent/saihelper.cpp | 3 + tests/mock_tests/Makefile.am | 11 +- tests/test_drop_counters.py | 705 ++++++++++++++++++++++ 14 files changed, 2086 insertions(+), 5 deletions(-) create mode 100644 orchagent/debug_counter/debug_counter.cpp create mode 100644 orchagent/debug_counter/debug_counter.h create mode 100644 orchagent/debug_counter/drop_counter.cpp create mode 100644 orchagent/debug_counter/drop_counter.h create mode 100644 orchagent/debug_counter/drop_reasons.h create mode 100644 orchagent/debugcounterorch.cpp create mode 100644 orchagent/debugcounterorch.h create mode 100644 tests/test_drop_counters.py diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index 429d82e5fe..79cfe99f6d 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -1,4 +1,4 @@ -INCLUDES = -I $(top_srcdir) -I $(top_srcdir)/warmrestart -I flex_counter +INCLUDES = -I $(top_srcdir) -I $(top_srcdir)/warmrestart -I flex_counter -I debug_counter CFLAGS_SAI = -I /usr/include/sai @@ -54,9 +54,11 @@ orchagent_SOURCES = \ watermarkorch.cpp \ policerorch.cpp \ sfloworch.cpp \ - chassisorch.cpp + chassisorch.cpp \ + debugcounterorch.cpp orchagent_SOURCES += flex_counter/flex_counter_manager.cpp flex_counter/flex_counter_stat_manager.cpp +orchagent_SOURCES += debug_counter/debug_counter.cpp debug_counter/drop_counter.cpp orchagent_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) orchagent_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) diff --git a/orchagent/debug_counter/debug_counter.cpp b/orchagent/debug_counter/debug_counter.cpp new file mode 100644 index 0000000000..8e01052e6e --- /dev/null +++ b/orchagent/debug_counter/debug_counter.cpp @@ -0,0 +1,112 @@ +#include "debug_counter.h" +#include "drop_counter.h" + +#include +#include +#include +#include +#include +#include "rediscommand.h" +#include +#include "logger.h" + +using std::runtime_error; +using std::string; +using std::unique_ptr; +using std::unordered_map; +using std::unordered_set; +using std::vector; +using swss::FieldValueTuple; + +extern sai_object_id_t gSwitchId; +extern sai_debug_counter_api_t *sai_debug_counter_api; + +// Set of supported attributes to support easy look-up. +const unordered_set DebugCounter::supported_debug_counter_attributes = +{ + COUNTER_ALIAS, + COUNTER_TYPE, + COUNTER_DESCRIPTION, + COUNTER_GROUP +}; + +const std::unordered_map DebugCounter::debug_counter_type_lookup = +{ + { PORT_INGRESS_DROPS, SAI_DEBUG_COUNTER_TYPE_PORT_IN_DROP_REASONS }, + { PORT_EGRESS_DROPS, SAI_DEBUG_COUNTER_TYPE_PORT_OUT_DROP_REASONS }, + { SWITCH_INGRESS_DROPS, SAI_DEBUG_COUNTER_TYPE_SWITCH_IN_DROP_REASONS }, + { SWITCH_EGRESS_DROPS, SAI_DEBUG_COUNTER_TYPE_SWITCH_OUT_DROP_REASONS } +}; + +// It is expected that derived types populate any relevant fields and +// initialize the counter in the SAI. +// +// If counter_type is not a member of debug_counter_type_lookup then this +// constructor will throw a runtime error. +DebugCounter::DebugCounter( + const string& counter_name, + const string& counter_type) + : name(counter_name) +{ + SWSS_LOG_ENTER(); + + auto counter_type_it = debug_counter_type_lookup.find(counter_type); + if (counter_type_it == debug_counter_type_lookup.end()) { + SWSS_LOG_ERROR("Failed to initialize debug counter of type '%s'", + counter_type.c_str()); + throw runtime_error("Failed to initialize debug counter"); + } + type = counter_type_it->first; +} + +// It is expected that derived types delete the counter from the SAI. +DebugCounter::~DebugCounter() +{ + SWSS_LOG_ENTER(); +} + +void DebugCounter::serializeDebugCounterType(sai_attribute_t& type_attribute) +{ + SWSS_LOG_ENTER(); + + sai_debug_counter_type_t sai_counter_type = debug_counter_type_lookup.at(type); + type_attribute.id = SAI_DEBUG_COUNTER_ATTR_TYPE; + type_attribute.value.s32 = sai_counter_type; + + SWSS_LOG_DEBUG("Serializing debug counter of type '%s'", type.c_str()); +} + +// addDebugCounterToSAI creates a new debug counter object in the SAI given a list of debug counter attributes. +// +// If the SAI returns an error then this method will throw a runtime error. +// +// Behavior is undefined if num_attributes is not equal to the number of +// attributes in debug_counter_attributes. +void DebugCounter::addDebugCounterToSAI(const int num_attributes, const sai_attribute_t *debug_counter_attributes) +{ + SWSS_LOG_ENTER(); + + SWSS_LOG_DEBUG("Adding debug counter '%s' to SAI", name.c_str()); + sai_object_id_t debug_counter_id; + if (sai_debug_counter_api->create_debug_counter(&debug_counter_id, + gSwitchId, + num_attributes, + debug_counter_attributes) != SAI_STATUS_SUCCESS) { + SWSS_LOG_ERROR("Failed to create debug counter '%s'", name.c_str()); + throw std::runtime_error("Failed to create debug counter"); + } + + SWSS_LOG_DEBUG("Created debug counter '%s' with OID=%lu", name.c_str(), debug_counter_id); + counter_id = debug_counter_id; +} + +void DebugCounter::removeDebugCounterFromSAI() +{ + SWSS_LOG_ENTER(); + + SWSS_LOG_DEBUG("Removing debug counter '%s' from SAI", name.c_str()); + if (sai_debug_counter_api->remove_debug_counter(counter_id) != SAI_STATUS_SUCCESS) { + SWSS_LOG_ERROR("Failed to remove debug counter '%s'", name.c_str()); + throw std::runtime_error("Failed to remove debug counter"); + } +} diff --git a/orchagent/debug_counter/debug_counter.h b/orchagent/debug_counter/debug_counter.h new file mode 100644 index 0000000000..27dec20920 --- /dev/null +++ b/orchagent/debug_counter/debug_counter.h @@ -0,0 +1,64 @@ +#ifndef SWSS_UTIL_DEBUG_COUNTER_H_ +#define SWSS_UTIL_DEBUG_COUNTER_H_ + +#include +#include +#include + +extern "C" { +#include "sai.h" +} + +// Supported debug counter attributes. +#define COUNTER_ALIAS "alias" +#define COUNTER_TYPE "type" +#define COUNTER_DESCRIPTION "desc" +#define COUNTER_GROUP "group" + +// Supported debug counter types. +#define PORT_INGRESS_DROPS "PORT_INGRESS_DROPS" +#define PORT_EGRESS_DROPS "PORT_EGRESS_DROPS" +#define SWITCH_INGRESS_DROPS "SWITCH_INGRESS_DROPS" +#define SWITCH_EGRESS_DROPS "SWITCH_EGRESS_DROPS" + +// DebugCounter represents a SAI debug counter object. +class DebugCounter +{ + public: + DebugCounter(const std::string& counter_name, const std::string& counter_type) noexcept(false); + DebugCounter(const DebugCounter&) = delete; + DebugCounter& operator=(const DebugCounter&) = delete; + virtual ~DebugCounter(); + + std::string getCounterName() const { return name; } + std::string getCounterType() const { return type; } + + virtual std::string getDebugCounterSAIStat() const noexcept(false) = 0; + + static const std::unordered_set& getSupportedDebugCounterAttributes() + { + return supported_debug_counter_attributes; + } + + // TODO: We should try to neatly abstract this like we've done for the isValid methods in DropCounter. + static const std::unordered_map& getDebugCounterTypeLookup() + { + return debug_counter_type_lookup; + } + + protected: + // These methods are intended to help with initialization. Dervied types will most likely + // need to define additional helper methods to serialize additional fields (see DropCounter for example). + void serializeDebugCounterType(sai_attribute_t& type_attribute); + void addDebugCounterToSAI(int num_attrs, const sai_attribute_t *counter_attrs) noexcept(false); + void removeDebugCounterFromSAI() noexcept(false); + + std::string name; + std::string type; + sai_object_id_t counter_id = 0; + + static const std::unordered_set supported_debug_counter_attributes; + static const std::unordered_map debug_counter_type_lookup; +}; + +#endif // _SWSS_UTIL_DEBUG_COUNTER_H_ diff --git a/orchagent/debug_counter/drop_counter.cpp b/orchagent/debug_counter/drop_counter.cpp new file mode 100644 index 0000000000..aab9c4b547 --- /dev/null +++ b/orchagent/debug_counter/drop_counter.cpp @@ -0,0 +1,372 @@ +#include "drop_counter.h" + +#include "logger.h" +#include "sai_serialize.h" + +using std::runtime_error; +using std::string; +using std::unordered_map; +using std::unordered_set; +using std::vector; + +extern sai_object_id_t gSwitchId; +extern sai_debug_counter_api_t *sai_debug_counter_api; + +const unordered_map DropCounter::ingress_drop_reason_lookup = +{ + { L2_ANY, SAI_IN_DROP_REASON_L2_ANY }, + { SMAC_MULTICAST, SAI_IN_DROP_REASON_SMAC_MULTICAST }, + { SMAC_EQUALS_DMAC, SAI_IN_DROP_REASON_SMAC_EQUALS_DMAC }, + { DMAC_RESERVED, SAI_IN_DROP_REASON_DMAC_RESERVED }, + { VLAN_TAG_NOT_ALLOWED, SAI_IN_DROP_REASON_VLAN_TAG_NOT_ALLOWED }, + { INGRESS_VLAN_FILTER, SAI_IN_DROP_REASON_INGRESS_VLAN_FILTER }, + { INGRESS_STP_FILTER, SAI_IN_DROP_REASON_INGRESS_STP_FILTER }, + { FDB_UC_DISCARD, SAI_IN_DROP_REASON_FDB_UC_DISCARD }, + { FDB_MC_DISCARD, SAI_IN_DROP_REASON_FDB_MC_DISCARD }, + { L2_LOOPBACK_FILTER, SAI_IN_DROP_REASON_L2_LOOPBACK_FILTER }, + { EXCEEDS_L2_MTU, SAI_IN_DROP_REASON_EXCEEDS_L2_MTU }, + { L3_ANY, SAI_IN_DROP_REASON_L3_ANY }, + { EXCEEDS_L3_MTU, SAI_IN_DROP_REASON_EXCEEDS_L3_MTU }, + { TTL, SAI_IN_DROP_REASON_TTL }, + { L3_LOOPBACK_FILTER, SAI_IN_DROP_REASON_L3_LOOPBACK_FILTER }, + { NON_ROUTABLE, SAI_IN_DROP_REASON_NON_ROUTABLE }, + { NO_L3_HEADER, SAI_IN_DROP_REASON_NO_L3_HEADER }, + { IP_HEADER_ERROR, SAI_IN_DROP_REASON_IP_HEADER_ERROR }, + { UC_DIP_MC_DMAC, SAI_IN_DROP_REASON_UC_DIP_MC_DMAC }, + { DIP_LOOPBACK, SAI_IN_DROP_REASON_DIP_LOOPBACK }, + { SIP_LOOPBACK, SAI_IN_DROP_REASON_SIP_LOOPBACK }, + { SIP_MC, SAI_IN_DROP_REASON_SIP_MC }, + { SIP_CLASS_E, SAI_IN_DROP_REASON_SIP_CLASS_E }, + { SIP_UNSPECIFIED, SAI_IN_DROP_REASON_SIP_UNSPECIFIED }, + { MC_DMAC_MISMATCH, SAI_IN_DROP_REASON_MC_DMAC_MISMATCH }, + { SIP_EQUALS_DIP, SAI_IN_DROP_REASON_SIP_EQUALS_DIP }, + { SIP_BC, SAI_IN_DROP_REASON_SIP_BC }, + { DIP_LOCAL, SAI_IN_DROP_REASON_DIP_LOCAL }, + { DIP_LINK_LOCAL, SAI_IN_DROP_REASON_DIP_LINK_LOCAL }, + { SIP_LINK_LOCAL, SAI_IN_DROP_REASON_SIP_LINK_LOCAL }, + { IPV6_MC_SCOPE0, SAI_IN_DROP_REASON_IPV6_MC_SCOPE0 }, + { IPV6_MC_SCOPE1, SAI_IN_DROP_REASON_IPV6_MC_SCOPE1 }, + { IRIF_DISABLED, SAI_IN_DROP_REASON_IRIF_DISABLED }, + { ERIF_DISABLED, SAI_IN_DROP_REASON_ERIF_DISABLED }, + { LPM4_MISS, SAI_IN_DROP_REASON_LPM4_MISS }, + { LPM6_MISS, SAI_IN_DROP_REASON_LPM6_MISS }, + { BLACKHOLE_ROUTE, SAI_IN_DROP_REASON_BLACKHOLE_ROUTE }, + { BLACKHOLE_ARP, SAI_IN_DROP_REASON_BLACKHOLE_ARP }, + { UNRESOLVED_NEXT_HOP, SAI_IN_DROP_REASON_UNRESOLVED_NEXT_HOP }, + { L3_EGRESS_LINK_DOWN, SAI_IN_DROP_REASON_L3_EGRESS_LINK_DOWN }, + { DECAP_ERROR, SAI_IN_DROP_REASON_DECAP_ERROR }, + { ACL_ANY, SAI_IN_DROP_REASON_ACL_ANY}, + { ACL_INGRESS_PORT, SAI_IN_DROP_REASON_ACL_INGRESS_PORT }, + { ACL_INGRESS_LAG, SAI_IN_DROP_REASON_ACL_INGRESS_LAG }, + { ACL_INGRESS_VLAN, SAI_IN_DROP_REASON_ACL_INGRESS_VLAN }, + { ACL_INGRESS_RIF, SAI_IN_DROP_REASON_ACL_INGRESS_RIF }, + { ACL_INGRESS_SWITCH, SAI_IN_DROP_REASON_ACL_INGRESS_SWITCH }, + { ACL_EGRESS_PORT, SAI_IN_DROP_REASON_ACL_EGRESS_PORT }, + { ACL_EGRESS_LAG, SAI_IN_DROP_REASON_ACL_EGRESS_LAG }, + { ACL_EGRESS_VLAN, SAI_IN_DROP_REASON_ACL_EGRESS_VLAN }, + { ACL_EGRESS_RIF, SAI_IN_DROP_REASON_ACL_EGRESS_RIF }, + { ACL_EGRESS_SWITCH, SAI_IN_DROP_REASON_ACL_EGRESS_SWITCH } +}; + +const unordered_map DropCounter::egress_drop_reason_lookup = +{ + { L2_ANY, SAI_OUT_DROP_REASON_L2_ANY }, + { EGRESS_VLAN_FILTER, SAI_OUT_DROP_REASON_EGRESS_VLAN_FILTER }, + { L3_ANY, SAI_OUT_DROP_REASON_L3_ANY }, + { L3_EGRESS_LINK_DOWN, SAI_OUT_DROP_REASON_L3_EGRESS_LINK_DOWN }, +}; + +// Need to allocate enough space for the SAI to report the drop reasons, 100 +// gives us plenty of space for both ingress and egress drop reasons. +const uint32_t maxDropReasons = 100; + +// If initialization fails, this constructor will throw a runtime error. +DropCounter::DropCounter(const string& counter_name, const string& counter_type, const unordered_set& drop_reasons) + : DebugCounter(counter_name, counter_type), drop_reasons(drop_reasons) +{ + SWSS_LOG_ENTER(); + initializeDropCounterInSAI(); +} + +DropCounter::~DropCounter() +{ + SWSS_LOG_ENTER(); + try + { + DebugCounter::removeDebugCounterFromSAI(); + } + catch (const std::runtime_error& e) + { + SWSS_LOG_ERROR("Failed to remove drop counter '%s' from SAI", name.c_str()); + } +} + +// If we are unable to query the SAI or the type of counter is not supported +// then this method throws a runtime error. +std::string DropCounter::getDebugCounterSAIStat() const +{ + SWSS_LOG_ENTER(); + + sai_attribute_t index_attribute; + index_attribute.id = SAI_DEBUG_COUNTER_ATTR_INDEX; + if (sai_debug_counter_api->get_debug_counter_attribute(counter_id, 1, &index_attribute) != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to get stat for debug counter '%s'", name.c_str()); + throw runtime_error("Failed to get debug counter stat"); + } + + auto index = index_attribute.value.u32; + if (type == PORT_INGRESS_DROPS) + { + return sai_serialize_port_stat(static_cast(SAI_PORT_STAT_IN_DROP_REASON_RANGE_BASE + index)); + } + else if (type == PORT_EGRESS_DROPS) + { + return sai_serialize_port_stat(static_cast(SAI_PORT_STAT_OUT_DROP_REASON_RANGE_BASE + index)); + } + else if (type == SWITCH_INGRESS_DROPS) + { + return sai_serialize_switch_stat(static_cast(SAI_SWITCH_STAT_IN_DROP_REASON_RANGE_BASE + index)); + } + else if (type == SWITCH_EGRESS_DROPS) + { + return sai_serialize_switch_stat(static_cast(SAI_SWITCH_STAT_OUT_DROP_REASON_RANGE_BASE + index)); + } + else + { + SWSS_LOG_ERROR("No stat found for debug counter '%s' of type '%s'", name.c_str(), type.c_str()); + throw runtime_error("No stat found for debug counter"); + } +} + +// If the drop reason is already present on this counter, this method has no +// effect. +// +// If the update fails, this method throws a runtime error. +void DropCounter::addDropReason(const std::string& drop_reason) +{ + SWSS_LOG_ENTER(); + + if (drop_reasons.find(drop_reason) != drop_reasons.end()) + { + SWSS_LOG_DEBUG("Drop reason '%s' already present on '%s'", drop_reason.c_str(), name.c_str()); + return; + } + + try + { + drop_reasons.emplace(drop_reason); + updateDropReasonsInSAI(); + } + catch (const std::runtime_error& e) + { + drop_reasons.erase(drop_reason); + throw; + } +} + +// If the drop reason is not present on this counter, this method has no +// effect. +// +// If the update fails, this method throws a runtime error. +void DropCounter::removeDropReason(const std::string& drop_reason) +{ + SWSS_LOG_ENTER(); + + auto drop_reason_it = drop_reasons.find(drop_reason); + if (drop_reason_it == drop_reasons.end()) + { + SWSS_LOG_DEBUG("Drop reason '%s' not present on '%s'", drop_reason.c_str(), name.c_str()); + return; + } + + try + { + drop_reasons.erase(drop_reason_it); + updateDropReasonsInSAI(); + } + catch (const std::runtime_error& e) + { + drop_reasons.emplace(drop_reason); + throw e; + } +} + +bool DropCounter::isIngressDropReasonValid(const std::string& drop_reason) +{ + return ingress_drop_reason_lookup.find(drop_reason) != ingress_drop_reason_lookup.end(); +} + +bool DropCounter::isEgressDropReasonValid(const std::string& drop_reason) +{ + return egress_drop_reason_lookup.find(drop_reason) != egress_drop_reason_lookup.end(); +} + +// If initialization fails for any reason, this method throws a runtime error. +void DropCounter::initializeDropCounterInSAI() +{ + sai_attribute_t debug_counter_attributes[2]; + vector drop_reason_list(drop_reasons.size()); + DebugCounter::serializeDebugCounterType(debug_counter_attributes[0]); + DropCounter::serializeDropReasons(static_cast(drop_reasons.size()), drop_reason_list.data(), debug_counter_attributes + 1); + DebugCounter::addDebugCounterToSAI(2, debug_counter_attributes); +} + +// serializeDropReasons takes the list of drop reasons associated with this +// counter and stores them in a SAI readable format in drop_reason_attribute. +// +// This method assumes that drop_reason_list points to a region in memory with +// enough space for drop_reason_count drop reasons to be stored. +// +// If any of the provided drop reasons (or their serialization) is undefined, +// then this method throws a runtime error. +void DropCounter::serializeDropReasons(uint32_t drop_reason_count, int32_t *drop_reason_list, sai_attribute_t *drop_reason_attribute) +{ + SWSS_LOG_ENTER(); + + if (type == PORT_INGRESS_DROPS || type == SWITCH_INGRESS_DROPS) + { + drop_reason_attribute->id = SAI_DEBUG_COUNTER_ATTR_IN_DROP_REASON_LIST; + drop_reason_attribute->value.s32list.count = drop_reason_count; + drop_reason_attribute->value.s32list.list = drop_reason_list; + + int index = 0; + for (auto drop_reason: drop_reasons) + { + auto reason_it = ingress_drop_reason_lookup.find(drop_reason); + if (reason_it == ingress_drop_reason_lookup.end()) + { + SWSS_LOG_ERROR("Ingress drop reason '%s' not found", drop_reason.c_str()); + throw runtime_error("Ingress drop reason not found"); + } + + drop_reason_list[index++] = static_cast(reason_it->second); + } + } + else if (type == PORT_EGRESS_DROPS || type == SWITCH_EGRESS_DROPS) + { + drop_reason_attribute->id = SAI_DEBUG_COUNTER_ATTR_OUT_DROP_REASON_LIST; + drop_reason_attribute->value.s32list.count = drop_reason_count; + drop_reason_attribute->value.s32list.list = drop_reason_list; + + int index = 0; + for (auto drop_reason: drop_reasons) + { + auto reason_it = egress_drop_reason_lookup.find(drop_reason); + if (reason_it == egress_drop_reason_lookup.end()) + { + SWSS_LOG_ERROR("Egress drop reason '%s' not found", drop_reason.c_str()); + throw runtime_error("Egress drop reason not found"); + } + + drop_reason_list[index++] = static_cast(reason_it->second); + } + } + else + { + SWSS_LOG_ERROR("Serialization undefined for drop counter type '%s'", type.c_str()); + throw runtime_error("Failed to serialize drop counter attributes"); + } +} + +// If the SAI update fails, this method throws a runtime error. +void DropCounter::updateDropReasonsInSAI() +{ + SWSS_LOG_ENTER(); + + sai_attribute_t updated_drop_reasons; + vector drop_reason_list(drop_reasons.size()); + serializeDropReasons(static_cast(drop_reasons.size()), drop_reason_list.data(), &updated_drop_reasons); + if (sai_debug_counter_api->set_debug_counter_attribute(counter_id, &updated_drop_reasons) != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Could not update drop reasons for drop counter '%s'", name.c_str()); + throw runtime_error("Could not update drop reason list"); + } +} + +// Multiple calls to this function are guaranteed to return the same reasons +// (assuming the device has not been rebooted between calls). The order of +// the list of reasons is not guaranteed to be the same between calls. +// +// If the device does not support querying drop reasons, this method will +// return an empty list. +vector DropCounter::getSupportedDropReasons(sai_debug_counter_attr_t drop_reason_type) +{ + sai_s32_list_t drop_reason_list; + int32_t supported_reasons[maxDropReasons]; + drop_reason_list.count = maxDropReasons; + drop_reason_list.list = supported_reasons; + + if (sai_query_attribute_enum_values_capability(gSwitchId, + SAI_OBJECT_TYPE_DEBUG_COUNTER, + drop_reason_type, + &drop_reason_list) != SAI_STATUS_SUCCESS) + { + SWSS_LOG_NOTICE("This device does not support querying drop reasons"); + return {}; + } + + vector supported_drop_reasons; + for (uint32_t i = 0; i < drop_reason_list.count; i++) + { + string drop_reason; + if (drop_reason_type == SAI_DEBUG_COUNTER_ATTR_IN_DROP_REASON_LIST) + { + drop_reason = sai_serialize_ingress_drop_reason(static_cast(drop_reason_list.list[i])); + } + else + { + drop_reason = sai_serialize_egress_drop_reason(static_cast(drop_reason_list.list[i])); + } + + supported_drop_reasons.push_back(drop_reason); + } + + return supported_drop_reasons; +} + +// serializeSupportedDropReasons takes a list of drop reasons and returns that +// list as a string. +// +// e.g. { "SMAC_EQUALS_DMAC", "INGRESS_VLAN_FILTER" } -> "["SMAC_EQUALS_DMAC","INGRESS_VLAN_FILTER"]" +// e.g. { } -> "[]" +string DropCounter::serializeSupportedDropReasons(vector drop_reasons) +{ + if (drop_reasons.size() == 0) + { + return "[]"; + } + + string supported_drop_reasons; + for (auto const &drop_reason : drop_reasons) + { + supported_drop_reasons += ','; + supported_drop_reasons += drop_reason; + } + + supported_drop_reasons[0] = '['; + return supported_drop_reasons + ']'; +} + +// It is not guaranteed that the amount of available counters will change only +// if counters are added or removed. Depending on the platform, debug counters +// may share hardware resources with other ASIC objects in which case this +// amount may change due to resource allocation in other parts of the system. +// +// If the device does not support querying counter availability, this method +// will return 0. +uint64_t DropCounter::getSupportedDebugCounterAmounts(sai_debug_counter_type_t counter_type) +{ + sai_attribute_t attr; + uint64_t count; + + attr.id = SAI_DEBUG_COUNTER_ATTR_TYPE; + attr.value.s32 = counter_type; + if (sai_object_type_get_availability(gSwitchId, SAI_OBJECT_TYPE_DEBUG_COUNTER, 1, &attr, &count) != SAI_STATUS_SUCCESS) + { + SWSS_LOG_NOTICE("This device does not support querying the number of drop counters"); + return 0; + } + + return count; +} diff --git a/orchagent/debug_counter/drop_counter.h b/orchagent/debug_counter/drop_counter.h new file mode 100644 index 0000000000..8ae34e3d4b --- /dev/null +++ b/orchagent/debug_counter/drop_counter.h @@ -0,0 +1,54 @@ +#ifndef SWSS_UTIL_DROP_COUNTER_H_ +#define SWSS_UTIL_DROP_COUNTER_H_ + +#include +#include +#include +#include +#include "debug_counter.h" +#include "drop_reasons.h" + +extern "C" { +#include "sai.h" +} + +// DropCounter represents a SAI debug counter object that track packet drops. +class DropCounter : public DebugCounter +{ + public: + DropCounter(const std::string& counter_name, + const std::string& counter_type, + const std::unordered_set& drop_reasons) noexcept(false); + DropCounter(const DropCounter&) = delete; + DropCounter& operator=(const DropCounter&) = delete; + virtual ~DropCounter(); + + const std::unordered_set& getDropReasons() const { return drop_reasons; } + + virtual std::string getDebugCounterSAIStat() const noexcept(false); + + void addDropReason(const std::string& drop_reason) noexcept(false); + void removeDropReason(const std::string& drop_reason) noexcept(false); + + static bool isIngressDropReasonValid(const std::string& drop_reason); + static bool isEgressDropReasonValid(const std::string& drop_reason); + + static std::vector getSupportedDropReasons(sai_debug_counter_attr_t drop_reason_type); + static std::string serializeSupportedDropReasons(std::vector drop_reasons); + static uint64_t getSupportedDebugCounterAmounts(sai_debug_counter_type_t counter_type); + + private: + void initializeDropCounterInSAI() noexcept(false); + void serializeDropReasons( + uint32_t drop_reason_count, + int32_t *drop_reason_list, + sai_attribute_t *drop_reason_attribute) noexcept(false); + void updateDropReasonsInSAI() noexcept(false); + + std::unordered_set drop_reasons; + + static const std::unordered_map ingress_drop_reason_lookup; + static const std::unordered_map egress_drop_reason_lookup; +}; + +#endif // SWSS_UTIL_DROP_COUNTER_H_ diff --git a/orchagent/debug_counter/drop_reasons.h b/orchagent/debug_counter/drop_reasons.h new file mode 100644 index 0000000000..08265f21e4 --- /dev/null +++ b/orchagent/debug_counter/drop_reasons.h @@ -0,0 +1,65 @@ +#ifndef DROP_REASONS_H +#define DROP_REASONS_H + +// L2 Drop Reasons +#define L2_ANY "L2_ANY" +#define SMAC_MULTICAST "SMAC_MULTICAST" +#define SMAC_EQUALS_DMAC "SMAC_EQUALS_DMAC" +#define DMAC_RESERVED "DMAC_RESERVED" +#define VLAN_TAG_NOT_ALLOWED "VLAN_TAG_NOT_ALLOWED" +#define INGRESS_VLAN_FILTER "INGRESS_VLAN_FILTER" +#define EGRESS_VLAN_FILTER "EGRESS_VLAN_FILTER" +#define INGRESS_STP_FILTER "INGRESS_STP_FILTER" +#define FDB_UC_DISCARD "FDB_UC_DISCARD" +#define FDB_MC_DISCARD "FDB_MC_DISCARD" +#define L2_LOOPBACK_FILTER "L2_LOOPBACK_FILTER" +#define EXCEEDS_L2_MTU "EXCEEDS_L2_MTU" + +// L3 Drop Reasons +#define L3_ANY "L3_ANY" +#define EXCEEDS_L3_MTU "EXCEEDS_L3_MTU" +#define TTL "TTL" +#define L3_LOOPBACK_FILTER "L3_LOOPBACK_FILTER" +#define NON_ROUTABLE "NON_ROUTABLE" +#define NO_L3_HEADER "NO_L3_HEADER" +#define IP_HEADER_ERROR "IP_HEADER_ERROR" +#define UC_DIP_MC_DMAC "UC_DIP_MC_DMAC" +#define DIP_LOOPBACK "DIP_LOOPBACK" +#define SIP_LOOPBACK "SIP_LOOPBACK" +#define SIP_MC "SIP_MC" +#define SIP_CLASS_E "SIP_CLASS_E" +#define SIP_UNSPECIFIED "SIP_UNSPECIFIED" +#define MC_DMAC_MISMATCH "MC_DMAC_MISMATCH" +#define SIP_EQUALS_DIP "SIP_EQUALS_DIP" +#define SIP_BC "SIP_BC" +#define DIP_LOCAL "DIP_LOCAL" +#define DIP_LINK_LOCAL "DIP_LINK_LOCAL" +#define SIP_LINK_LOCAL "SIP_LINK_LOCAL" +#define IPV6_MC_SCOPE0 "IPV6_MC_SCOPE0" +#define IPV6_MC_SCOPE1 "IPV6_MC_SCOPE1" +#define IRIF_DISABLED "IRIF_DISABLED" +#define ERIF_DISABLED "ERIF_DISABLED" +#define LPM4_MISS "LPM4_MISS" +#define LPM6_MISS "LPM6_MISS" +#define BLACKHOLE_ROUTE "BLACKHOLE_ROUTE" +#define BLACKHOLE_ARP "BLACKHOLE_ARP" +#define UNRESOLVED_NEXT_HOP "UNRESOLVED_NEXT_HOP" +#define L3_EGRESS_LINK_DOWN "L3_EGRESS_LINK_DOWN" + +// Tunnel Drop Reasons +#define DECAP_ERROR "DECAP_ERROR" + +// ACL Drop Reasons +#define ACL_ANY "ACL_ANY" +#define ACL_INGRESS_PORT "ACL_INGRESS_PORT" +#define ACL_INGRESS_LAG "ACL_INGRESS_LAG" +#define ACL_INGRESS_VLAN "ACL_INGRESS_VLAN" +#define ACL_INGRESS_RIF "ACL_INGRESS_RIF" +#define ACL_INGRESS_SWITCH "ACL_INGRESS_SWITCH" +#define ACL_EGRESS_PORT "ACL_EGRESS_PORT" +#define ACL_EGRESS_LAG "ACL_EGRESS_LAG" +#define ACL_EGRESS_VLAN "ACL_EGRESS_VLAN" +#define ACL_EGRESS_RIF "ACL_EGRESS_RIF" +#define ACL_EGRESS_SWITCH "ACL_EGRESS_SWITCH" + +#endif diff --git a/orchagent/debugcounterorch.cpp b/orchagent/debugcounterorch.cpp new file mode 100644 index 0000000000..8c989e6aa0 --- /dev/null +++ b/orchagent/debugcounterorch.cpp @@ -0,0 +1,588 @@ +#include "debugcounterorch.h" +#include "portsorch.h" +#include "rediscommand.h" +#include "sai_serialize.h" +#include "schema.h" +#include "drop_counter.h" +#include + +using std::string; +using std::unordered_map; +using std::unordered_set; +using std::vector; + +extern sai_object_id_t gSwitchId; +extern PortsOrch *gPortsOrch; + +static const unordered_map flex_counter_type_lookup = { + { PORT_INGRESS_DROPS, CounterType::PORT_DEBUG }, + { PORT_EGRESS_DROPS, CounterType::PORT_DEBUG }, + { SWITCH_INGRESS_DROPS, CounterType::SWITCH_DEBUG }, + { SWITCH_EGRESS_DROPS, CounterType::SWITCH_DEBUG }, +}; + +// Initializing DebugCounterOrch creates a group entry in FLEX_COUNTER_DB, so this +// object should only be initialized once. +DebugCounterOrch::DebugCounterOrch(DBConnector *db, const vector& table_names, int poll_interval) : + Orch(db, table_names), + flex_counter_manager(DEBUG_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, poll_interval), + m_stateDb(new DBConnector(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)), + m_debugCapabilitiesTable(new Table(m_stateDb.get(), STATE_DEBUG_COUNTER_CAPABILITIES_NAME)), + m_countersDb(new DBConnector(COUNTERS_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)), + m_counterNameToPortStatMap(new Table(m_countersDb.get(), COUNTERS_DEBUG_NAME_PORT_STAT_MAP)), + m_counterNameToSwitchStatMap(new Table(m_countersDb.get(), COUNTERS_DEBUG_NAME_SWITCH_STAT_MAP)) +{ + SWSS_LOG_ENTER(); + publishDropCounterCapabilities(); + flex_counter_manager.enableFlexCounterGroup(); +} + +DebugCounterOrch::~DebugCounterOrch(void) +{ + SWSS_LOG_ENTER(); +} + +// doTask processes updates from the consumer and modifies the state of the +// following components: +// 1) The ASIC, by creating, modifying, and deleting debug counters +// 2) syncd, by creating, modifying, and deleting flex counters to +// keep track of the debug counters +// +// Updates can fail due to the following: +// 1) Malformed requests: if the update contains an unknown or unsupported +// counter type or drop reason then the update will fail +// 2) SAI failures: if the SAI returns an error for any reason then the +// update will fail +// It is guaranteed that failed updates will not modify the state of the +// system. +// +// In addition, updates are idempotent - repeating the same request any number +// of times will always result in the same external behavior. +void DebugCounterOrch::doTask(Consumer& consumer) +{ + SWSS_LOG_ENTER(); + + // Currently we depend on 1) the switch and 2) the ports being ready + // before we can set up the counters. If debug counters for other + // object types are added we may need to update this dependency. + if (!gPortsOrch->allPortsReady()) + { + return; + } + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + string key = kfvKey(t); + string op = kfvOp(t); + std::vector values = kfvFieldsValues(t); + + auto table_name = consumer.getTableName(); + task_process_status task_status = task_process_status::task_ignore; + if (table_name == CFG_DEBUG_COUNTER_TABLE_NAME) + { + if (op == SET_COMMAND) + { + try + { + task_status = installDebugCounter(key, values); + } + catch (const std::runtime_error& e) + { + SWSS_LOG_ERROR("Failed to create debug counter '%s'", key.c_str()); + task_status = task_process_status::task_failed; + } + } + else if (op == DEL_COMMAND) + { + try + { + task_status = uninstallDebugCounter(key); + } + catch (const std::runtime_error& e) + { + SWSS_LOG_ERROR("Failed to delete debug counter '%s'", key.c_str()); + task_status = task_process_status::task_failed; + } + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s\n", op.c_str()); + } + } + else if (table_name == CFG_DEBUG_COUNTER_DROP_REASON_TABLE_NAME) + { + string counter_name, drop_reason; + parseDropReasonUpdate(key, '|', &counter_name, &drop_reason); + + if (op == SET_COMMAND) + { + try + { + task_status = addDropReason(counter_name, drop_reason); + } + catch (const std::runtime_error& e) + { + SWSS_LOG_ERROR("Failed to add drop reason '%s' to counter '%s'", drop_reason.c_str(), counter_name.c_str()); + task_status = task_process_status::task_failed; + } + } + else if (op == DEL_COMMAND) + { + try + { + task_status = removeDropReason(counter_name, drop_reason); + } + catch (const std::runtime_error& e) + { + SWSS_LOG_ERROR("Failed to remove drop reason '%s' from counter '%s'", drop_reason.c_str(), counter_name.c_str()); + task_status = task_process_status::task_failed; + } + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s\n", op.c_str()); + } + } + else + { + SWSS_LOG_ERROR("Received update from unknown table '%s'", table_name.c_str()); + } + + switch (task_status) + { + case task_process_status::task_success: + consumer.m_toSync.erase(it++); + break; + case task_process_status::task_ignore: + SWSS_LOG_WARN("Debug counters '%s' task ignored", op.c_str()); + consumer.m_toSync.erase(it++); + break; + case task_process_status::task_need_retry: + SWSS_LOG_NOTICE("Failed to process debug counters '%s' task, retrying", op.c_str()); + ++it; + break; + case task_process_status::task_failed: + SWSS_LOG_ERROR("Failed to process debug counters '%s' task, error(s) occured during execution", op.c_str()); + consumer.m_toSync.erase(it++); + break; + default: + SWSS_LOG_ERROR("Invalid task status %d", task_status); + consumer.m_toSync.erase(it++); + break; + } + } +} + +// Debug Capability Reporting Functions START HERE ------------------------------------------------- + +// publishDropCounterCapabilities queries the SAI for available drop counter +// capabilities on this device and publishes the information to the +// DROP_COUNTER_CAPABILITIES table in STATE_DB. +void DebugCounterOrch::publishDropCounterCapabilities() +{ + string supported_ingress_drop_reasons = DropCounter::serializeSupportedDropReasons( + DropCounter::getSupportedDropReasons(SAI_DEBUG_COUNTER_ATTR_IN_DROP_REASON_LIST)); + string supported_egress_drop_reasons = DropCounter::serializeSupportedDropReasons( + DropCounter::getSupportedDropReasons(SAI_DEBUG_COUNTER_ATTR_OUT_DROP_REASON_LIST)); + + for (auto const &counter_type : DebugCounter::getDebugCounterTypeLookup()) + { + string num_counters = std::to_string(DropCounter::getSupportedDebugCounterAmounts(counter_type.second)); + + string drop_reasons; + if (counter_type.first == PORT_INGRESS_DROPS || counter_type.first == SWITCH_INGRESS_DROPS) + { + drop_reasons = supported_ingress_drop_reasons; + } + else + { + drop_reasons = supported_egress_drop_reasons; + } + + // Only include available capabilities in State DB + if (num_counters != "0" && !drop_reasons.empty()) + { + vector fieldValues; + fieldValues.push_back(FieldValueTuple("count", num_counters)); + fieldValues.push_back(FieldValueTuple("reasons", drop_reasons)); + + SWSS_LOG_DEBUG("Setting '%s' capabilities to count='%s', reasons='%s'", counter_type.first.c_str(), num_counters.c_str(), drop_reasons.c_str()); + m_debugCapabilitiesTable->set(counter_type.first, fieldValues); + } + } +} + +// doTask Handler Functions START HERE ------------------------------------------------------------- + +// Note that this function cannot be used to re-initialize a counter. To create a counter +// with the same name but different attributes (e.g. type, drop reasons, etc.) you will need +// to delete the original counter first or use a function like addDropReason, if available. +task_process_status DebugCounterOrch::installDebugCounter(const string& counter_name, const vector& attributes) +{ + SWSS_LOG_ENTER(); + + if (debug_counters.find(counter_name) != debug_counters.end()) + { + SWSS_LOG_DEBUG("Debug counter '%s' already exists", counter_name.c_str()); + return task_process_status::task_success; + } + + // Note: this method currently assumes that all counters are drop counters. + // If you are adding support for a non-drop counter than it may make sense + // to either: a) dispatch to different handlers in doTask or b) dispatch to + // different helper methods in this method. + + string counter_type = getDebugCounterType(attributes); + addFreeCounter(counter_name, counter_type); + reconcileFreeDropCounters(counter_name); + + SWSS_LOG_NOTICE("Succesfully created drop counter %s", counter_name.c_str()); + return task_process_status::task_success; +} + +task_process_status DebugCounterOrch::uninstallDebugCounter(const string& counter_name) +{ + SWSS_LOG_ENTER(); + + auto it = debug_counters.find(counter_name); + if (it == debug_counters.end()) + { + if (free_drop_counters.find(counter_name) != free_drop_counters.end()) + { + deleteFreeCounter(counter_name); + } + else + { + SWSS_LOG_ERROR("Debug counter %s does not exist", counter_name.c_str()); + } + + return task_process_status::task_ignore; + } + + DebugCounter *counter = it->second.get(); + string counter_type = counter->getCounterType(); + string counter_stat = counter->getDebugCounterSAIStat(); + + debug_counters.erase(it); + uninstallDebugFlexCounters(counter_type, counter_stat); + + if (counter_type == PORT_INGRESS_DROPS || counter_type == PORT_EGRESS_DROPS) + { + m_counterNameToPortStatMap->hdel("", counter_name); + } + else + { + m_counterNameToSwitchStatMap->hdel("", counter_name); + } + + SWSS_LOG_NOTICE("Succesfully deleted drop counter %s", counter_name.c_str()); + return task_process_status::task_success; +} + +task_process_status DebugCounterOrch::addDropReason(const string& counter_name, const string& drop_reason) +{ + SWSS_LOG_ENTER(); + + if (!isDropReasonValid(drop_reason)) + { + return task_failed; + } + + auto it = debug_counters.find(counter_name); + if (it == debug_counters.end()) + { + // In order to gracefully handle the case where the drop reason updates + // are received before the create counter update, we keep track of reasons + // we've seen in the free_drop_reasons table. + addFreeDropReason(counter_name, drop_reason); + reconcileFreeDropCounters(counter_name); + + SWSS_LOG_NOTICE("Succesfully created drop counter %s", counter_name.c_str()); + return task_process_status::task_success; + } + + DropCounter *counter = dynamic_cast(it->second.get()); + counter->addDropReason(drop_reason); + + SWSS_LOG_NOTICE("Added drop reason %s to drop counter %s", drop_reason.c_str(), counter_name.c_str()); + return task_process_status::task_success; +} + +// A drop counter must always contain at least one drop reason, so this function +// will do nothing if you attempt to remove the last drop reason. +task_process_status DebugCounterOrch::removeDropReason(const string& counter_name, const string& drop_reason) +{ + SWSS_LOG_ENTER(); + + if (!isDropReasonValid(drop_reason)) + { + return task_failed; + } + + auto it = debug_counters.find(counter_name); + if (it == debug_counters.end()) + { + deleteFreeDropReason(counter_name, drop_reason); + return task_success; + } + + DropCounter *counter = dynamic_cast(it->second.get()); + const unordered_set& drop_reasons = counter->getDropReasons(); + + if (drop_reasons.size() <= 1) + { + SWSS_LOG_WARN("Attempted to remove all drop reasons from counter '%s'", counter_name.c_str()); + return task_ignore; + } + + counter->removeDropReason(drop_reason); + + SWSS_LOG_NOTICE("Removed drop reason %s from drop counter %s", drop_reason.c_str(), counter_name.c_str()); + return task_success; +} + +// Free Table Management Functions START HERE ------------------------------------------------------ + +// Note that entries will remain in the table until at least one drop reason is added to the counter. +void DebugCounterOrch::addFreeCounter(const string& counter_name, const string& counter_type) +{ + SWSS_LOG_ENTER(); + + if (free_drop_counters.find(counter_name) != free_drop_counters.end()) + { + SWSS_LOG_DEBUG("Debug counter '%s' is in free counter table", counter_name.c_str()); + return; + } + + SWSS_LOG_DEBUG("Adding debug counter '%s' to free counter table", counter_name.c_str()); + free_drop_counters.emplace(counter_name, counter_type); +} + +void DebugCounterOrch::deleteFreeCounter(const string& counter_name) +{ + SWSS_LOG_ENTER(); + + if (free_drop_counters.find(counter_name) == free_drop_counters.end()) + { + SWSS_LOG_ERROR("Debug counter %s does not exist", counter_name.c_str()); + return; + } + + SWSS_LOG_DEBUG("Removing debug counter '%s' from free counter table", counter_name.c_str()); + free_drop_counters.erase(counter_name); +} + +// Note that entries will remain in the table until a drop counter is added. +void DebugCounterOrch::addFreeDropReason(const string& counter_name, const string& drop_reason) +{ + SWSS_LOG_ENTER(); + + auto reasons_it = free_drop_reasons.find(counter_name); + + if (reasons_it == free_drop_reasons.end()) + { + SWSS_LOG_DEBUG("Creating free drop reason table for counter '%s'", counter_name.c_str()); + unordered_set new_reasons = { drop_reason }; + free_drop_reasons.emplace(make_pair(counter_name, new_reasons)); + } + else + { + SWSS_LOG_DEBUG("Adding additional drop reasons to free drop reason table for counter '%s'", counter_name.c_str()); + reasons_it->second.emplace(drop_reason); + } +} + +void DebugCounterOrch::deleteFreeDropReason(const string& counter_name, const string& drop_reason) +{ + SWSS_LOG_ENTER(); + + auto reasons_it = free_drop_reasons.find(counter_name); + + if (reasons_it == free_drop_reasons.end()) { + SWSS_LOG_DEBUG("Attempted to remove drop reason '%s' from counter '%s' that does not exist", drop_reason.c_str(), counter_name.c_str()); + return; + } + + SWSS_LOG_DEBUG("Removing free drop reason from counter '%s'", counter_name.c_str()); + reasons_it->second.erase(drop_reason); + + if (reasons_it->second.empty()) { + free_drop_reasons.erase(reasons_it); + } +} + +void DebugCounterOrch::reconcileFreeDropCounters(const string& counter_name) +{ + SWSS_LOG_ENTER(); + + auto counter_it = free_drop_counters.find(counter_name); + auto reasons_it = free_drop_reasons.find(counter_name); + + if (counter_it != free_drop_counters.end() && reasons_it != free_drop_reasons.end()) + { + SWSS_LOG_DEBUG("Found counter '%s' and drop reasons, creating the counter", counter_name.c_str()); + createDropCounter(counter_name, counter_it->second, reasons_it->second); + free_drop_counters.erase(counter_it); + free_drop_reasons.erase(reasons_it); + SWSS_LOG_NOTICE("Succesfully created new drop counter %s", counter_name.c_str()); + } +} + +// Flex Counter Management Functions START HERE ---------------------------------------------------- + +CounterType DebugCounterOrch::getFlexCounterType(const string& counter_type) +{ + SWSS_LOG_ENTER(); + + auto flex_counter_type_it = flex_counter_type_lookup.find(counter_type); + if (flex_counter_type_it == flex_counter_type_lookup.end()) + { + SWSS_LOG_ERROR("Flex counter type '%s' not found", counter_type.c_str()); + throw runtime_error("Flex counter type not found"); + } + return flex_counter_type_it->second; +} + +void DebugCounterOrch::installDebugFlexCounters(const string& counter_type, + const string& counter_stat) +{ + SWSS_LOG_ENTER(); + CounterType flex_counter_type = getFlexCounterType(counter_type); + + if (flex_counter_type == CounterType::SWITCH_DEBUG) + { + flex_counter_manager.addFlexCounterStat(gSwitchId, flex_counter_type, counter_stat); + } + else if (flex_counter_type == CounterType::PORT_DEBUG) + { + for (auto const &curr : gPortsOrch->getAllPorts()) + { + if (curr.second.m_type != Port::Type::PHY) + { + continue; + } + + flex_counter_manager.addFlexCounterStat( + curr.second.m_port_id, + flex_counter_type, + counter_stat); + } + } +} + +void DebugCounterOrch::uninstallDebugFlexCounters(const string& counter_type, + const string& counter_stat) +{ + SWSS_LOG_ENTER(); + CounterType flex_counter_type = getFlexCounterType(counter_type); + + if (flex_counter_type == CounterType::SWITCH_DEBUG) + { + flex_counter_manager.removeFlexCounterStat(gSwitchId, flex_counter_type, counter_stat); + } + else if (flex_counter_type == CounterType::PORT_DEBUG) + { + for (auto const &curr : gPortsOrch->getAllPorts()) + { + if (curr.second.m_type != Port::Type::PHY) + { + continue; + } + + flex_counter_manager.removeFlexCounterStat( + curr.second.m_port_id, + flex_counter_type, + counter_stat); + } + } +} + +// Debug Counter Initialization Helper Functions START HERE ---------------------------------------- + +// NOTE: At this point COUNTER_TYPE is the only field from CONFIG_DB that we care about. In the future +// if other fields become relevant it may make sense to extend this method and return a struct with the +// relevant fields. +std::string DebugCounterOrch::getDebugCounterType(const vector& values) const +{ + SWSS_LOG_ENTER(); + + std::string counter_type; + for (auto attr : values) + { + std::string attr_name = fvField(attr); + auto supported_debug_counter_attributes = DebugCounter::getSupportedDebugCounterAttributes(); + auto attr_name_it = supported_debug_counter_attributes.find(attr_name); + if (attr_name_it == supported_debug_counter_attributes.end()) + { + SWSS_LOG_ERROR("Unknown debug counter attribute '%s'", attr_name.c_str()); + continue; + } + + std::string attr_value = fvValue(attr); + if (attr_name == "type") + { + auto debug_counter_type_lookup = DebugCounter::getDebugCounterTypeLookup(); + auto counter_type_it = debug_counter_type_lookup.find(attr_value); + if (counter_type_it == debug_counter_type_lookup.end()) + { + SWSS_LOG_ERROR("Debug counter type '%s' does not exist", attr_value.c_str()); + throw std::runtime_error("Failed to initialize debug counter"); + } + + counter_type = counter_type_it->first; + } + } + + return counter_type; +} + +// createDropCounter creates a new drop counter in the SAI and installs a +// flex counter to poll the counter data. +// +// If SAI initialization fails or flex counter installation fails then this +// method will throw an exception. +void DebugCounterOrch::createDropCounter(const string& counter_name, const string& counter_type, const unordered_set& drop_reasons) +{ + auto counter = std::unique_ptr(new DropCounter(counter_name, counter_type, drop_reasons)); + std::string counter_stat = counter->getDebugCounterSAIStat(); + debug_counters.emplace(counter_name, std::move(counter)); + installDebugFlexCounters(counter_type, counter_stat); + + if (counter_type == PORT_INGRESS_DROPS || counter_type == PORT_EGRESS_DROPS) + { + m_counterNameToPortStatMap->set("", { FieldValueTuple(counter_name, counter_stat) }); + } + else + { + m_counterNameToSwitchStatMap->set("", { FieldValueTuple(counter_name, counter_stat) }); + } +} + +// Debug Counter Configuration Helper Functions START HERE ----------------------------------------- + +// parseDropReasonUpdate takes a key from CONFIG_DB and returns the 1) the counter name being targeted and +// 2) the drop reason to be added or removed via output parameters. +void DebugCounterOrch::parseDropReasonUpdate(const string& key, const char delimeter, string *counter_name, string *drop_reason) const +{ + size_t counter_end = key.find(delimeter); + *counter_name = key.substr(0, counter_end); + SWSS_LOG_DEBUG("DEBUG_COUNTER COUNTER NAME = %s (%d, %zd)", counter_name->c_str(), 0, counter_end); + *drop_reason = key.substr(counter_end + 1); + SWSS_LOG_DEBUG("DEBUG_COUNTER RULE NAME = %s (%zd, %zd)", drop_reason->c_str(), counter_end + 1, key.length()); +} + +bool DebugCounterOrch::isDropReasonValid(const string& drop_reason) const +{ + SWSS_LOG_ENTER(); + + if (!DropCounter::isIngressDropReasonValid(drop_reason) && + !DropCounter::isEgressDropReasonValid(drop_reason)) + { + SWSS_LOG_ERROR("Drop reason %s not found", drop_reason.c_str()); + return false; + } + + return true; +} \ No newline at end of file diff --git a/orchagent/debugcounterorch.h b/orchagent/debugcounterorch.h new file mode 100644 index 0000000000..49ca64b7f7 --- /dev/null +++ b/orchagent/debugcounterorch.h @@ -0,0 +1,97 @@ +#ifndef DEBUG_COUNTER_ORCH_H +#define DEBUG_COUNTER_ORCH_H + +#include +#include +#include +#include + +#include "orch.h" +#include "flex_counter_stat_manager.h" +#include "debug_counter.h" +#include "drop_counter.h" + +extern "C" { +#include "sai.h" +} + +#define DEBUG_COUNTER_FLEX_COUNTER_GROUP "DEBUG_COUNTER" + +// DebugCounterOrch is an orchestrator for managing debug counters. It handles +// the creation, deletion, and modification of debug counters. +class DebugCounterOrch: public Orch +{ +public: + DebugCounterOrch(swss::DBConnector *db, const std::vector& table_names, int poll_interval); + virtual ~DebugCounterOrch(void); + + void doTask(Consumer& consumer); + +private: + // Debug Capability Reporting Functions + void publishDropCounterCapabilities(); + + // doTask Handler Functions + task_process_status installDebugCounter(const std::string& counter_name, const std::vector& attributes); + task_process_status uninstallDebugCounter(const std::string& counter_name); + task_process_status addDropReason(const std::string& counter_name, const std::string& drop_reason); + task_process_status removeDropReason(const std::string& counter_name, const std::string& drop_reason); + + // Free Table Management Functions + void addFreeCounter(const std::string& counter_name, const std::string& counter_type); + void deleteFreeCounter(const std::string& counter_name); + void addFreeDropReason(const std::string& counter_name, const std::string& drop_reason); + void deleteFreeDropReason(const std::string& counter_name, const std::string& drop_reason); + void reconcileFreeDropCounters(const std::string& counter_name); + + // Flex Counter Management Functions + CounterType getFlexCounterType(const std::string& counter_type) noexcept(false); + void installDebugFlexCounters( + const std::string& counter_type, + const std::string& counter_stat); + void uninstallDebugFlexCounters( + const std::string& counter_type, + const std::string& counter_stat); + + // Debug Counter Initialization Helper Functions + std::string getDebugCounterType( + const std::vector& values) const noexcept(false); + void createDropCounter( + const std::string& counter_name, + const std::string& counter_type, + const std::unordered_set& drop_reasons) noexcept(false); + + // Debug Counter Configuration Helper Functions + void parseDropReasonUpdate( + const std::string& key, + const char delimeter, + std::string *counter_name, + std::string *drop_reason) const; + bool isDropReasonValid(const std::string& drop_reason) const; + + // Data Members + std::shared_ptr m_stateDb = nullptr; + std::shared_ptr m_debugCapabilitiesTable = nullptr; + + std::shared_ptr m_countersDb = nullptr; + std::shared_ptr m_counterNameToPortStatMap = nullptr; + std::shared_ptr m_counterNameToSwitchStatMap = nullptr; + + FlexCounterStatManager flex_counter_manager; + + std::unordered_map> debug_counters; + + // free_drop_counters are drop counters that have been created by a user + // that do not have any drop reasons associated with them yet. Because + // we cannot create a drop counter without any drop reasons, we keep track + // of these counters in this table. + std::unordered_map free_drop_counters; + + // free_drop_reasons are drop reasons that have been added by a user + // that do not have a counter associated with them yet. Because we + // cannot add drop reasons to a counter that doesn't exist yet, + // we keep track of the reasons in this table. + std::unordered_map> free_drop_reasons; +}; + +#endif diff --git a/orchagent/flexcounterorch.cpp b/orchagent/flexcounterorch.cpp index 8493f6897a..c94c1b1891 100644 --- a/orchagent/flexcounterorch.cpp +++ b/orchagent/flexcounterorch.cpp @@ -7,6 +7,8 @@ #include "sai_serialize.h" #include "pfcwdorch.h" #include "bufferorch.h" +#include "flexcounterorch.h" +#include "debugcounterorch.h" extern sai_port_api_t *sai_port_api; @@ -25,6 +27,7 @@ unordered_map flexCounterGroupMap = {"PG_WATERMARK", PG_WATERMARK_STAT_COUNTER_FLEX_COUNTER_GROUP}, {BUFFER_POOL_WATERMARK_KEY, BUFFER_POOL_WATERMARK_STAT_COUNTER_FLEX_COUNTER_GROUP}, {"RIF", RIF_STAT_COUNTER_FLEX_COUNTER_GROUP}, + {"DEBUG_COUNTER", DEBUG_COUNTER_FLEX_COUNTER_GROUP}, }; diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index ada19d1494..756b8a35eb 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -200,6 +200,13 @@ bool OrchDaemon::init() }; SflowOrch *sflow_orch = new SflowOrch(m_applDb, sflow_tables); + vector debug_counter_tables = { + CFG_DEBUG_COUNTER_TABLE_NAME, + CFG_DEBUG_COUNTER_DROP_REASON_TABLE_NAME + }; + + DebugCounterOrch *debug_counter_orch = new DebugCounterOrch(m_configDb, debug_counter_tables, 1000); + /* * The order of the orch list is important for state restore of warm start and * the queued processing in m_toSync map after gPortsOrch->allPortsReady() is set. @@ -208,8 +215,7 @@ bool OrchDaemon::init() * when iterating ConsumerMap. * That is ensured implicitly by the order of map key, "LAG_TABLE" is smaller than "VLAN_TABLE" in lexicographic order. */ - m_orchList = { gSwitchOrch, gCrmOrch, gBufferOrch, gPortsOrch, gIntfsOrch, gNeighOrch, gRouteOrch, copp_orch, tunnel_decap_orch, qos_orch, wm_orch, policer_orch, sflow_orch}; - + m_orchList = { gSwitchOrch, gCrmOrch, gBufferOrch, gPortsOrch, gIntfsOrch, gNeighOrch, gRouteOrch, copp_orch, tunnel_decap_orch, qos_orch, wm_orch, policer_orch, sflow_orch, debug_counter_orch}; bool initialize_dtel = false; if (platform == BFN_PLATFORM_SUBSTRING || platform == VS_PLATFORM_SUBSTRING) diff --git a/orchagent/orchdaemon.h b/orchagent/orchdaemon.h index bfa52fdef8..5e9c9b914a 100644 --- a/orchagent/orchdaemon.h +++ b/orchagent/orchdaemon.h @@ -28,6 +28,7 @@ #include "watermarkorch.h" #include "policerorch.h" #include "sfloworch.h" +#include "debugcounterorch.h" #include "directory.h" using namespace swss; diff --git a/orchagent/saihelper.cpp b/orchagent/saihelper.cpp index dbadc7e288..9fbe449179 100644 --- a/orchagent/saihelper.cpp +++ b/orchagent/saihelper.cpp @@ -42,6 +42,7 @@ sai_fdb_api_t* sai_fdb_api; sai_dtel_api_t* sai_dtel_api; sai_bmtor_api_t* sai_bmtor_api; sai_samplepacket_api_t* sai_samplepacket_api; +sai_debug_counter_api_t* sai_debug_counter_api; extern sai_object_id_t gSwitchId; extern bool gSairedisRecord; @@ -132,6 +133,7 @@ void initSaiApi() sai_api_query(SAI_API_DTEL, (void **)&sai_dtel_api); sai_api_query((sai_api_t)SAI_API_BMTOR, (void **)&sai_bmtor_api); sai_api_query(SAI_API_SAMPLEPACKET, (void **)&sai_samplepacket_api); + sai_api_query(SAI_API_DEBUG_COUNTER, (void **)&sai_debug_counter_api); sai_log_set(SAI_API_SWITCH, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_BRIDGE, SAI_LOG_LEVEL_NOTICE); @@ -159,6 +161,7 @@ void initSaiApi() sai_log_set(SAI_API_DTEL, SAI_LOG_LEVEL_NOTICE); sai_log_set((sai_api_t)SAI_API_BMTOR, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_SAMPLEPACKET, SAI_LOG_LEVEL_NOTICE); + sai_log_set(SAI_API_DEBUG_COUNTER, SAI_LOG_LEVEL_NOTICE); } void initSaiRedis(const string &record_location) diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 247a33bf1a..73574f5b62 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -1,3 +1,8 @@ +FLEX_CTR_DIR = $(top_srcdir)/orchagent/flex_counter +DEBUG_CTR_DIR = $(top_srcdir)/orchagent/debug_counter + +INCLUDES = -I $(FLEX_CTR_DIR) -I $(DEBUG_CTR_DIR) + CFLAGS_SAI = -I /usr/include/sai TESTS = tests @@ -54,7 +59,11 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/flexcounterorch.cpp \ $(top_srcdir)/orchagent/watermarkorch.cpp \ $(top_srcdir)/orchagent/chassisorch.cpp \ - $(top_srcdir)/orchagent/sfloworch.cpp + $(top_srcdir)/orchagent/sfloworch.cpp \ + $(top_srcdir)/orchagent/debugcounterorch.cpp + +tests_SOURCES += $(FLEX_CTR_DIR)/flex_counter_manager.cpp $(FLEX_CTR_DIR)/flex_counter_stat_manager.cpp +tests_SOURCES += $(DEBUG_CTR_DIR)/debug_counter.cpp $(DEBUG_CTR_DIR)/drop_counter.cpp tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) tests_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) -I$(top_srcdir)/orchagent diff --git a/tests/test_drop_counters.py b/tests/test_drop_counters.py new file mode 100644 index 0000000000..473c20885f --- /dev/null +++ b/tests/test_drop_counters.py @@ -0,0 +1,705 @@ +from swsscommon import swsscommon + +import time + +# Supported drop counters +PORT_INGRESS_DROPS = 'PORT_INGRESS_DROPS' +PORT_EGRESS_DROPS = 'PORT_EGRESS_DROPS' +SWITCH_INGRESS_DROPS = 'SWITCH_INGRESS_DROPS' +SWITCH_EGRESS_DROPS = 'SWITCH_EGRESS_DROPS' + +# Debug Counter Table +DEBUG_COUNTER_TABLE = 'DEBUG_COUNTER' +DROP_REASON_TABLE = 'DEBUG_COUNTER_DROP_REASON' + +# Debug Counter Capability Table +CAPABILITIES_TABLE = 'DEBUG_COUNTER_CAPABILITIES' +CAP_COUNT = 'count' +CAP_REASONS = 'reasons' +SUPPORTED_COUNTER_CAPABILITIES = [CAP_COUNT, CAP_REASONS] +INGRESS_COUNTER_TYPES = [PORT_INGRESS_DROPS, SWITCH_INGRESS_DROPS] +EGRESS_COUNTER_TYPES = [PORT_EGRESS_DROPS, SWITCH_EGRESS_DROPS] +SUPPORTED_COUNTER_TYPES = INGRESS_COUNTER_TYPES + EGRESS_COUNTER_TYPES + +# Debug Counter Flex Counter Group +FLEX_COUNTER_GROUP_TABLE = 'FLEX_COUNTER_GROUP_TABLE' +DEBUG_COUNTER_FLEX_GROUP = 'DEBUG_COUNTER' +FLEX_STATS_MODE_FIELD = 'STATS_MODE' +FLEX_POLL_INTERVAL_FIELD = 'POLL_INTERVAL' +FLEX_STATUS_FIELD = 'FLEX_COUNTER_STATUS' +FLEX_STATUS_ENABLE = 'enable' +EXPECTED_FLEX_STATS_MODE = 'STATS_MODE_READ' +EXPECTED_POLL_INTERVAL_THRESHOLD = 0 +EXPECTED_FLEX_GROUP_FIELDS = [FLEX_STATS_MODE_FIELD, FLEX_POLL_INTERVAL_FIELD, FLEX_STATUS_FIELD] + +# Debug Counter Flex Counters +FLEX_COUNTER_TABLE = 'FLEX_COUNTER_TABLE:DEBUG_COUNTER' +PORT_DEBUG_COUNTER_LIST = 'PORT_DEBUG_COUNTER_ID_LIST' +SWITCH_DEBUG_COUNTER_LIST = 'SWITCH_DEBUG_COUNTER_ID_LIST' +PORT_STAT_BASE = 'SAI_PORT_STAT_IN_DROP_REASON_RANGE_BASE' +PORT_STAT_INDEX_1 = 'SAI_PORT_STAT_IN_CONFIGURED_DROP_REASONS_1_DROPPED_PKTS' +SWITCH_STAT_BASE = 'SAI_SWITCH_STAT_IN_DROP_REASON_RANGE_BASE' +SWITCH_STAT_INDEX_1 = 'SAI_SWITCH_STAT_IN_CONFIGURED_DROP_REASONS_1_DROPPED_PKTS' + +# ASIC DB Fields +ASIC_STATE_TABLE = 'ASIC_STATE:SAI_OBJECT_TYPE_DEBUG_COUNTER' +ASIC_COUNTER_TYPE_FIELD = 'SAI_DEBUG_COUNTER_ATTR_TYPE' +ASIC_COUNTER_INGRESS_REASON_LIST_FIELD = 'SAI_DEBUG_COUNTER_ATTR_IN_DROP_REASON_LIST' +ASIC_COUNTER_EGRESS_REASON_LIST_FIELD = 'SAI_DEBUG_COUNTER_ATTR_OUT_DROP_REASON_LIST' +ASIC_COUNTER_PORT_IN_TYPE = 'SAI_DEBUG_COUNTER_TYPE_PORT_IN_DROP_REASONS' +ASIC_COUNTER_PORT_OUT_TYPE = 'SAI_DEBUG_COUNTER_TYPE_PORT_OUT_DROP_REASONS' +ASIC_COUNTER_SWITCH_IN_TYPE = 'SAI_DEBUG_COUNTER_TYPE_SWITCH_IN_DROP_REASONS' +ASIC_COUNTER_SWITCH_OUT_TYPE = 'SAI_DEBUG_COUNTER_TYPE_SWITCH_OUT_DROP_REASONS' +EXPECTED_ASIC_FIELDS = [ASIC_COUNTER_TYPE_FIELD, ASIC_COUNTER_INGRESS_REASON_LIST_FIELD, ASIC_COUNTER_EGRESS_REASON_LIST_FIELD] +EXPECTED_NUM_ASIC_FIELDS = 2 + +# FIXME: It is really annoying to have to re-run tests due to inconsistent timing, should +# implement some sort of polling interface for checking ASIC/flex counter tables after +# applying changes to config DB +class TestDropCounters(object): + def setup_db(self, dvs): + self.asic_db = swsscommon.DBConnector(1, dvs.redis_sock, 0) + self.config_db = swsscommon.DBConnector(4, dvs.redis_sock, 0) + self.flex_db = swsscommon.DBConnector(5, dvs.redis_sock, 0) + self.state_db = swsscommon.DBConnector(6, dvs.redis_sock, 0) + + def genericGetAndAssert(self, table, key): + status, fields = table.get(key) + assert status + return fields + + def checkFlexCounterGroup(self): + flex_group_table = swsscommon.Table(self.flex_db, FLEX_COUNTER_GROUP_TABLE) + status, group_vars = flex_group_table.get(DEBUG_COUNTER_FLEX_GROUP) + assert status + assert len(group_vars) == len(EXPECTED_FLEX_GROUP_FIELDS) + + for var in group_vars: + assert len(var) == 2 + group_field = var[0] + group_contents = var[1] + + assert group_field in EXPECTED_FLEX_GROUP_FIELDS + + if group_field == FLEX_STATS_MODE_FIELD: + assert group_contents == EXPECTED_FLEX_STATS_MODE + elif group_field == FLEX_POLL_INTERVAL_FIELD: + assert int(group_contents) > EXPECTED_POLL_INTERVAL_THRESHOLD + elif group_field == FLEX_STATUS_FIELD: + assert group_contents == FLEX_STATUS_ENABLE + else: + assert False + + def checkFlexState(self, stats, counter_list_type): + flex_counter_table = swsscommon.Table(self.flex_db, FLEX_COUNTER_TABLE) + + for oid in flex_counter_table.getKeys(): + attributes = self.genericGetAndAssert(flex_counter_table, oid) + assert len(attributes) == 1 + field, tracked_stats = attributes[0] + assert field == counter_list_type + for stat in stats: + assert stat in tracked_stats + + def checkASICState(self, counter, counter_type, reasons): + asic_state_table = swsscommon.Table(self.asic_db, ASIC_STATE_TABLE) + asic_counter_params = self.genericGetAndAssert(asic_state_table, counter) + if len(asic_counter_params) != EXPECTED_NUM_ASIC_FIELDS: + return False + + for param in asic_counter_params: + param_name = param[0] + param_contents = param[1] + + if param_name not in EXPECTED_ASIC_FIELDS: + return False + + if param_name == ASIC_COUNTER_TYPE_FIELD: + if param_contents != counter_type: + return False + elif param_name == ASIC_COUNTER_INGRESS_REASON_LIST_FIELD: + if int(param_contents[0]) != len(reasons): + return False + + for reason in reasons: + if reason not in param_contents: + return False + else: + return False + + return True + + def create_drop_counter(self, name, counter_type): + debug_counter_table = swsscommon.Table(self.config_db, DEBUG_COUNTER_TABLE) + counter_metadata = swsscommon.FieldValuePairs([('type', counter_type)]) + debug_counter_table.set(name, counter_metadata) + + def add_drop_reason(self, name, drop_reason): + drop_reason_table = swsscommon.Table(self.config_db, '{}|{}'.format(DROP_REASON_TABLE, name)) + drop_reason_entry = swsscommon.FieldValuePairs([('NULL', 'NULL')]) + drop_reason_table.set(drop_reason, drop_reason_entry) + + def remove_drop_reason(self, name, drop_reason): + drop_reason_table = swsscommon.Table(self.config_db, '{}|{}'.format(DROP_REASON_TABLE, name)) + drop_reason_table._del(drop_reason) + + def delete_drop_counter(self, name): + debug_counter_table = swsscommon.Table(self.config_db, DEBUG_COUNTER_TABLE) + debug_counter_table._del(name) + + def test_deviceCapabilitiesTablePopulated(self, dvs, testlog): + """ + This test verifies that DebugCounterOrch has succesfully queried + the capabilities for this device and populated state DB with the + correct values. + """ + self.setup_db(dvs) + + # Check that the capabilities table 1) exists and 2) has been populated + # for each type of counter + capabilities_table = swsscommon.Table(self.state_db, CAPABILITIES_TABLE) + counter_types = capabilities_table.getKeys() + assert len(counter_types) == len(SUPPORTED_COUNTER_TYPES) + + # Check that the data for each counter type is consistent + for counter_type in SUPPORTED_COUNTER_TYPES: + assert counter_type in counter_types + + # By definiton, each capability entry should contain exactly the same fields + counter_capabilities = self.genericGetAndAssert(capabilities_table, counter_type) + assert len(counter_capabilities) == len(SUPPORTED_COUNTER_CAPABILITIES) + + # Check that the values of each field actually match the + # capabilities currently defined in the virtual switch + for capability in counter_capabilities: + assert len(capability) == 2 + capability_name = capability[0] + capability_contents = capability[1] + + assert capability_name in SUPPORTED_COUNTER_CAPABILITIES + + if capability_name == CAP_COUNT: + assert int(capability_contents) == 3 + elif capability_name == CAP_REASONS and counter_type in INGRESS_COUNTER_TYPES: + assert len(capability_contents.split(',')) == 3 + elif capability_name == CAP_REASONS and counter_type in EGRESS_COUNTER_TYPES: + assert len(capability_contents.split(',')) == 2 + else: + assert False + + def test_flexCounterGroupInitialized(self, dvs, testlog): + """ + This test verifies that DebugCounterOrch has succesfully + setup a flex counter group for the drop counters. + """ + self.setup_db(dvs) + self.checkFlexCounterGroup() + + def test_createAndRemoveDropCounterBasic(self, dvs, testlog): + """ + This test verifies that a drop counter can succesfully be + created and deleted. + """ + self.setup_db(dvs) + + asic_state_table = swsscommon.Table(self.asic_db, ASIC_STATE_TABLE) + flex_counter_table = swsscommon.Table(self.flex_db, FLEX_COUNTER_TABLE) + + name = 'TEST' + reason = 'L3_ANY' + + self.create_drop_counter(name, PORT_INGRESS_DROPS) + + # Because no reasons have been added to the counter yet, nothing should + # be put in ASIC DB and the flex counters should not start polling yet. + assert len(asic_state_table.getKeys()) == 0 + assert len(flex_counter_table.getKeys()) == 0 + + self.add_drop_reason(name, reason) + time.sleep(3) + + # Verify that the flex counters have been created to poll the new + # counter. + self.checkFlexState([PORT_STAT_BASE], PORT_DEBUG_COUNTER_LIST) + + # Verify that the drop counter has been added to ASIC DB with the + # correct reason added. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 1 + assert self.checkASICState(asic_keys[0], ASIC_COUNTER_PORT_IN_TYPE, [reason]) + + self.delete_drop_counter(name) + time.sleep(3) + + # Verify that the counter has been removed from ASIC DB and the flex + # counters have been torn down. + assert len(asic_state_table.getKeys()) == 0 + assert len(flex_counter_table.getKeys()) == 0 + + # Cleanup for the next test. + self.remove_drop_reason(name, reason) + + def test_createAndRemoveDropCounterReversed(self, dvs, testlog): + """ + This test verifies that a drop counter can succesfully be created + and deleted when the drop reasons are added before the counter is + created. + """ + self.setup_db(dvs) + + asic_state_table = swsscommon.Table(self.asic_db, ASIC_STATE_TABLE) + flex_counter_table = swsscommon.Table(self.flex_db, FLEX_COUNTER_TABLE) + + name = 'TEST' + reason = 'L3_ANY' + + self.add_drop_reason(name, reason) + + # Because the actual counter has not been created yet, nothing should + # be put in ASIC DB and the flex counters should not start polling yet. + assert len(asic_state_table.getKeys()) == 0 + assert len(flex_counter_table.getKeys()) == 0 + + self.create_drop_counter(name, PORT_INGRESS_DROPS) + time.sleep(3) + + # Verify that the flex counters have been created to poll the new + # counter. + self.checkFlexState([PORT_STAT_BASE], PORT_DEBUG_COUNTER_LIST) + + # Verify that the drop counter has been added to ASIC DB with the + # correct reason added. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 1 + assert self.checkASICState(asic_keys[0], ASIC_COUNTER_PORT_IN_TYPE, [reason]) + + self.delete_drop_counter(name) + time.sleep(3) + + # Verify that the counter has been removed from ASIC DB and the flex + # counters have been torn down. + assert len(asic_state_table.getKeys()) == 0 + assert len(flex_counter_table.getKeys()) == 0 + + # Cleanup for the next test. + self.remove_drop_reason(name, reason) + + def test_createCounterWithInvalidCounterType(self, dvs, testlog): + """ + This test verifies that the state of the system is unaffected + when an invalid counter type is passed to CONFIG DB. + """ + self.setup_db(dvs) + + asic_state_table = swsscommon.Table(self.asic_db, ASIC_STATE_TABLE) + flex_counter_table = swsscommon.Table(self.flex_db, FLEX_COUNTER_TABLE) + + name = 'BAD_CTR' + reason = 'L3_ANY' + + self.create_drop_counter(name, 'THIS_IS_DEFINITELY_NOT_A_VALID_COUNTER_TYPE') + self.add_drop_reason(name, reason) + time.sleep(3) + + # Verify that nothing has been added to ASIC DB and no flex counters + # were created. + assert len(asic_state_table.getKeys()) == 0 + assert len(flex_counter_table.getKeys()) == 0 + + # Cleanup for the next test. + self.delete_drop_counter(name) + self.remove_drop_reason(name, reason) + + def test_createCounterWithInvalidDropReason(self, dvs, testlog): + """ + This test verifies that the state of the system is unaffected + when an invalid drop reason is passed to CONFIG DB. + """ + self.setup_db(dvs) + + asic_state_table = swsscommon.Table(self.asic_db, ASIC_STATE_TABLE) + flex_counter_table = swsscommon.Table(self.flex_db, FLEX_COUNTER_TABLE) + + name = 'BAD_CTR' + reason = 'THIS_IS_DEFINITELY_NOT_A_VALID_DROP_REASON' + + self.create_drop_counter(name, SWITCH_INGRESS_DROPS) + self.add_drop_reason(name, reason) + time.sleep(3) + + # Verify that nothing has been added to ASIC DB and no flex counters + # were created. + assert len(asic_state_table.getKeys()) == 0 + assert len(flex_counter_table.getKeys()) == 0 + + # Cleanup for the next test. + self.delete_drop_counter(name) + self.remove_drop_reason(name, reason) + + def test_addReasonToInitializedCounter(self, dvs, testlog): + """ + This test verifies that a drop reason can be added to a counter + that has already been initialized. + """ + self.setup_db(dvs) + + asic_state_table = swsscommon.Table(self.asic_db, ASIC_STATE_TABLE) + flex_counter_table = swsscommon.Table(self.flex_db, FLEX_COUNTER_TABLE) + + name = 'ADD_TEST' + reason1 = 'L3_ANY' + + self.create_drop_counter(name, SWITCH_INGRESS_DROPS) + self.add_drop_reason(name, reason1) + time.sleep(3) + + # Verify that a counter has been created. We will verify the state of + # the counter in the next step. + assert len(asic_state_table.getKeys()) == 1 + self.checkFlexState([SWITCH_STAT_BASE], SWITCH_DEBUG_COUNTER_LIST) + + reason2 = 'ACL_ANY' + self.add_drop_reason(name, reason2) + time.sleep(3) + + # Verify that the drop counter has been added to ASIC DB, including the + # reason that was added. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 1 + assert self.checkASICState(asic_keys[0], ASIC_COUNTER_SWITCH_IN_TYPE, [reason1, reason2]) + + # Cleanup for the next test. + self.delete_drop_counter(name) + self.remove_drop_reason(name, reason1) + self.remove_drop_reason(name, reason2) + + def test_removeReasonFromInitializedCounter(self, dvs, testlog): + """ + This test verifies that a drop reason can be removed from a counter + that has already been initialized without deleting the counter. + """ + self.setup_db(dvs) + + asic_state_table = swsscommon.Table(self.asic_db, ASIC_STATE_TABLE) + flex_counter_table = swsscommon.Table(self.flex_db, FLEX_COUNTER_TABLE) + + name = 'ADD_TEST' + reason1 = 'L3_ANY' + reason2 = 'ACL_ANY' + + self.create_drop_counter(name, SWITCH_INGRESS_DROPS) + self.add_drop_reason(name, reason1) + self.add_drop_reason(name, reason2) + time.sleep(3) + + # Verify that a counter has been created. We will verify the state of + # the counter in the next step. + assert len(asic_state_table.getKeys()) == 1 + self.checkFlexState([SWITCH_STAT_BASE], SWITCH_DEBUG_COUNTER_LIST) + + self.remove_drop_reason(name, reason2) + time.sleep(3) + + # Verify that the drop counter has been added to ASIC DB, excluding the + # reason that was removed. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 1 + assert self.checkASICState(asic_keys[0], ASIC_COUNTER_SWITCH_IN_TYPE, [reason1]) + + # Cleanup for the next test. + self.delete_drop_counter(name) + self.remove_drop_reason(name, reason1) + + def test_removeAllDropReasons(self, dvs, testlog): + """ + This test verifies that it is not possible to remove all drop + reasons from a drop counter. + """ + self.setup_db(dvs) + + asic_state_table = swsscommon.Table(self.asic_db, ASIC_STATE_TABLE) + flex_counter_table = swsscommon.Table(self.flex_db, FLEX_COUNTER_TABLE) + + name = 'ADD_TEST' + reason1 = 'L3_ANY' + + self.create_drop_counter(name, SWITCH_INGRESS_DROPS) + self.add_drop_reason(name, reason1) + time.sleep(3) + + # Verify that a counter has been created. We will verify the state of + # the counter in the next step. + assert len(asic_state_table.getKeys()) == 1 + self.checkFlexState([SWITCH_STAT_BASE], SWITCH_DEBUG_COUNTER_LIST) + + self.remove_drop_reason(name, reason1) + time.sleep(3) + + # Verify that the drop counter has been added to ASIC DB, including the + # last reason that we attempted to remove. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 1 + assert self.checkASICState(asic_keys[0], ASIC_COUNTER_SWITCH_IN_TYPE, [reason1]) + + # Cleanup for the next test. + self.delete_drop_counter(name) + + def test_addDropReasonMultipleTimes(self, dvs, testlog): + """ + This test verifies that the same drop reason can be added multiple + times without affecting the system. + """ + self.setup_db(dvs) + + asic_state_table = swsscommon.Table(self.asic_db, ASIC_STATE_TABLE) + flex_counter_table = swsscommon.Table(self.flex_db, FLEX_COUNTER_TABLE) + + name = 'ADD_TEST' + reason1 = 'L3_ANY' + + self.create_drop_counter(name, SWITCH_INGRESS_DROPS) + self.add_drop_reason(name, reason1) + time.sleep(3) + + # Verify that a counter has been created. We will verify the state of + # the counter in the next step. + assert len(asic_state_table.getKeys()) == 1 + self.checkFlexState([SWITCH_STAT_BASE], SWITCH_DEBUG_COUNTER_LIST) + + reason2 = 'ACL_ANY' + self.add_drop_reason(name, reason2) + time.sleep(3) + + # Verify that the drop counter has been added to ASIC DB, including the + # reason that was added. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 1 + assert self.checkASICState(asic_keys[0], ASIC_COUNTER_SWITCH_IN_TYPE, [reason1, reason2]) + + self.add_drop_reason(name, reason2) + time.sleep(3) + + # Verify that the ASIC state is the same as before adding the redundant + # drop reason. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 1 + assert self.checkASICState(asic_keys[0], ASIC_COUNTER_SWITCH_IN_TYPE, [reason1, reason2]) + + # Cleanup for the next test. + self.delete_drop_counter(name) + self.remove_drop_reason(name, reason1) + self.remove_drop_reason(name, reason2) + + def test_addInvalidDropReason(self, dvs, testlog): + """ + This test verifies that adding a drop reason to a counter that is + not recognized will not affect the system. + """ + self.setup_db(dvs) + + asic_state_table = swsscommon.Table(self.asic_db, ASIC_STATE_TABLE) + flex_counter_table = swsscommon.Table(self.flex_db, FLEX_COUNTER_TABLE) + + name = 'ADD_TEST' + reason1 = 'L3_ANY' + + self.create_drop_counter(name, SWITCH_INGRESS_DROPS) + self.add_drop_reason(name, reason1) + time.sleep(3) + + # Verify that a counter has been created. We will verify the state of + # the counter in the next step. + assert len(asic_state_table.getKeys()) == 1 + self.checkFlexState([SWITCH_STAT_BASE], SWITCH_DEBUG_COUNTER_LIST) + + reason2 = 'ACL_ANY' + self.add_drop_reason(name, reason2) + time.sleep(3) + + # Verify that the drop counter has been added to ASIC DB, including the + # reason that was added. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 1 + assert self.checkASICState(asic_keys[0], ASIC_COUNTER_SWITCH_IN_TYPE, [reason1, reason2]) + + dummy_reason = 'ZOBOOMBAFOO' + self.add_drop_reason(name, dummy_reason) + time.sleep(3) + + # Verify that the ASIC state is the same as before adding the invalid + # drop reason. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 1 + assert self.checkASICState(asic_keys[0], ASIC_COUNTER_SWITCH_IN_TYPE, [reason1, reason2]) + + # Cleanup for the next test. + self.delete_drop_counter(name) + self.remove_drop_reason(name, reason1) + self.remove_drop_reason(name, reason2) + + def test_removeDropReasonMultipleTimes(self, dvs, testlog): + """ + This test verifies that removing a drop reason multiple times will + not affect the system. + """ + self.setup_db(dvs) + + asic_state_table = swsscommon.Table(self.asic_db, ASIC_STATE_TABLE) + flex_counter_table = swsscommon.Table(self.flex_db, FLEX_COUNTER_TABLE) + + name = 'ADD_TEST' + reason1 = 'L3_ANY' + reason2 = 'ACL_ANY' + + self.create_drop_counter(name, SWITCH_INGRESS_DROPS) + self.add_drop_reason(name, reason1) + self.add_drop_reason(name, reason2) + time.sleep(3) + + # Verify that a counter has been created. We will verify the state of + # the counter in the next step. + assert len(asic_state_table.getKeys()) == 1 + self.checkFlexState([SWITCH_STAT_BASE], SWITCH_DEBUG_COUNTER_LIST) + + self.remove_drop_reason(name, reason2) + time.sleep(3) + + # Verify that the drop counter has been added to ASIC DB, excluding the + # reason that was removed. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 1 + assert self.checkASICState(asic_keys[0], ASIC_COUNTER_SWITCH_IN_TYPE, [reason1]) + + self.remove_drop_reason(name, reason2) + time.sleep(3) + + # Verify that the ASIC state is the same as before the redundant + # remove operation. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 1 + assert self.checkASICState(asic_keys[0], ASIC_COUNTER_SWITCH_IN_TYPE, [reason1]) + + # Cleanup for the next test. + self.delete_drop_counter(name) + self.remove_drop_reason(name, reason1) + + def test_removeNonexistentDropReason(self, dvs, testlog): + """ + This test verifies that removing a drop reason that does not exist + on the device will not affect the system. + """ + self.setup_db(dvs) + + asic_state_table = swsscommon.Table(self.asic_db, ASIC_STATE_TABLE) + flex_counter_table = swsscommon.Table(self.flex_db, FLEX_COUNTER_TABLE) + + name = 'ADD_TEST' + reason1 = 'L3_ANY' + reason2 = 'ACL_ANY' + + self.create_drop_counter(name, SWITCH_INGRESS_DROPS) + self.add_drop_reason(name, reason1) + time.sleep(3) + + # Verify the counter has been created and is in the correct state. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 1 + assert self.checkASICState(asic_keys[0], ASIC_COUNTER_SWITCH_IN_TYPE, [reason1]) + + self.remove_drop_reason(name, reason2) + time.sleep(3) + + # Verify that the ASIC state is unchanged after the nonexistent remove. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 1 + assert self.checkASICState(asic_keys[0], ASIC_COUNTER_SWITCH_IN_TYPE, [reason1]) + + # Cleanup for the next test. + self.delete_drop_counter(name) + self.remove_drop_reason(name, reason1) + + def test_removeInvalidDropReason(self, dvs, testlog): + """ + This test verifies that removing a drop reason that is not recognized + will not affect the system. + """ + self.setup_db(dvs) + + asic_state_table = swsscommon.Table(self.asic_db, ASIC_STATE_TABLE) + flex_counter_table = swsscommon.Table(self.flex_db, FLEX_COUNTER_TABLE) + + name = 'ADD_TEST' + reason1 = 'L3_ANY' + bogus_reason = 'LIVE_LAUGH_LOVE' + + self.create_drop_counter(name, SWITCH_INGRESS_DROPS) + self.add_drop_reason(name, reason1) + time.sleep(3) + + # Verify the counter has been created and is in the correct state. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 1 + assert self.checkASICState(asic_keys[0], ASIC_COUNTER_SWITCH_IN_TYPE, [reason1]) + + self.remove_drop_reason(name, bogus_reason) + time.sleep(3) + + # Verify that the ASIC state is unchanged after the bad remove. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 1 + assert self.checkASICState(asic_keys[0], ASIC_COUNTER_SWITCH_IN_TYPE, [reason1]) + + # Cleanup for the next test. + self.delete_drop_counter(name) + self.remove_drop_reason(name, reason1) + + def test_createAndDeleteMultipleCounters(self, dvs, testlog): + """ + This test verifies that creating and deleting multiple drop counters + at the same time works correctly. + """ + self.setup_db(dvs) + + asic_state_table = swsscommon.Table(self.asic_db, ASIC_STATE_TABLE) + flex_counter_table = swsscommon.Table(self.flex_db, FLEX_COUNTER_TABLE) + + name1 = 'DEBUG_0' + reason1 = 'L3_ANY' + + name2 = 'DEBUG_1' + reason2 = 'ACL_ANY' + + self.create_drop_counter(name1, PORT_INGRESS_DROPS) + self.add_drop_reason(name1, reason1) + + self.create_drop_counter(name2, PORT_INGRESS_DROPS) + self.add_drop_reason(name2, reason2) + + time.sleep(5) + + # Verify that the flex counters are correctly tracking two different + # drop counters. + self.checkFlexState([PORT_STAT_BASE, PORT_STAT_INDEX_1], PORT_DEBUG_COUNTER_LIST) + + # Verify that there are two entries in the ASIC DB, one for each counter. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 2 + for key in asic_keys: + assert (self.checkASICState(key, ASIC_COUNTER_PORT_IN_TYPE, [reason1]) or self.checkASICState(key, ASIC_COUNTER_PORT_IN_TYPE, [reason2])) + + self.delete_drop_counter(name2) + self.remove_drop_reason(name2, reason2) + time.sleep(3) + + # Verify that the flex counters are tracking ONE drop counter after + # the update. + self.checkFlexState([PORT_STAT_BASE], PORT_DEBUG_COUNTER_LIST) + + # Verify that there is ONE entry in the ASIC DB after the update. + asic_keys = asic_state_table.getKeys() + assert len(asic_keys) == 1 + assert self.checkASICState(asic_keys[0], ASIC_COUNTER_PORT_IN_TYPE, [reason1]) + + # Cleanup for the next test. + self.delete_drop_counter(name1) + self.remove_drop_reason(name1, reason1) From 3c2966ff1274443d2892b8effc80d66a562112cd Mon Sep 17 00:00:00 2001 From: lguohan Date: Mon, 25 Nov 2019 14:19:00 -0800 Subject: [PATCH 23/63] [vstest]: gather logs when dvs fail to start (#1140) --- tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/conftest.py b/tests/conftest.py index 8fba25d8d8..5e1c496c71 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -232,6 +232,7 @@ def __init__(self, name=None, imgname=None, keeptb=False, fakeplatform=None): self.init_asicdb_validator() self.appldb = ApplDbValidator(self) except: + self.get_logs() self.destroy() raise From fc085ee70df1b6e4edf512bcfac212be429ab021 Mon Sep 17 00:00:00 2001 From: Dong Zhang <41927498+dzhangalibaba@users.noreply.github.com> Date: Tue, 26 Nov 2019 14:48:33 -0800 Subject: [PATCH 24/63] [MultiDB]: using new APIs in orch agents and mocktest (#1138) --- cfgmgr/buffermgrd.cpp | 4 +- cfgmgr/intfmgrd.cpp | 6 +- cfgmgr/nbrmgrd.cpp | 6 +- cfgmgr/portmgrd.cpp | 6 +- cfgmgr/sflowmgrd.cpp | 4 +- cfgmgr/teammgrd.cpp | 6 +- cfgmgr/vlanmgrd.cpp | 6 +- cfgmgr/vrfmgrd.cpp | 6 +- cfgmgr/vxlanmgrd.cpp | 6 +- fpmsyncd/fpmsyncd.cpp | 4 +- neighsyncd/neighsyncd.cpp | 4 +- orchagent/aclorch.cpp | 2 +- orchagent/bufferorch.cpp | 4 +- orchagent/countercheckorch.cpp | 2 +- orchagent/crmorch.cpp | 2 +- orchagent/debugcounterorch.cpp | 6 +- orchagent/fdborch.cpp | 2 +- .../flex_counter/flex_counter_manager.cpp | 4 +- orchagent/flexcounterorch.cpp | 2 +- orchagent/intfsorch.cpp | 6 +- orchagent/main.cpp | 6 +- orchagent/orchagent_restart_check.cpp | 2 +- orchagent/pfcwdorch.cpp | 6 +- orchagent/portsorch.cpp | 6 +- orchagent/routeresync.cpp | 2 +- orchagent/watermarkorch.cpp | 4 +- portsyncd/portsyncd.cpp | 6 +- swssconfig/swssconfig.cpp | 4 +- swssconfig/swssplayer.cpp | 5 +- teamsyncd/teamsyncd.cpp | 4 +- tests/mock_tests/aclorch_ut.cpp | 8 +-- tests/mock_tests/database_config.json | 57 +++++++++++++++++++ tests/mock_tests/mock_dbconnector.cpp | 22 +++++++ tests/mock_tests/portsorch_ut.cpp | 6 +- 34 files changed, 150 insertions(+), 76 deletions(-) create mode 100644 tests/mock_tests/database_config.json diff --git a/cfgmgr/buffermgrd.cpp b/cfgmgr/buffermgrd.cpp index ac59b87bd8..9bce96f19c 100644 --- a/cfgmgr/buffermgrd.cpp +++ b/cfgmgr/buffermgrd.cpp @@ -78,8 +78,8 @@ int main(int argc, char **argv) CFG_PORT_CABLE_LEN_TABLE_NAME, }; - DBConnector cfgDb(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector stateDb(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector cfgDb("CONFIG_DB", 0); + DBConnector stateDb("STATE_DB", 0); BufferMgr buffmgr(&cfgDb, &stateDb, pg_lookup_file, cfg_buffer_tables); diff --git a/cfgmgr/intfmgrd.cpp b/cfgmgr/intfmgrd.cpp index 891373dcc7..d184b66e4a 100644 --- a/cfgmgr/intfmgrd.cpp +++ b/cfgmgr/intfmgrd.cpp @@ -48,9 +48,9 @@ int main(int argc, char **argv) CFG_VLAN_SUB_INTF_TABLE_NAME, }; - DBConnector cfgDb(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector appDb(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector stateDb(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector cfgDb("CONFIG_DB", 0); + DBConnector appDb("APPL_DB", 0); + DBConnector stateDb("STATE_DB", 0); IntfMgr intfmgr(&cfgDb, &appDb, &stateDb, cfg_intf_tables); diff --git a/cfgmgr/nbrmgrd.cpp b/cfgmgr/nbrmgrd.cpp index fc2ed2158d..d9b6829036 100644 --- a/cfgmgr/nbrmgrd.cpp +++ b/cfgmgr/nbrmgrd.cpp @@ -49,9 +49,9 @@ int main(int argc, char **argv) CFG_NEIGH_TABLE_NAME, }; - DBConnector cfgDb(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector appDb(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector stateDb(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector cfgDb("CONFIG_DB", 0); + DBConnector appDb("APPL_DB", 0); + DBConnector stateDb("STATE_DB", 0); NbrMgr nbrmgr(&cfgDb, &appDb, &stateDb, cfg_nbr_tables); diff --git a/cfgmgr/portmgrd.cpp b/cfgmgr/portmgrd.cpp index d96ac4e962..b0f0c887dd 100644 --- a/cfgmgr/portmgrd.cpp +++ b/cfgmgr/portmgrd.cpp @@ -44,9 +44,9 @@ int main(int argc, char **argv) CFG_PORT_TABLE_NAME, }; - DBConnector cfgDb(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector appDb(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector stateDb(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector cfgDb("CONFIG_DB", 0); + DBConnector appDb("APPL_DB", 0); + DBConnector stateDb("STATE_DB", 0); PortMgr portmgr(&cfgDb, &appDb, &stateDb, cfg_port_tables); diff --git a/cfgmgr/sflowmgrd.cpp b/cfgmgr/sflowmgrd.cpp index 343f0ead0a..0436ad5f00 100644 --- a/cfgmgr/sflowmgrd.cpp +++ b/cfgmgr/sflowmgrd.cpp @@ -46,8 +46,8 @@ int main(int argc, char **argv) CFG_PORT_TABLE_NAME }; - DBConnector cfgDb(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector appDb(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector cfgDb("CONFIG_DB", 0); + DBConnector appDb("APPL_DB", 0); SflowMgr sflowmgr(&cfgDb, &appDb, cfg_sflow_tables); diff --git a/cfgmgr/teammgrd.cpp b/cfgmgr/teammgrd.cpp index bac429e0a3..6217a0a21a 100644 --- a/cfgmgr/teammgrd.cpp +++ b/cfgmgr/teammgrd.cpp @@ -26,9 +26,9 @@ int main(int argc, char **argv) try { - DBConnector conf_db(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector app_db(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector state_db(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector conf_db("CONFIG_DB", 0); + DBConnector app_db("APPL_DB", 0); + DBConnector state_db("STATE_DB", 0); WarmStart::initialize("teammgrd", "teamd"); WarmStart::checkWarmStart("teammgrd", "teamd"); diff --git a/cfgmgr/vlanmgrd.cpp b/cfgmgr/vlanmgrd.cpp index a2bafe0284..88e4745758 100644 --- a/cfgmgr/vlanmgrd.cpp +++ b/cfgmgr/vlanmgrd.cpp @@ -53,9 +53,9 @@ int main(int argc, char **argv) CFG_VLAN_MEMBER_TABLE_NAME, }; - DBConnector cfgDb(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector appDb(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector stateDb(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector cfgDb("CONFIG_DB", 0); + DBConnector appDb("APPL_DB", 0); + DBConnector stateDb("STATE_DB", 0); WarmStart::initialize("vlanmgrd", "swss"); WarmStart::checkWarmStart("vlanmgrd", "swss"); diff --git a/cfgmgr/vrfmgrd.cpp b/cfgmgr/vrfmgrd.cpp index dc01641002..6a347896b3 100644 --- a/cfgmgr/vrfmgrd.cpp +++ b/cfgmgr/vrfmgrd.cpp @@ -45,9 +45,9 @@ int main(int argc, char **argv) CFG_VNET_TABLE_NAME, }; - DBConnector cfgDb(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector appDb(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector stateDb(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector cfgDb("CONFIG_DB", 0); + DBConnector appDb("APPL_DB", 0); + DBConnector stateDb("STATE_DB", 0); VrfMgr vrfmgr(&cfgDb, &appDb, &stateDb, cfg_vrf_tables); diff --git a/cfgmgr/vxlanmgrd.cpp b/cfgmgr/vxlanmgrd.cpp index 3acd88bdfe..8e86cfbe49 100644 --- a/cfgmgr/vxlanmgrd.cpp +++ b/cfgmgr/vxlanmgrd.cpp @@ -45,9 +45,9 @@ int main(int argc, char **argv) try { - DBConnector cfgDb(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector appDb(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector stateDb(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector cfgDb("CONFIG_DB", 0); + DBConnector appDb("APPL_DB", 0); + DBConnector stateDb("STATE_DB", 0); vector cfg_vnet_tables = { CFG_VNET_TABLE_NAME, diff --git a/fpmsyncd/fpmsyncd.cpp b/fpmsyncd/fpmsyncd.cpp index 8040a7ecb4..59427bf1fa 100644 --- a/fpmsyncd/fpmsyncd.cpp +++ b/fpmsyncd/fpmsyncd.cpp @@ -47,11 +47,11 @@ static bool eoiuFlagsSet(Table &bgpStateTable) int main(int argc, char **argv) { swss::Logger::linkToDbNative("fpmsyncd"); - DBConnector db(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector db("APPL_DB", 0); RedisPipeline pipeline(&db); RouteSync sync(&pipeline); - DBConnector stateDb(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector stateDb("STATE_DB", 0); Table bgpStateTable(&stateDb, STATE_BGP_TABLE_NAME); NetDispatcher::getInstance().registerMessageHandler(RTM_NEWROUTE, &sync); diff --git a/neighsyncd/neighsyncd.cpp b/neighsyncd/neighsyncd.cpp index ad3f9bc065..c14380fb3a 100644 --- a/neighsyncd/neighsyncd.cpp +++ b/neighsyncd/neighsyncd.cpp @@ -15,9 +15,9 @@ int main(int argc, char **argv) { Logger::linkToDbNative("neighsyncd"); - DBConnector appDb(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector appDb("APPL_DB", 0); RedisPipeline pipelineAppDB(&appDb); - DBConnector stateDb(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector stateDb("STATE_DB", 0); NeighSync sync(&pipelineAppDB, &stateDb); diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index fbafdecb37..b12644efd2 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -22,7 +22,7 @@ bool AclOrch::m_bCollectCounters = true; sai_uint32_t AclRule::m_minPriority = 0; sai_uint32_t AclRule::m_maxPriority = 0; -swss::DBConnector AclOrch::m_db(COUNTERS_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); +swss::DBConnector AclOrch::m_db("COUNTERS_DB", 0); swss::Table AclOrch::m_countersTable(&m_db, "COUNTERS"); extern sai_acl_api_t* sai_acl_api; diff --git a/orchagent/bufferorch.cpp b/orchagent/bufferorch.cpp index ee1de1899d..5465d6a6bd 100644 --- a/orchagent/bufferorch.cpp +++ b/orchagent/bufferorch.cpp @@ -36,10 +36,10 @@ type_map BufferOrch::m_buffer_type_maps = { BufferOrch::BufferOrch(DBConnector *db, vector &tableNames) : Orch(db, tableNames), - m_flexCounterDb(new DBConnector(FLEX_COUNTER_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)), + m_flexCounterDb(new DBConnector("FLEX_COUNTER_DB", 0)), m_flexCounterTable(new ProducerTable(m_flexCounterDb.get(), FLEX_COUNTER_TABLE)), m_flexCounterGroupTable(new ProducerTable(m_flexCounterDb.get(), FLEX_COUNTER_GROUP_TABLE)), - m_countersDb(new DBConnector(COUNTERS_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)), + m_countersDb(new DBConnector("COUNTERS_DB", 0)), m_countersDbRedisClient(m_countersDb.get()) { SWSS_LOG_ENTER(); diff --git a/orchagent/countercheckorch.cpp b/orchagent/countercheckorch.cpp index 68edb69e20..097a3a93ff 100644 --- a/orchagent/countercheckorch.cpp +++ b/orchagent/countercheckorch.cpp @@ -24,7 +24,7 @@ CounterCheckOrch& CounterCheckOrch::getInstance(DBConnector *db) CounterCheckOrch::CounterCheckOrch(DBConnector *db, vector &tableNames): Orch(db, tableNames), - m_countersDb(new DBConnector(COUNTERS_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)), + m_countersDb(new DBConnector("COUNTERS_DB", 0)), m_countersTable(new Table(m_countersDb.get(), COUNTERS_TABLE)) { SWSS_LOG_ENTER(); diff --git a/orchagent/crmorch.cpp b/orchagent/crmorch.cpp index 03dcea26dd..537d637063 100644 --- a/orchagent/crmorch.cpp +++ b/orchagent/crmorch.cpp @@ -151,7 +151,7 @@ const map crmUsedCntsTableMap = CrmOrch::CrmOrch(DBConnector *db, string tableName): Orch(db, tableName), - m_countersDb(new DBConnector(COUNTERS_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)), + m_countersDb(new DBConnector("COUNTERS_DB", 0)), m_countersCrmTable(new Table(m_countersDb.get(), COUNTERS_CRM_TABLE)), m_timer(new SelectableTimer(timespec { .tv_sec = CRM_POLLING_INTERVAL_DEFAULT, .tv_nsec = 0 })) { diff --git a/orchagent/debugcounterorch.cpp b/orchagent/debugcounterorch.cpp index 8c989e6aa0..b70c6625c6 100644 --- a/orchagent/debugcounterorch.cpp +++ b/orchagent/debugcounterorch.cpp @@ -26,9 +26,9 @@ static const unordered_map flex_counter_type_lookup = { DebugCounterOrch::DebugCounterOrch(DBConnector *db, const vector& table_names, int poll_interval) : Orch(db, table_names), flex_counter_manager(DEBUG_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, poll_interval), - m_stateDb(new DBConnector(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)), + m_stateDb(new DBConnector("STATE_DB", 0)), m_debugCapabilitiesTable(new Table(m_stateDb.get(), STATE_DEBUG_COUNTER_CAPABILITIES_NAME)), - m_countersDb(new DBConnector(COUNTERS_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)), + m_countersDb(new DBConnector("COUNTERS_DB", 0)), m_counterNameToPortStatMap(new Table(m_countersDb.get(), COUNTERS_DEBUG_NAME_PORT_STAT_MAP)), m_counterNameToSwitchStatMap(new Table(m_countersDb.get(), COUNTERS_DEBUG_NAME_SWITCH_STAT_MAP)) { @@ -585,4 +585,4 @@ bool DebugCounterOrch::isDropReasonValid(const string& drop_reason) const } return true; -} \ No newline at end of file +} diff --git a/orchagent/fdborch.cpp b/orchagent/fdborch.cpp index 2c0ff988df..9a54ba98f1 100644 --- a/orchagent/fdborch.cpp +++ b/orchagent/fdborch.cpp @@ -32,7 +32,7 @@ FdbOrch::FdbOrch(TableConnector applDbConnector, TableConnector stateDbConnector Orch::addExecutor(flushNotifier); /* Add FDB notifications support from ASIC */ - DBConnector *notificationsDb = new DBConnector(ASIC_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector *notificationsDb = new DBConnector("ASIC_DB", 0); m_fdbNotificationConsumer = new swss::NotificationConsumer(notificationsDb, "NOTIFICATIONS"); auto fdbNotifier = new Notifier(m_fdbNotificationConsumer, this, "FDB_NOTIFICATIONS"); Orch::addExecutor(fdbNotifier); diff --git a/orchagent/flex_counter/flex_counter_manager.cpp b/orchagent/flex_counter/flex_counter_manager.cpp index 0b0eddd8c4..323bd309c4 100644 --- a/orchagent/flex_counter/flex_counter_manager.cpp +++ b/orchagent/flex_counter/flex_counter_manager.cpp @@ -45,7 +45,7 @@ FlexCounterManager::FlexCounterManager( stats_mode(stats_mode), polling_interval(polling_interval), enabled(false), - flex_counter_db(new DBConnector(FLEX_COUNTER_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)), + flex_counter_db(new DBConnector("FLEX_COUNTER_DB", 0)), flex_counter_group_table(new ProducerTable(flex_counter_db.get(), FLEX_COUNTER_GROUP_TABLE)), flex_counter_table(new ProducerTable(flex_counter_db.get(), FLEX_COUNTER_TABLE)) { @@ -222,4 +222,4 @@ string FlexCounterManager::serializeCounterStats( } return stats_string; -} \ No newline at end of file +} diff --git a/orchagent/flexcounterorch.cpp b/orchagent/flexcounterorch.cpp index c94c1b1891..571b69c50f 100644 --- a/orchagent/flexcounterorch.cpp +++ b/orchagent/flexcounterorch.cpp @@ -33,7 +33,7 @@ unordered_map flexCounterGroupMap = FlexCounterOrch::FlexCounterOrch(DBConnector *db, vector &tableNames): Orch(db, tableNames), - m_flexCounterDb(new DBConnector(FLEX_COUNTER_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)), + m_flexCounterDb(new DBConnector("FLEX_COUNTER_DB", 0)), m_flexCounterGroupTable(new ProducerTable(m_flexCounterDb.get(), FLEX_COUNTER_GROUP_TABLE)) { SWSS_LOG_ENTER(); diff --git a/orchagent/intfsorch.cpp b/orchagent/intfsorch.cpp index f540224129..ddf2240455 100644 --- a/orchagent/intfsorch.cpp +++ b/orchagent/intfsorch.cpp @@ -55,9 +55,9 @@ IntfsOrch::IntfsOrch(DBConnector *db, string tableName, VRFOrch *vrf_orch) : SWSS_LOG_ENTER(); /* Initialize DB connectors */ - m_counter_db = shared_ptr(new DBConnector(COUNTERS_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)); - m_flex_db = shared_ptr(new DBConnector(FLEX_COUNTER_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)); - m_asic_db = shared_ptr(new DBConnector(ASIC_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)); + m_counter_db = shared_ptr(new DBConnector("COUNTERS_DB", 0)); + m_flex_db = shared_ptr(new DBConnector("FLEX_COUNTER_DB", 0)); + m_asic_db = shared_ptr(new DBConnector("ASIC_DB", 0)); /* Initialize COUNTER_DB tables */ m_rifNameTable = unique_ptr
(new Table(m_counter_db.get(), COUNTERS_RIF_NAME_MAP)); m_rifTypeTable = unique_ptr
(new Table(m_counter_db.get(), COUNTERS_RIF_TYPE_MAP)); diff --git a/orchagent/main.cpp b/orchagent/main.cpp index a0bb8af524..a5591b9d00 100644 --- a/orchagent/main.cpp +++ b/orchagent/main.cpp @@ -291,9 +291,9 @@ int main(int argc, char **argv) SWSS_LOG_NOTICE("Created underlay router interface ID %" PRIx64, gUnderlayIfId); /* Initialize orchestration components */ - DBConnector appl_db(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector config_db(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector state_db(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector appl_db("APPL_DB", 0); + DBConnector config_db("CONFIG_DB", 0); + DBConnector state_db("STATE_DB", 0); auto orchDaemon = make_shared(&appl_db, &config_db, &state_db); diff --git a/orchagent/orchagent_restart_check.cpp b/orchagent/orchagent_restart_check.cpp index aaf0901427..f91f701a53 100644 --- a/orchagent/orchagent_restart_check.cpp +++ b/orchagent/orchagent_restart_check.cpp @@ -105,7 +105,7 @@ int main(int argc, char **argv) } } - swss::DBConnector db(APPL_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + swss::DBConnector db("APPL_DB", 0); // Send warm restart query via "RESTARTCHECK" notification channel swss::NotificationProducer restartQuery(&db, "RESTARTCHECK"); // Will listen for the reply on "RESTARTCHECKREPLY" channel diff --git a/orchagent/pfcwdorch.cpp b/orchagent/pfcwdorch.cpp index d6635b49b3..d9de320527 100644 --- a/orchagent/pfcwdorch.cpp +++ b/orchagent/pfcwdorch.cpp @@ -36,7 +36,7 @@ extern PortsOrch *gPortsOrch; template PfcWdOrch::PfcWdOrch(DBConnector *db, vector &tableNames): Orch(db, tableNames), - m_countersDb(new DBConnector(COUNTERS_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)), + m_countersDb(new DBConnector("COUNTERS_DB", 0)), m_countersTable(new Table(m_countersDb.get(), COUNTERS_TABLE)) { SWSS_LOG_ENTER(); @@ -652,14 +652,14 @@ PfcWdSwOrch::PfcWdSwOrch( const vector &queueAttrIds, int pollInterval): PfcWdOrch(db, tableNames), - m_flexCounterDb(new DBConnector(FLEX_COUNTER_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)), + m_flexCounterDb(new DBConnector("FLEX_COUNTER_DB", 0)), m_flexCounterTable(new ProducerTable(m_flexCounterDb.get(), FLEX_COUNTER_TABLE)), m_flexCounterGroupTable(new ProducerTable(m_flexCounterDb.get(), FLEX_COUNTER_GROUP_TABLE)), c_portStatIds(portStatIds), c_queueStatIds(queueStatIds), c_queueAttrIds(queueAttrIds), m_pollInterval(pollInterval), - m_applDb(make_shared(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)), + m_applDb(make_shared("APPL_DB", 0)), m_applTable(make_shared
(m_applDb.get(), APP_PFC_WD_TABLE_NAME "_INSTORM")), m_applDbRedisClient(m_applDb.get()) { diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index 586a81f755..30e93430ca 100644 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -156,7 +156,7 @@ PortsOrch::PortsOrch(DBConnector *db, vector &tableNames) SWSS_LOG_ENTER(); /* Initialize counter table */ - m_counter_db = shared_ptr(new DBConnector(COUNTERS_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)); + m_counter_db = shared_ptr(new DBConnector("COUNTERS_DB", 0)); m_counterTable = unique_ptr
(new Table(m_counter_db.get(), COUNTERS_PORT_NAME_MAP)); /* Initialize port table */ @@ -173,7 +173,7 @@ PortsOrch::PortsOrch(DBConnector *db, vector &tableNames) m_pgPortTable = unique_ptr
(new Table(m_counter_db.get(), COUNTERS_PG_PORT_MAP)); m_pgIndexTable = unique_ptr
(new Table(m_counter_db.get(), COUNTERS_PG_INDEX_MAP)); - m_flex_db = shared_ptr(new DBConnector(FLEX_COUNTER_DB, DBConnector::DEFAULT_UNIXSOCKET, 0)); + m_flex_db = shared_ptr(new DBConnector("FLEX_COUNTER_DB", 0)); m_flexCounterTable = unique_ptr(new ProducerTable(m_flex_db.get(), FLEX_COUNTER_TABLE)); m_flexCounterGroupTable = unique_ptr(new ProducerTable(m_flex_db.get(), FLEX_COUNTER_GROUP_TABLE)); @@ -315,7 +315,7 @@ PortsOrch::PortsOrch(DBConnector *db, vector &tableNames) removeDefaultBridgePorts(); /* Add port oper status notification support */ - DBConnector *notificationsDb = new DBConnector(ASIC_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector *notificationsDb = new DBConnector("ASIC_DB", 0); m_portStatusNotificationConsumer = new swss::NotificationConsumer(notificationsDb, "NOTIFICATIONS"); auto portStatusNotificatier = new Notifier(m_portStatusNotificationConsumer, this, "PORT_STATUS_NOTIFICATIONS"); Orch::addExecutor(portStatusNotificatier); diff --git a/orchagent/routeresync.cpp b/orchagent/routeresync.cpp index 871d1c34e8..477226374b 100644 --- a/orchagent/routeresync.cpp +++ b/orchagent/routeresync.cpp @@ -19,7 +19,7 @@ int main(int argc, char **argv) SWSS_LOG_ENTER(); - DBConnector db(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector db("APPL_DB", 0); ProducerStateTable r(&db, APP_ROUTE_TABLE_NAME); if (argc != 2) diff --git a/orchagent/watermarkorch.cpp b/orchagent/watermarkorch.cpp index 0b8c20e911..7860ef7cc1 100644 --- a/orchagent/watermarkorch.cpp +++ b/orchagent/watermarkorch.cpp @@ -23,8 +23,8 @@ WatermarkOrch::WatermarkOrch(DBConnector *db, const vector &tables): { SWSS_LOG_ENTER(); - m_countersDb = make_shared(COUNTERS_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - m_appDb = make_shared(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + m_countersDb = make_shared("COUNTERS_DB", 0); + m_appDb = make_shared("APPL_DB", 0); m_countersTable = make_shared
(m_countersDb.get(), COUNTERS_TABLE); m_periodicWatermarkTable = make_shared
(m_countersDb.get(), PERIODIC_WATERMARKS_TABLE); m_persistentWatermarkTable = make_shared
(m_countersDb.get(), PERSISTENT_WATERMARKS_TABLE); diff --git a/portsyncd/portsyncd.cpp b/portsyncd/portsyncd.cpp index 7efec9d58a..8d618c80cb 100644 --- a/portsyncd/portsyncd.cpp +++ b/portsyncd/portsyncd.cpp @@ -65,9 +65,9 @@ int main(int argc, char **argv) } } - DBConnector cfgDb(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector appl_db(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector state_db(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector cfgDb("CONFIG_DB", 0); + DBConnector appl_db("APPL_DB", 0); + DBConnector state_db("STATE_DB", 0); ProducerStateTable p(&appl_db, APP_PORT_TABLE_NAME); SubscriberStateTable portCfg(&cfgDb, CFG_PORT_TABLE_NAME); diff --git a/swssconfig/swssconfig.cpp b/swssconfig/swssconfig.cpp index 02fdaaaa13..058b83a56b 100644 --- a/swssconfig/swssconfig.cpp +++ b/swssconfig/swssconfig.cpp @@ -15,8 +15,6 @@ using namespace std; using namespace swss; using json = nlohmann::json; -int db_port = 6379; -const char* const hostname = "localhost"; const char* const op_name = "OP"; const char* const name_delimiter = ":"; const int el_count = 2; @@ -43,7 +41,7 @@ void dump_db_item(KeyOpFieldsValuesTuple &db_item) bool write_db_data(vector &db_items) { - DBConnector db(APPL_DB, hostname, db_port, 0); + DBConnector db("APPL_DB", 0, true); for (auto &db_item : db_items) { dump_db_item(db_item); diff --git a/swssconfig/swssplayer.cpp b/swssconfig/swssplayer.cpp index a4c0ce5355..d999ace999 100644 --- a/swssconfig/swssplayer.cpp +++ b/swssconfig/swssplayer.cpp @@ -9,11 +9,8 @@ using namespace std; using namespace swss; -constexpr int DB_PORT = 6379; -constexpr char* DB_HOSTNAME = "localhost"; - static int line_index = 0; -static DBConnector db(APPL_DB, DB_HOSTNAME, DB_PORT, 0); +static DBConnector db("APPL_DB", 0, true); void usage() { diff --git a/teamsyncd/teamsyncd.cpp b/teamsyncd/teamsyncd.cpp index faf3e24f57..ef368ca691 100644 --- a/teamsyncd/teamsyncd.cpp +++ b/teamsyncd/teamsyncd.cpp @@ -12,8 +12,8 @@ using namespace swss; int main(int argc, char **argv) { swss::Logger::linkToDbNative(TEAMSYNCD_APP_NAME); - DBConnector db(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); - DBConnector stateDb(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector db("APPL_DB", 0); + DBConnector stateDb("STATE_DB", 0); Select s; TeamSync sync(&db, &stateDb, &s); diff --git a/tests/mock_tests/aclorch_ut.cpp b/tests/mock_tests/aclorch_ut.cpp index c6541dbded..19d52cafbf 100644 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -98,7 +98,7 @@ namespace aclorch_test AclTest() { - m_config_db = make_shared(CONFIG_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + m_config_db = make_shared("CONFIG_DB", 0); } void SetUp() override @@ -235,9 +235,9 @@ namespace aclorch_test AclOrchTest() { // FIXME: move out from constructor - m_app_db = make_shared(APPL_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); - m_config_db = make_shared(CONFIG_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); - m_state_db = make_shared(STATE_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + m_app_db = make_shared("APPL_DB", 0); + m_config_db = make_shared("CONFIG_DB", 0); + m_state_db = make_shared("STATE_DB", 0); } static map gProfileMap; diff --git a/tests/mock_tests/database_config.json b/tests/mock_tests/database_config.json new file mode 100644 index 0000000000..b86ae11bba --- /dev/null +++ b/tests/mock_tests/database_config.json @@ -0,0 +1,57 @@ +{ + "INSTANCES": { + "redis":{ + "hostname" : "127.0.0.1", + "port" : 6379, + "unix_socket_path" : "/var/run/redis/redis.sock" + } + }, + "DATABASES" : { + "APPL_DB" : { + "id" : 0, + "separator": ":", + "instance" : "redis" + }, + "ASIC_DB" : { + "id" : 1, + "separator": ":", + "instance" : "redis" + }, + "COUNTERS_DB" : { + "id" : 2, + "separator": ":", + "instance" : "redis" + }, + "LOGLEVEL_DB" : { + "id" : 3, + "separator": ":", + "instance" : "redis" + }, + "CONFIG_DB" : { + "id" : 4, + "separator": "|", + "instance" : "redis" + }, + "PFC_WD_DB" : { + "id" : 5, + "separator": ":", + "instance" : "redis" + }, + "FLEX_COUNTER_DB" : { + "id" : 5, + "separator": ":", + "instance" : "redis" + }, + "STATE_DB" : { + "id" : 6, + "separator": "|", + "instance" : "redis" + }, + "SNMP_OVERLAY_DB" : { + "id" : 7, + "separator": "|", + "instance" : "redis" + } + }, + "VERSION" : "1.0" +} diff --git a/tests/mock_tests/mock_dbconnector.cpp b/tests/mock_tests/mock_dbconnector.cpp index 6a021e036a..df9b25e179 100644 --- a/tests/mock_tests/mock_dbconnector.cpp +++ b/tests/mock_tests/mock_dbconnector.cpp @@ -38,6 +38,28 @@ namespace swss m_conn->fd = socket(AF_UNIX, SOCK_DGRAM, 0); } + DBConnector::DBConnector(const std::string& dbName, unsigned int timeout, bool isTcpConn) + { + if (swss::SonicDBConfig::isInit() == false) + swss::SonicDBConfig::initialize("./database_config.json"); + m_dbId = swss::SonicDBConfig::getDbId(dbName); + if (isTcpConn) + { + m_conn = (redisContext *)calloc(1, sizeof(redisContext)); + m_conn->connection_type = REDIS_CONN_TCP; + m_conn->tcp.host = strdup(swss::SonicDBConfig::getDbHostname(dbName).c_str()); + m_conn->tcp.port = swss::SonicDBConfig::getDbPort(dbName); + m_conn->fd = socket(AF_UNIX, SOCK_DGRAM, 0); + } + else + { + m_conn = (redisContext *)calloc(1, sizeof(redisContext)); + m_conn->connection_type = REDIS_CONN_UNIX; + m_conn->unix_sock.path = strdup(swss::SonicDBConfig::getDbSock(dbName).c_str()); + m_conn->fd = socket(AF_UNIX, SOCK_DGRAM, 0); + } + } + int DBConnector::getDbId() const { return m_dbId; diff --git a/tests/mock_tests/portsorch_ut.cpp b/tests/mock_tests/portsorch_ut.cpp index 40cfbd3ecc..40b0db6c3b 100644 --- a/tests/mock_tests/portsorch_ut.cpp +++ b/tests/mock_tests/portsorch_ut.cpp @@ -19,11 +19,11 @@ namespace portsorch_test { // FIXME: move out from constructor m_app_db = make_shared( - APPL_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + "APPL_DB", 0); m_config_db = make_shared( - CONFIG_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + "CONFIG_DB", 0); m_state_db = make_shared( - STATE_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + "STATE_DB", 0); } virtual void SetUp() override From b9317511bac3de0f55fa1a7ecdab5742d6e4eabb Mon Sep 17 00:00:00 2001 From: Shuotian Cheng Date: Tue, 26 Nov 2019 18:26:14 -0800 Subject: [PATCH 25/63] [teamsyncd]: Add retry logic in teamsyncd to avoid team handler init failure (#854) * [teamsyncd]: Add retry logic in teamsyncd to avoid team handler init failure team_alloc and team_init fail occasionally when they start the same time as teamd instances. Add the retry logic to avoid such cases. Signed-off-by: Shu0T1an ChenG --- teamsyncd/teamsync.cpp | 72 ++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/teamsyncd/teamsync.cpp b/teamsyncd/teamsync.cpp index a194f5dd9c..64016ff557 100644 --- a/teamsyncd/teamsync.cpp +++ b/teamsyncd/teamsync.cpp @@ -12,6 +12,8 @@ #include "warm_restart.h" #include "teamsync.h" +#include + using namespace std; using namespace std::chrono; using namespace swss; @@ -203,32 +205,54 @@ TeamSync::TeamPortSync::TeamPortSync(const string &lagName, int ifindex, m_lagName(lagName), m_ifindex(ifindex) { - m_team = team_alloc(); - if (!m_team) - { - SWSS_LOG_ERROR("Unable to allocated team socket"); - throw system_error(make_error_code(errc::address_not_available), - "Unable to allocated team socket"); - } - - int err = team_init(m_team, ifindex); - if (err) - { - team_free(m_team); - m_team = NULL; - SWSS_LOG_ERROR("Unable to init team socket"); - throw system_error(make_error_code(errc::address_not_available), - "Unable to init team socket"); - } + int count = 0; + int max_retries = 3; - err = team_change_handler_register(m_team, &gPortChangeHandler, this); - if (err) + while (true) { - team_free(m_team); - m_team = NULL; - SWSS_LOG_ERROR("Unable to register port change event"); - throw system_error(make_error_code(errc::address_not_available), - "Unable to register port change event"); + try + { + m_team = team_alloc(); + if (!m_team) + { + throw system_error(make_error_code(errc::address_not_available), + "Unable to allocate team socket"); + } + + int err = team_init(m_team, ifindex); + if (err) + { + team_free(m_team); + m_team = NULL; + throw system_error(make_error_code(errc::address_not_available), + "Unable to initialize team socket"); + } + + err = team_change_handler_register(m_team, &gPortChangeHandler, this); + if (err) + { + team_free(m_team); + m_team = NULL; + throw system_error(make_error_code(errc::address_not_available), + "Unable to register port change event"); + } + + break; + } + catch (const system_error& e) + { + if (++count == max_retries) + { + throw; + } + else + { + SWSS_LOG_WARN("Failed to initialize team handler. LAG=%s error=%d:%s, attempt=%d", + lagName.c_str(), e.code().value(), e.what(), count); + } + + sleep(1); + } } /* Sync LAG at first */ From 8b4cfb69cbc9c9a918803bb993e26cf88c6ce1d9 Mon Sep 17 00:00:00 2001 From: Andriy Kokhan <43479230+akokhan@users.noreply.github.com> Date: Wed, 27 Nov 2019 09:31:12 +0200 Subject: [PATCH 26/63] Cleanup configure.ac from BFN specific code (#1133) Signed-off-by: Andriy Kokhan --- configure.ac | 4 ---- 1 file changed, 4 deletions(-) diff --git a/configure.ac b/configure.ac index 44071996d5..04899b0249 100644 --- a/configure.ac +++ b/configure.ac @@ -36,10 +36,6 @@ AM_CONDITIONAL(DEBUG, test x$debug = xtrue) CFLAGS_COMMON="-std=c++14 -Wall -fPIC -Wno-write-strings -I/usr/include/libnl3 -I/usr/include/swss" -AM_CONDITIONAL(sonic_asic_platform_barefoot, test x$CONFIGURED_PLATFORM = xbarefoot) -AM_COND_IF([sonic_asic_platform_barefoot], - [CFLAGS_COMMON+=" -I/opt/bfn/install/include/switchsai"]) - CFLAGS_COMMON+=" -Werror" CFLAGS_COMMON+=" -Wno-reorder" CFLAGS_COMMON+=" -Wcast-align" From 7bf63a03b56c1193e2743c1e769234c216d66f13 Mon Sep 17 00:00:00 2001 From: shine4chen <37530989+shine4chen@users.noreply.github.com> Date: Sun, 1 Dec 2019 14:29:03 +0800 Subject: [PATCH 27/63] [teammgrd]during warm-reboot teamd need to recover system-id from saved lacp-pdu (#1003) * during warm-reboot teamd need to use same system-id before warm-reboot Signed-off-by: shine.chen * refine per review comment Signed-off-by: shine.chen --- cfgmgr/teammgr.cpp | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/cfgmgr/teammgr.cpp b/cfgmgr/teammgr.cpp index 8071d44ada..0ff3ddfdf8 100644 --- a/cfgmgr/teammgr.cpp +++ b/cfgmgr/teammgr.cpp @@ -7,6 +7,8 @@ #include "portmgr.h" #include +#include +#include #include #include @@ -396,8 +398,35 @@ task_process_status TeamMgr::addLag(const string &alias, int min_links, bool fal string res; stringstream conf; + + const string dump_path = "/var/warmboot/teamd/"; + MacAddress mac_boot = m_mac; + + // set portchannel mac same with mac before warmStart, when warmStart and there + // is a file written by teamd. + ifstream aliasfile(dump_path + alias); + if (WarmStart::isWarmStart() && aliasfile.is_open()) + { + const int partner_system_id_offset = 40; + string line; + + while (getline(aliasfile, line)) + { + ifstream memberfile(dump_path + line, ios::binary); + uint8_t mac_temp[ETHER_ADDR_LEN]; + + if (!memberfile.is_open()) + continue; + + memberfile.seekg(partner_system_id_offset, std::ios::beg); + memberfile.read(reinterpret_cast(mac_temp), ETHER_ADDR_LEN); + mac_boot = MacAddress(mac_temp); + break; + } + } + conf << "'{\"device\":\"" << alias << "\"," - << "\"hwaddr\":\"" << m_mac.to_string() << "\"," + << "\"hwaddr\":\"" << mac_boot.to_string() << "\"," << "\"runner\":{" << "\"active\":true," << "\"name\":\"lacp\""; @@ -418,7 +447,6 @@ task_process_status TeamMgr::addLag(const string &alias, int min_links, bool fal alias.c_str(), conf.str().c_str()); string warmstart_flag = WarmStart::isWarmStart() ? " -w -o " : " -r "; - const string dump_path = "/var/warmboot/teamd/"; cmd << TEAMD_CMD << warmstart_flag From efe142aa2f0b6ddf67bd1085e2f1536fc7dea322 Mon Sep 17 00:00:00 2001 From: Ze Gan Date: Tue, 3 Dec 2019 23:10:58 +0800 Subject: [PATCH 28/63] Fix bug: Wrong condition for mac address (#1142) Signed-off-by: Ze Gan --- cfgmgr/vxlanmgr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfgmgr/vxlanmgr.cpp b/cfgmgr/vxlanmgr.cpp index e06321d516..cdb3c2ab80 100644 --- a/cfgmgr/vxlanmgr.cpp +++ b/cfgmgr/vxlanmgr.cpp @@ -299,7 +299,7 @@ bool VxlanMgr::doVxlanCreateTask(const KeyOpFieldsValuesTuple & t) // If the mac address has been set auto macAddress = getVxlanRouterMacAddress(); - if (macAddress.first) + if (!macAddress.first) { SWSS_LOG_DEBUG("Mac address is not ready"); // Suspend this message util the mac address is set From a4a1d3be49c39eb1f137b02bdf1c4b479bbe1cba Mon Sep 17 00:00:00 2001 From: Volodymyr Samotiy Date: Tue, 3 Dec 2019 18:27:29 +0200 Subject: [PATCH 29/63] [vnet]: Update VNET route table size to 40K for BITMAP implementation (#1132) Signed-off-by: Volodymyr Samotiy --- orchagent/vnetorch.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchagent/vnetorch.h b/orchagent/vnetorch.h index 965b8cadb8..d994da4420 100644 --- a/orchagent/vnetorch.h +++ b/orchagent/vnetorch.h @@ -14,7 +14,7 @@ #include "observer.h" #define VNET_BITMAP_SIZE 32 -#define VNET_TUNNEL_SIZE 3072 +#define VNET_TUNNEL_SIZE 40960 #define VNET_NEIGHBOR_MAX 0xffff #define VXLAN_ENCAP_TTL 128 From 03be9833cc94cfc52bd95f072eeef5a8bbf1fc48 Mon Sep 17 00:00:00 2001 From: rajendra-dendukuri <47423477+rajendra-dendukuri@users.noreply.github.com> Date: Tue, 10 Dec 2019 11:13:12 -0500 Subject: [PATCH 30/63] Increase ip2me CIR/CBR for faster in-band file transfers (#1000) Increase incoming packet rate on in-band interfaces to support faster download of large files. SONiC firmware image download over in-band can take a lot of time if the incoming packet rate is limited to 600pps. This, change increases it to 6000pps. Especially when used by Zero Touch Provisioning or by sonic_installer for firmware upgrade over in-band interfaces. Signed-off-by: Rajendra Dendukuri --- swssconfig/sample/00-copp.config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/swssconfig/sample/00-copp.config.json b/swssconfig/sample/00-copp.config.json index e82242830c..de3ec33c7f 100644 --- a/swssconfig/sample/00-copp.config.json +++ b/swssconfig/sample/00-copp.config.json @@ -50,8 +50,8 @@ "queue": "1", "meter_type":"packets", "mode":"sr_tcm", - "cir":"600", - "cbs":"600", + "cir":"6000", + "cbs":"6000", "red_action":"drop" }, "OP": "SET" From b8745f89369cf027457cead34267cbcaa62ff405 Mon Sep 17 00:00:00 2001 From: Volodymyr Samotiy Date: Thu, 12 Dec 2019 19:25:29 +0200 Subject: [PATCH 31/63] [bitmap_vnet]: Fix removal flow for tunnel route (#1139) There was a mistake in BITMAP VNET code that caused used_count for endpoints not to be decremented during removing tunnel route. As a result some SDK resources were not fully removed in case one endpoint is shared between two or more tunnel routes. Signed-off-by: Volodymyr Samotiy --- orchagent/vnetorch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchagent/vnetorch.cpp b/orchagent/vnetorch.cpp index 0dae814e86..bfe15b702e 100644 --- a/orchagent/vnetorch.cpp +++ b/orchagent/vnetorch.cpp @@ -1102,7 +1102,7 @@ bool VNetBitmapObject::removeTunnelRoute(IpPrefix& ipPrefix) throw std::runtime_error("VNET tunnel route removal failed"); } - auto endpointInfo = endpointMap_.at(endpoint); + auto& endpointInfo = endpointMap_.at(endpoint); sai_status_t status; From 823e4263dd7e44abd04b96dcda89ac3f89b6dcfa Mon Sep 17 00:00:00 2001 From: Danny Allen Date: Thu, 12 Dec 2019 19:00:14 -0800 Subject: [PATCH 32/63] [aclorch] Enable DSCP rules on IPv6 mirror tables (#1146) - Enable DSCP field for MIRRORV6 tables - Add vs test for MIRRORV6 table initialization Signed-off-by: Danny Allen --- orchagent/aclorch.cpp | 2 +- tests/test_mirror_ipv6_separate.py | 64 ++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index b12644efd2..b25d18a44c 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -1427,7 +1427,7 @@ bool AclTable::create() attr.value.s32 = acl_stage; table_attrs.push_back(attr); - if (type == ACL_TABLE_MIRROR) + if (type == ACL_TABLE_MIRROR || type == ACL_TABLE_MIRRORV6) { attr.id = SAI_ACL_TABLE_ATTR_FIELD_DSCP; attr.value.booldata = true; diff --git a/tests/test_mirror_ipv6_separate.py b/tests/test_mirror_ipv6_separate.py index 8b12d0fbec..06f64e8691 100644 --- a/tests/test_mirror_ipv6_separate.py +++ b/tests/test_mirror_ipv6_separate.py @@ -136,6 +136,70 @@ def remove_mirror_acl_rule(self, table, rule): tbl._del(table + "|" + rule) time.sleep(1) + def test_MirrorV6TableCreation(self, dvs, testlog): + self.setup_db(dvs) + + acl_table_v6 = "MIRROR_TABLE_V6" + ports = ["Ethernet0", "Ethernet4"] + + # Create the V6 table + self.create_acl_table(acl_table_v6, ports, "MIRRORV6") + + # Check that the V6 table has been created + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_TABLE") + table_entries = [k for k in tbl.getKeys() if k not in dvs.asicdb.default_acl_tables] + assert len(table_entries) == 1 + + # Get the data from the V6 table + v6_table_id = table_entries[0] + status, attributes = tbl.get(v6_table_id) + assert status + + # TODO: Refactor mirror table tests so that these attributes can be shared between tests for v4, v6, and + # dscp mirror tables. + expected_sai_attributes = [ + "SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE", + "SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL", + "SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6", + "SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6", + "SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE", + "SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE", + "SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT", + "SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT", + "SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS", + "SAI_ACL_TABLE_ATTR_FIELD_DSCP", + ] + + expected_sai_list_attributes = [ + "SAI_ACL_TABLE_ATTR_FIELD_ACL_RANGE_TYPE", + "SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST" + ] + + # Check that all of the V6 table attributes have been populated + for attribute in attributes: + key = attribute[0] + value = attribute[1] + + if key in expected_sai_attributes: + assert value == "true" + elif key in expected_sai_list_attributes: + count = int(value[0:1]) + list_attrs = value[2:].split(',') + if key == "SAI_ACL_TABLE_ATTR_FIELD_ACL_RANGE_TYPE": + assert set(list_attrs) == set(["SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE", "SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE"]) + elif key == "SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST": + assert set(list_attrs) == set(["SAI_ACL_BIND_POINT_TYPE_PORT", "SAI_ACL_BIND_POINT_TYPE_LAG"]) + else: + print("Encountered unexpected range attribute on mirror table: {}".format(key)) + assert False + elif key == "SAI_ACL_TABLE_ATTR_ACL_STAGE": + assert value == "SAI_ACL_STAGE_INGRESS" + else: + print("Encountered unexpected attribute on mirror table: {}".format(key)) + assert False + + # Delete the V6 table + self.remove_acl_table(acl_table_v6) # Test case - create a MIRROR table and a MIRRORV6 table in separated mode # 0. predefine the VS platform: mellanox platform From 9f6efa088d8645572b82af590f20e8d60e9be285 Mon Sep 17 00:00:00 2001 From: Stepan Blyshchak <38952541+stepanblyschak@users.noreply.github.com> Date: Wed, 18 Dec 2019 13:56:56 +0200 Subject: [PATCH 33/63] [port/buffer] introduce a sync mechanism to protect port PG/queue from changes under PFC storm (#1143) * [port/buffer] introduce a sync mechanism to protect port PG/queue from changes under PFC storm Signed-off-by: Stepan Blyschak * [pfcactionhandler] fix pg lock bit is not set Signed-off-by: Stepan Blyschak * [portsorch_ut] add PfcZeroBufferHandlerLocksPortPgAndQueue unit test Signed-off-by: Stepan Blyschak * [pfcactionhandler] add method header Signed-off-by: Stepan Blyschak * [port.h] fix typos Signed-off-by: Stepan Blyschak * [pfcactionhandler] fix method name that set lock bits Signed-off-by: Stepan Blyschak --- orchagent/bufferorch.cpp | 10 ++ orchagent/pfcactionhandler.cpp | 32 +++++-- orchagent/pfcactionhandler.h | 12 ++- orchagent/port.h | 10 ++ orchagent/portsorch.cpp | 2 + tests/mock_tests/mock_orchagent_main.h | 1 + tests/mock_tests/portsorch_ut.cpp | 122 +++++++++++++++++++++++++ tests/mock_tests/ut_saihelper.cpp | 2 + 8 files changed, 181 insertions(+), 10 deletions(-) diff --git a/orchagent/bufferorch.cpp b/orchagent/bufferorch.cpp index 5465d6a6bd..7ab11ca650 100644 --- a/orchagent/bufferorch.cpp +++ b/orchagent/bufferorch.cpp @@ -562,6 +562,11 @@ task_process_status BufferOrch::processQueue(Consumer &consumer) SWSS_LOG_ERROR("Invalid queue index specified:%zd", ind); return task_process_status::task_invalid_entry; } + if (port.m_queue_lock[ind]) + { + SWSS_LOG_WARN("Queue %zd on port %s is locked, will retry", ind, port_name.c_str()); + return task_process_status::task_need_retry; + } queue_id = port.m_queue_ids[ind]; SWSS_LOG_DEBUG("Applying buffer profile:0x%" PRIx64 " to queue index:%zd, queue sai_id:0x%" PRIx64, sai_buffer_profile, ind, queue_id); sai_status_t sai_status = sai_queue_api->set_queue_attribute(queue_id, &attr); @@ -661,6 +666,11 @@ task_process_status BufferOrch::processPriorityGroup(Consumer &consumer) SWSS_LOG_ERROR("Invalid pg index specified:%zd", ind); return task_process_status::task_invalid_entry; } + if (port.m_priority_group_lock[ind]) + { + SWSS_LOG_WARN("Priority group %zd on port %s is locked, will retry", ind, port_name.c_str()); + return task_process_status::task_need_retry; + } pg_id = port.m_priority_group_ids[ind]; SWSS_LOG_DEBUG("Applying buffer profile:0x%" PRIx64 " to port:%s pg index:%zd, pg sai_id:0x%" PRIx64, sai_buffer_profile, port_name.c_str(), ind, pg_id); sai_status_t sai_status = sai_buffer_api->set_ingress_priority_group_attribute(pg_id, &attr); diff --git a/orchagent/pfcactionhandler.cpp b/orchagent/pfcactionhandler.cpp index b78b9e993e..153016c729 100644 --- a/orchagent/pfcactionhandler.cpp +++ b/orchagent/pfcactionhandler.cpp @@ -434,6 +434,15 @@ PfcWdZeroBufferHandler::PfcWdZeroBufferHandler(sai_object_id_t port, { SWSS_LOG_ENTER(); + Port portInstance; + if (!gPortsOrch->getPort(port, portInstance)) + { + SWSS_LOG_ERROR("Cannot get port by ID 0x%" PRIx64, port); + return; + } + + setPriorityGroupAndQueueLockFlag(portInstance, true); + sai_attribute_t attr; attr.id = SAI_QUEUE_ATTR_BUFFER_PROFILE_ID; @@ -462,13 +471,6 @@ PfcWdZeroBufferHandler::PfcWdZeroBufferHandler(sai_object_id_t port, m_originalQueueBufferProfile = oldQueueProfileId; // Get PG - Port portInstance; - if (!gPortsOrch->getPort(port, portInstance)) - { - SWSS_LOG_ERROR("Cannot get port by ID 0x%" PRIx64, port); - return; - } - sai_object_id_t pg = portInstance.m_priority_group_ids[static_cast (queueId)]; attr.id = SAI_INGRESS_PRIORITY_GROUP_ATTR_BUFFER_PROFILE; @@ -533,6 +535,22 @@ PfcWdZeroBufferHandler::~PfcWdZeroBufferHandler(void) SWSS_LOG_ERROR("Failed to set buffer profile ID on queue 0x%" PRIx64 ": %d", getQueue(), status); return; } + + setPriorityGroupAndQueueLockFlag(portInstance, false); +} + +void PfcWdZeroBufferHandler::setPriorityGroupAndQueueLockFlag(Port& port, bool isLocked) const +{ + // set lock bits on PG and queue + port.m_priority_group_lock[static_cast(getQueueId())] = isLocked; + for (size_t i = 0; i < port.m_queue_ids.size(); ++i) + { + if (port.m_queue_ids[i] == getQueue()) + { + port.m_queue_lock[i] = isLocked; + } + } + gPortsOrch->setPort(port.m_alias, port); } PfcWdZeroBufferHandler::ZeroBufferProfile::ZeroBufferProfile(void) diff --git a/orchagent/pfcactionhandler.h b/orchagent/pfcactionhandler.h index 07a27babec..e7739cf341 100644 --- a/orchagent/pfcactionhandler.h +++ b/orchagent/pfcactionhandler.h @@ -31,17 +31,17 @@ class PfcWdActionHandler uint8_t queueId, shared_ptr
countersTable); virtual ~PfcWdActionHandler(void); - inline sai_object_id_t getPort(void) + inline sai_object_id_t getPort(void) const { return m_port; } - inline sai_object_id_t getQueue(void) + inline sai_object_id_t getQueue(void) const { return m_queue; } - inline sai_object_id_t getQueueId(void) + inline uint8_t getQueueId(void) const { return m_queueId; } @@ -123,6 +123,12 @@ class PfcWdZeroBufferHandler: public PfcWdLossyHandler virtual ~PfcWdZeroBufferHandler(void); private: + /* + * Sets lock bits on port's priority group and queue + * to protect them from beeing changed by other Orch's + */ + void setPriorityGroupAndQueueLockFlag(Port& port, bool isLocked) const; + // Singletone class for keeping shared data - zero buffer profiles class ZeroBufferProfile { diff --git a/orchagent/port.h b/orchagent/port.h index fac1cae2b6..2af0eb85db 100644 --- a/orchagent/port.h +++ b/orchagent/port.h @@ -96,6 +96,16 @@ class Port std::vector m_priority_group_ids; sai_port_priority_flow_control_mode_t m_pfc_asym = SAI_PORT_PRIORITY_FLOW_CONTROL_MODE_COMBINED; uint8_t m_pfc_bitmask = 0; + /* + * Following two bit vectors are used to lock + * the PG/queue from being changed in BufferOrch. + * The use case scenario is when PfcWdZeroBufferHandler + * sets zero buffer profile it should protect PG/queue + * from being overwritten in BufferOrch. + */ + std::vector m_queue_lock; + std::vector m_priority_group_lock; + }; } diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index 30e93430ca..f5d616323c 100644 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -2608,6 +2608,7 @@ void PortsOrch::initializeQueues(Port &port) SWSS_LOG_INFO("Get %d queues for port %s", attr.value.u32, port.m_alias.c_str()); port.m_queue_ids.resize(attr.value.u32); + port.m_queue_lock.resize(attr.value.u32); if (attr.value.u32 == 0) { @@ -2643,6 +2644,7 @@ void PortsOrch::initializePriorityGroups(Port &port) SWSS_LOG_INFO("Get %d priority groups for port %s", attr.value.u32, port.m_alias.c_str()); port.m_priority_group_ids.resize(attr.value.u32); + port.m_priority_group_lock.resize(attr.value.u32); if (attr.value.u32 == 0) { diff --git a/tests/mock_tests/mock_orchagent_main.h b/tests/mock_tests/mock_orchagent_main.h index d2d313279d..9971463ce4 100644 --- a/tests/mock_tests/mock_orchagent_main.h +++ b/tests/mock_tests/mock_orchagent_main.h @@ -54,3 +54,4 @@ extern sai_tunnel_api_t *sai_tunnel_api; extern sai_next_hop_api_t *sai_next_hop_api; extern sai_hostif_api_t *sai_hostif_api; extern sai_buffer_api_t *sai_buffer_api; +extern sai_queue_api_t *sai_queue_api; diff --git a/tests/mock_tests/portsorch_ut.cpp b/tests/mock_tests/portsorch_ut.cpp index 40b0db6c3b..0bd88bc0fa 100644 --- a/tests/mock_tests/portsorch_ut.cpp +++ b/tests/mock_tests/portsorch_ut.cpp @@ -1,6 +1,7 @@ #include "ut_helper.h" #include "mock_orchagent_main.h" #include "mock_table.h" +#include "pfcactionhandler.h" #include @@ -14,12 +15,15 @@ namespace portsorch_test shared_ptr m_app_db; shared_ptr m_config_db; shared_ptr m_state_db; + shared_ptr m_counters_db; PortsOrchTest() { // FIXME: move out from constructor m_app_db = make_shared( "APPL_DB", 0); + m_counters_db = make_shared( + "COUNTERS_DB", 0); m_config_db = make_shared( "CONFIG_DB", 0); m_state_db = make_shared( @@ -310,4 +314,122 @@ namespace portsorch_test gBufferOrch->dumpPendingTasks(ts); ASSERT_TRUE(ts.empty()); } + + TEST_F(PortsOrchTest, PfcZeroBufferHandlerLocksPortPgAndQueue) + { + Table portTable = Table(m_app_db.get(), APP_PORT_TABLE_NAME); + Table pgTable = Table(m_config_db.get(), CFG_BUFFER_PG_TABLE_NAME); + Table profileTable = Table(m_config_db.get(), CFG_BUFFER_PROFILE_TABLE_NAME); + Table poolTable = Table(m_config_db.get(), CFG_BUFFER_POOL_TABLE_NAME); + + // Get SAI default ports to populate DB + auto ports = ut_helper::getInitialSaiPorts(); + + // Create dependencies ... + + const int portsorch_base_pri = 40; + + vector ports_tables = { + { APP_PORT_TABLE_NAME, portsorch_base_pri + 5 }, + { APP_VLAN_TABLE_NAME, portsorch_base_pri + 2 }, + { APP_VLAN_MEMBER_TABLE_NAME, portsorch_base_pri }, + { APP_LAG_TABLE_NAME, portsorch_base_pri + 4 }, + { APP_LAG_MEMBER_TABLE_NAME, portsorch_base_pri } + }; + + ASSERT_EQ(gPortsOrch, nullptr); + gPortsOrch = new PortsOrch(m_app_db.get(), ports_tables); + vector buffer_tables = { CFG_BUFFER_POOL_TABLE_NAME, + CFG_BUFFER_PROFILE_TABLE_NAME, + CFG_BUFFER_QUEUE_TABLE_NAME, + CFG_BUFFER_PG_TABLE_NAME, + CFG_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME, + CFG_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME }; + + ASSERT_EQ(gBufferOrch, nullptr); + gBufferOrch = new BufferOrch(m_config_db.get(), buffer_tables); + + // Populate port table with SAI ports + for (const auto &it : ports) + { + portTable.set(it.first, it.second); + } + + // Set PortConfigDone, PortInitDone + portTable.set("PortConfigDone", { { "count", to_string(ports.size()) } }); + portTable.set("PortInitDone", { { "lanes", "0" } }); + + // refill consumer + gPortsOrch->addExistingData(&portTable); + + // Apply configuration : + // create ports + + static_cast(gPortsOrch)->doTask(); + + // Apply configuration + // ports + static_cast(gPortsOrch)->doTask(); + + ASSERT_TRUE(gPortsOrch->allPortsReady()); + + // No more tasks + vector ts; + gPortsOrch->dumpPendingTasks(ts); + ASSERT_TRUE(ts.empty()); + ts.clear(); + + // Simulate storm drop handler started on Ethernet0 TC 3 + Port port; + gPortsOrch->getPort("Ethernet0", port); + + auto countersTable = make_shared
(m_counters_db.get(), COUNTERS_TABLE); + auto dropHandler = make_unique(port.m_port_id, port.m_queue_ids[3], 3, countersTable); + + // Create test buffer pool + poolTable.set( + "test_pool", + { + { "type", "ingress" }, + { "mode", "dynamic" }, + { "size", "4200000" }, + }); + + // Create test buffer profile + profileTable.set("test_profile", { { "pool", "[BUFFER_POOL|test_pool]" }, + { "xon", "14832" }, + { "xoff", "14832" }, + { "size", "35000" }, + { "dynamic_th", "0" } }); + + // Apply profile on PGs 3-4 all ports + for (const auto &it : ports) + { + std::ostringstream oss; + oss << it.first << "|3-4"; + pgTable.set(oss.str(), { { "profile", "[BUFFER_PROFILE|test_profile]" } }); + } + gBufferOrch->addExistingData(&pgTable); + gBufferOrch->addExistingData(&poolTable); + gBufferOrch->addExistingData(&profileTable); + + // process pool, profile and PGs + static_cast(gBufferOrch)->doTask(); + + auto pgConsumer = static_cast(gBufferOrch->getExecutor(CFG_BUFFER_PG_TABLE_NAME)); + pgConsumer->dumpPendingTasks(ts); + ASSERT_FALSE(ts.empty()); // PG is skipped + ts.clear(); + + // release zero buffer drop handler + dropHandler.reset(); + + // process PGs + static_cast(gBufferOrch)->doTask(); + + pgConsumer = static_cast(gBufferOrch->getExecutor(CFG_BUFFER_PG_TABLE_NAME)); + pgConsumer->dumpPendingTasks(ts); + ASSERT_TRUE(ts.empty()); // PG should be proceesed now + ts.clear(); + } } diff --git a/tests/mock_tests/ut_saihelper.cpp b/tests/mock_tests/ut_saihelper.cpp index 5d5d07c190..34b76e7e5a 100644 --- a/tests/mock_tests/ut_saihelper.cpp +++ b/tests/mock_tests/ut_saihelper.cpp @@ -77,6 +77,7 @@ namespace ut_helper sai_api_query(SAI_API_ACL, (void **)&sai_acl_api); sai_api_query(SAI_API_HOSTIF, (void **)&sai_hostif_api); sai_api_query(SAI_API_BUFFER, (void **)&sai_buffer_api); + sai_api_query(SAI_API_QUEUE, (void **)&sai_queue_api); return SAI_STATUS_SUCCESS; } @@ -99,6 +100,7 @@ namespace ut_helper sai_acl_api = nullptr; sai_hostif_api = nullptr; sai_buffer_api = nullptr; + sai_queue_api = nullptr; } map> getInitialSaiPorts() From 77fa5a41d004631dc6389756d2231da2182929e2 Mon Sep 17 00:00:00 2001 From: Kamil Cudnik Date: Sun, 22 Dec 2019 14:20:25 +0100 Subject: [PATCH 34/63] Move away sairedis logrotate from signal handler (#1153) * Move away sairedis logrotate from signal handler * Update tests --- orchagent/main.cpp | 11 ++--------- orchagent/orchdaemon.cpp | 14 ++++++++++++++ tests/mock_tests/mock_orchagent_main.cpp | 3 ++- tests/mock_tests/mock_orchagent_main.h | 1 + 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/orchagent/main.cpp b/orchagent/main.cpp index a5591b9d00..6d3d6d228e 100644 --- a/orchagent/main.cpp +++ b/orchagent/main.cpp @@ -47,6 +47,7 @@ int gBatchSize = DEFAULT_BATCH_SIZE; bool gSairedisRecord = true; bool gSwssRecord = true; bool gLogRotate = false; +bool gSaiRedisLogRotate = false; bool gSyncMode = false; ofstream gRecordOfs; @@ -73,15 +74,7 @@ void sighup_handler(int signo) * Don't do any logging since they are using mutexes. */ gLogRotate = true; - - sai_attribute_t attr; - attr.id = SAI_REDIS_SWITCH_ATTR_PERFORM_LOG_ROTATE; - attr.value.booldata = true; - - if (sai_switch_api != NULL) - { - sai_switch_api->set_switch_attribute(gSwitchId, &attr); - } + gSaiRedisLogRotate = true; } void syncd_apply_view() diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 756b8a35eb..338b803dce 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -19,6 +19,7 @@ using namespace swss; extern sai_switch_api_t* sai_switch_api; extern sai_object_id_t gSwitchId; +extern bool gSaiRedisLogRotate; extern void syncd_apply_view(); /* @@ -400,6 +401,19 @@ void OrchDaemon::flush() SWSS_LOG_ERROR("Failed to flush redis pipeline %d", status); exit(EXIT_FAILURE); } + + // check if logroate is requested + if (gSaiRedisLogRotate) + { + SWSS_LOG_NOTICE("performing log rotate"); + + gSaiRedisLogRotate = false; + + attr.id = SAI_REDIS_SWITCH_ATTR_PERFORM_LOG_ROTATE; + attr.value.booldata = true; + + sai_switch_api->set_switch_attribute(gSwitchId, &attr); + } } void OrchDaemon::start() diff --git a/tests/mock_tests/mock_orchagent_main.cpp b/tests/mock_tests/mock_orchagent_main.cpp index 60b5f5e00f..86651a7c94 100644 --- a/tests/mock_tests/mock_orchagent_main.cpp +++ b/tests/mock_tests/mock_orchagent_main.cpp @@ -18,10 +18,11 @@ int gBatchSize = DEFAULT_BATCH_SIZE; bool gSairedisRecord = true; bool gSwssRecord = true; bool gLogRotate = false; +bool gSaiRedisLogRotate = false; ofstream gRecordOfs; string gRecordFile; MirrorOrch *gMirrorOrch; VRFOrch *gVrfOrch; -void syncd_apply_view() {} \ No newline at end of file +void syncd_apply_view() {} diff --git a/tests/mock_tests/mock_orchagent_main.h b/tests/mock_tests/mock_orchagent_main.h index 9971463ce4..71bb522d77 100644 --- a/tests/mock_tests/mock_orchagent_main.h +++ b/tests/mock_tests/mock_orchagent_main.h @@ -19,6 +19,7 @@ extern int gBatchSize; extern bool gSwssRecord; extern bool gSairedisRecord; extern bool gLogRotate; +extern bool gSaiRedisLogRotate; extern ofstream gRecordOfs; extern string gRecordFile; From 80e01c01d38017cd21ec3bc9594b418d8e7fee5a Mon Sep 17 00:00:00 2001 From: judyjoseph <53951155+judyjoseph@users.noreply.github.com> Date: Wed, 15 Jan 2020 09:43:22 -0800 Subject: [PATCH 35/63] Teamd :: fix for cleaning up the teamd processes correctly on teamd docker stop (#1159) * Send explicit signal to the teamd processes whenthe teamd docker exits. When the teamd docker receives a stop signal, only the processes started by supervisord gets the SIGTERM, so this fix is to propogate the signal to teamd processes via the signal handler in teamsyncd process. * Updates to take care of boundary conditions in the teamsyncd signal handler. * Better way of signal Handling by setting a flag in the signal handler and checking for the flag in the main loop. This way the cleanUp handler is not run in the signal Handler context and can add more Logs as we need not care for signal safety now. * Updated the logic so that teammgrd controls the lifecycle of teamd. Teammgrd tracks the PID's of the teamd processes and sents the SIGTERM signal when the teamd docker is stopped. * Minor change in the function defenition * Updates based on the comments * Minor update in teammgr.cpp --- cfgmgr/teammgr.cpp | 79 +++++++++++++++++++++++++++++++++++++++++ cfgmgr/teammgr.h | 8 +++++ cfgmgr/teammgrd.cpp | 18 ++++++++++ teamsyncd/teamsync.cpp | 26 ++++++++++++++ teamsyncd/teamsync.h | 1 + teamsyncd/teamsyncd.cpp | 18 ++++++++++ 6 files changed, 150 insertions(+) diff --git a/cfgmgr/teammgr.cpp b/cfgmgr/teammgr.cpp index 0ff3ddfdf8..6121c30f0c 100644 --- a/cfgmgr/teammgr.cpp +++ b/cfgmgr/teammgr.cpp @@ -10,11 +10,15 @@ #include #include #include +#include #include #include #include #include +#include + +#define PID_FILE_PATH "/var/run/teamd/" using namespace std; using namespace swss; @@ -110,6 +114,79 @@ void TeamMgr::doTask(Consumer &consumer) } } + +pid_t TeamMgr::getTeamPid(const string &alias) +{ + SWSS_LOG_ENTER(); + pid_t pid = 0; + + string file = string(PID_FILE_PATH) + alias + string(".pid"); + ifstream infile(file); + if (!infile.is_open()) + { + SWSS_LOG_WARN("The LAG PID file: %s is not readable", file.c_str()); + return 0; + } + + string line; + getline(infile, line); + if (line.empty()) + { + SWSS_LOG_WARN("The LAG PID file: %s is empty", file.c_str()); + } + else + { + /*Store the PID value */ + pid = stoi(line, nullptr, 10); + } + + /* Close the file and return */ + infile.close(); + + return pid; +} + + +void TeamMgr::addLagPid(const string &alias) +{ + SWSS_LOG_ENTER(); + m_lagPIDList[alias] = getTeamPid(alias); +} + +void TeamMgr::removeLagPid(const string &alias) +{ + SWSS_LOG_ENTER(); + m_lagPIDList.erase(alias); +} + +void TeamMgr::cleanTeamProcesses(int signo) +{ + pid_t pid = 0; + + for (const auto& it: m_lagList) + { + pid = m_lagPIDList[it]; + if(!pid) { + SWSS_LOG_WARN("Invalid PID found for LaG %s ", it.c_str()); + + /* Try to get the PID again */ + pid = getTeamPid(it); + } + + if(pid > 0) + { + SWSS_LOG_INFO("Sending TERM Signal to (PID: %d) for LaG %s ", pid, it.c_str()); + kill(pid, signo); + } + else + { + SWSS_LOG_ERROR("Can't send TERM signal to LAG %s. PID wasn't found", it.c_str()); + } + } + + return; +} + void TeamMgr::doLagTask(Consumer &consumer) { SWSS_LOG_ENTER(); @@ -173,6 +250,7 @@ void TeamMgr::doLagTask(Consumer &consumer) } m_lagList.insert(alias); + addLagPid(alias); } setLagAdminStatus(alias, admin_status); @@ -189,6 +267,7 @@ void TeamMgr::doLagTask(Consumer &consumer) { removeLag(alias); m_lagList.erase(alias); + removeLagPid(alias); } } diff --git a/cfgmgr/teammgr.h b/cfgmgr/teammgr.h index db3b033844..9003d25d54 100644 --- a/cfgmgr/teammgr.h +++ b/cfgmgr/teammgr.h @@ -7,6 +7,7 @@ #include "netmsg.h" #include "orch.h" #include "producerstatetable.h" +#include namespace swss { @@ -17,6 +18,8 @@ class TeamMgr : public Orch const std::vector &tables); using Orch::doTask; + void cleanTeamProcesses(int signo); + private: Table m_cfgMetadataTable; // To retrieve MAC address Table m_cfgPortTable; @@ -29,6 +32,7 @@ class TeamMgr : public Orch ProducerStateTable m_appLagTable; std::set m_lagList; + std::map m_lagPIDList; MacAddress m_mac; @@ -45,6 +49,10 @@ class TeamMgr : public Orch bool setLagAdminStatus(const std::string &alias, const std::string &admin_status); bool setLagMtu(const std::string &alias, const std::string &mtu); bool setLagLearnMode(const std::string &alias, const std::string &learn_mode); + + pid_t getTeamPid(const std::string &alias); + void addLagPid(const std::string &alias); + void removeLagPid(const std::string &alias); bool isPortEnslaved(const std::string &); bool findPortMaster(std::string &, const std::string &); diff --git a/cfgmgr/teammgrd.cpp b/cfgmgr/teammgrd.cpp index 6217a0a21a..60828e038d 100644 --- a/cfgmgr/teammgrd.cpp +++ b/cfgmgr/teammgrd.cpp @@ -5,6 +5,7 @@ #include "netlink.h" #include "select.h" #include "warm_restart.h" +#include using namespace std; using namespace swss; @@ -17,6 +18,14 @@ bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool received_sigterm = false; + +void sig_handler(int signo) +{ + received_sigterm = true; + return; +} + int main(int argc, char **argv) { Logger::linkToDbNative("teammgrd"); @@ -24,6 +33,9 @@ int main(int argc, char **argv) SWSS_LOG_NOTICE("--- Starting teammrgd ---"); + /* Register the signal handler for SIGTERM */ + signal(SIGTERM, sig_handler); + try { DBConnector conf_db("CONFIG_DB", 0); @@ -55,6 +67,12 @@ int main(int argc, char **argv) while (true) { + if(received_sigterm) + { + teammgr.cleanTeamProcesses(SIGTERM); + received_sigterm = false; + } + Selectable *sel; int ret; diff --git a/teamsyncd/teamsync.cpp b/teamsyncd/teamsync.cpp index 64016ff557..2719189f8e 100644 --- a/teamsyncd/teamsync.cpp +++ b/teamsyncd/teamsync.cpp @@ -109,6 +109,22 @@ void TeamSync::onMsg(int nlmsg_type, struct nl_object *obj) if (!type || (strcmp(type, TEAM_DRV_NAME) != 0)) return; + unsigned int flags = rtnl_link_get_flags(link); + bool admin = flags & IFF_UP; + bool oper = flags & IFF_LOWER_UP; + unsigned int ifindex = rtnl_link_get_ifindex(link); + + if (type) + { + SWSS_LOG_INFO(" nlmsg type:%d key:%s admin:%d oper:%d ifindex:%d type:%s", + nlmsg_type, lagName.c_str(), admin, oper, ifindex, type); + } + else + { + SWSS_LOG_INFO(" nlmsg type:%d key:%s admin:%d oper:%d ifindex:%d", + nlmsg_type, lagName.c_str(), admin, oper, ifindex); + } + if (nlmsg_type == RTM_DELLINK) { if (m_teamSelectables.find(lagName) != m_teamSelectables.end()) @@ -194,6 +210,16 @@ void TeamSync::removeLag(const string &lagName) m_selectablesToRemove.insert(lagName); } +void TeamSync::cleanTeamSync() +{ + for (const auto& it: m_teamSelectables) + { + /* Cleanup LAG */ + removeLag(it.first); + } + return; +} + const struct team_change_handler TeamSync::TeamPortSync::gPortChangeHandler = { .func = TeamSync::TeamPortSync::teamdHandler, .type_mask = TEAM_PORT_CHANGE | TEAM_OPTION_CHANGE diff --git a/teamsyncd/teamsync.h b/teamsyncd/teamsync.h index f47049d1a6..406953e312 100644 --- a/teamsyncd/teamsync.h +++ b/teamsyncd/teamsync.h @@ -25,6 +25,7 @@ class TeamSync : public NetMsg TeamSync(DBConnector *db, DBConnector *stateDb, Select *select); void periodic(); + void cleanTeamSync(); /* Listen to RTM_NEWLINK, RTM_DELLINK to track team devices */ virtual void onMsg(int nlmsg_type, struct nl_object *obj); diff --git a/teamsyncd/teamsyncd.cpp b/teamsyncd/teamsyncd.cpp index ef368ca691..92f59553a9 100644 --- a/teamsyncd/teamsyncd.cpp +++ b/teamsyncd/teamsyncd.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "logger.h" #include "select.h" #include "netdispatcher.h" @@ -9,6 +10,14 @@ using namespace std; using namespace swss; +bool received_sigterm = false; + +void sig_handler(int signo) +{ + received_sigterm = true; + return; +} + int main(int argc, char **argv) { swss::Logger::linkToDbNative(TEAMSYNCD_APP_NAME); @@ -20,6 +29,9 @@ int main(int argc, char **argv) NetDispatcher::getInstance().registerMessageHandler(RTM_NEWLINK, &sync); NetDispatcher::getInstance().registerMessageHandler(RTM_DELLINK, &sync); + /* Register the signal handler for SIGTERM */ + signal(SIGTERM, sig_handler); + while (1) { try @@ -33,6 +45,12 @@ int main(int argc, char **argv) s.addSelectable(&netlink); while (true) { + if(received_sigterm) + { + sync.cleanTeamSync(); + received_sigterm = false; + } + Selectable *temps; s.select(&temps, 1000); // block for a second sync.periodic(); From 1eac91e1ef22b73ab057dc970fa44d6b4ef45c55 Mon Sep 17 00:00:00 2001 From: Stepan Blyshchak <38952541+stepanblyschak@users.noreply.github.com> Date: Wed, 15 Jan 2020 22:10:45 +0200 Subject: [PATCH 36/63] [portsorch] process PortsOrch tables in specific order (#1108) * [portsorch] process PortsOrch tables in specific order * [portsorch] fix other tables not drained and make tableOrder constexpr * [portsorch_ut] fix test according to fix in portsorch Don't care if PORT_TABLE was not processed fully, we care about LAG, LAG_TABLE, VLAN and VLAN_TABLE. * [tests] fix ut after unsuccessful conflict resolution --- orchagent/orchdaemon.cpp | 23 +++-- orchagent/portsorch.cpp | 28 ++++++ orchagent/portsorch.h | 2 +- tests/mock_tests/portsorch_ut.cpp | 142 +++++++++++++++++++++++++++++- 4 files changed, 180 insertions(+), 15 deletions(-) diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 338b803dce..d71bafab85 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -212,11 +212,11 @@ bool OrchDaemon::init() * The order of the orch list is important for state restore of warm start and * the queued processing in m_toSync map after gPortsOrch->allPortsReady() is set. * - * For the multiple consumers in ports_tables, tasks for LAG_TABLE is processed before VLAN_TABLE - * when iterating ConsumerMap. - * That is ensured implicitly by the order of map key, "LAG_TABLE" is smaller than "VLAN_TABLE" in lexicographic order. + * For the multiple consumers in Orchs, tasks in a table which name is smaller in lexicographic order are processed first + * when iterating ConsumerMap. This is ensured implicitly by the order of keys in ordered map. + * For cases when Orch has to process tables in specific order, like PortsOrch during warm start, it has to override Orch::doTask() */ - m_orchList = { gSwitchOrch, gCrmOrch, gBufferOrch, gPortsOrch, gIntfsOrch, gNeighOrch, gRouteOrch, copp_orch, tunnel_decap_orch, qos_orch, wm_orch, policer_orch, sflow_orch, debug_counter_orch}; + m_orchList = { gSwitchOrch, gCrmOrch, gPortsOrch, gBufferOrch, gIntfsOrch, gNeighOrch, gRouteOrch, copp_orch, tunnel_decap_orch, qos_orch, wm_orch, policer_orch, sflow_orch, debug_counter_orch}; bool initialize_dtel = false; if (platform == BFN_PLATFORM_SUBSTRING || platform == VS_PLATFORM_SUBSTRING) @@ -510,20 +510,17 @@ bool OrchDaemon::warmRestoreAndSyncUp() } /* - * Four iterations are needed. + * Three iterations are needed. * - * First iteration: switchorch, Port init/hostif create part of portorch. + * First iteration: switchorch, Port init/hostif create part of portorch, buffers configuration * - * Second iteratoin: gBufferOrch which requires port created, - * then port speed/mtu/fec_mode/pfc_asym/admin_status config. + * Second iteratoin: port speed/mtu/fec_mode/pfc_asym/admin_status config, + * other orch(s) which wait for port to become ready. * - * Third iteration: other orch(s) which wait for port init done. - * - * Fourth iteration: Drain remaining data that are out of order like LAG_MEMBER_TABLE and - * VLAN_MEMBER_TABLE since they were checked before LAG_TABLE and VLAN_TABLE within gPortsOrch. + * Third iteration: Drain remaining data that are out of order. */ - for (auto it = 0; it < 4; it++) + for (auto it = 0; it < 3; it++) { SWSS_LOG_DEBUG("The current iteration is %d", it); diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index f5d616323c..ab9c37b371 100644 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -2556,6 +2556,34 @@ void PortsOrch::doLagMemberTask(Consumer &consumer) } } +void PortsOrch::doTask() +{ + constexpr auto tableOrder = { + APP_PORT_TABLE_NAME, + APP_LAG_TABLE_NAME, + APP_LAG_MEMBER_TABLE_NAME, + APP_VLAN_TABLE_NAME, + APP_VLAN_MEMBER_TABLE_NAME, + }; + + for (auto tableName: tableOrder) + { + auto consumer = getExecutor(tableName); + consumer->drain(); + } + + // drain remaining tables + for (auto& it: m_consumerMap) + { + auto tableName = it.first; + auto consumer = it.second.get(); + if (find(tableOrder.begin(), tableOrder.end(), tableName) == tableOrder.end()) + { + consumer->drain(); + } + } +} + void PortsOrch::doTask(Consumer &consumer) { SWSS_LOG_ENTER(); diff --git a/orchagent/portsorch.h b/orchagent/portsorch.h index 6f314d6bff..a1a99a9406 100644 --- a/orchagent/portsorch.h +++ b/orchagent/portsorch.h @@ -135,9 +135,9 @@ class PortsOrch : public Orch, public Subject map m_port_ref_count; unordered_set m_pendingPortSet; - NotificationConsumer* m_portStatusNotificationConsumer; + void doTask() override; void doTask(Consumer &consumer); void doPortTask(Consumer &consumer); void doVlanTask(Consumer &consumer); diff --git a/tests/mock_tests/portsorch_ut.cpp b/tests/mock_tests/portsorch_ut.cpp index 0bd88bc0fa..0803c0300f 100644 --- a/tests/mock_tests/portsorch_ut.cpp +++ b/tests/mock_tests/portsorch_ut.cpp @@ -383,7 +383,7 @@ namespace portsorch_test Port port; gPortsOrch->getPort("Ethernet0", port); - auto countersTable = make_shared
(m_counters_db.get(), COUNTERS_TABLE); + auto countersTable = make_shared
(m_counters_db.get(), COUNTERS_TABLE); auto dropHandler = make_unique(port.m_port_id, port.m_queue_ids[3], 3, countersTable); // Create test buffer pool @@ -432,4 +432,144 @@ namespace portsorch_test ASSERT_TRUE(ts.empty()); // PG should be proceesed now ts.clear(); } + + /* + * The scope of this test is to verify that LAG member is + * added to a LAG before any other object on LAG is created, like RIF, bridge port in warm mode. + * For objects like RIF which are created by a different Orch we know that they will wait until + * allPortsReady(), so we can guaranty they won't be created if PortsOrch can process ports, lags, + * vlans in single doTask(). + * If objects are created in PortsOrch, like bridge port, we will spy on SAI API to verify they are + * not called before create_lag_member. + * This is done like this because of limitation on Mellanox platform that does not allow to create objects + * on LAG before at least one LAG members is added in warm reboot. Later this will be fixed. + * + */ + TEST_F(PortsOrchTest, LagMemberIsCreatedBeforeOtherObjectsAreCreatedOnLag) + { + + Table portTable = Table(m_app_db.get(), APP_PORT_TABLE_NAME); + Table lagTable = Table(m_app_db.get(), APP_LAG_TABLE_NAME); + Table lagMemberTable = Table(m_app_db.get(), APP_LAG_MEMBER_TABLE_NAME); + Table vlanTable = Table(m_app_db.get(), APP_VLAN_TABLE_NAME); + Table vlanMemberTable = Table(m_app_db.get(), APP_VLAN_MEMBER_TABLE_NAME); + + // Get SAI default ports to populate DB + auto ports = ut_helper::getInitialSaiPorts(); + + // Create dependencies ... + const int portsorch_base_pri = 40; + + vector ports_tables = { + { APP_PORT_TABLE_NAME, portsorch_base_pri + 5 }, + { APP_VLAN_TABLE_NAME, portsorch_base_pri + 2 }, + { APP_VLAN_MEMBER_TABLE_NAME, portsorch_base_pri }, + { APP_LAG_TABLE_NAME, portsorch_base_pri + 4 }, + { APP_LAG_MEMBER_TABLE_NAME, portsorch_base_pri } + }; + + ASSERT_EQ(gPortsOrch, nullptr); + gPortsOrch = new PortsOrch(m_app_db.get(), ports_tables); + vector buffer_tables = { CFG_BUFFER_POOL_TABLE_NAME, + CFG_BUFFER_PROFILE_TABLE_NAME, + CFG_BUFFER_QUEUE_TABLE_NAME, + CFG_BUFFER_PG_TABLE_NAME, + CFG_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME, + CFG_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME }; + + ASSERT_EQ(gBufferOrch, nullptr); + gBufferOrch = new BufferOrch(m_config_db.get(), buffer_tables); + + /* + * Next we will prepare some configuration data to be consumed by PortsOrch + * 32 Ports, 1 LAG, 1 port is LAG member and LAG is in Vlan. + */ + + // Populate pot table with SAI ports + for (const auto &it : ports) + { + portTable.set(it.first, it.second); + } + + // Set PortConfigDone + portTable.set("PortConfigDone", { { "count", to_string(ports.size()) } }); + portTable.set("PortInitDone", { { } }); + + lagTable.set("PortChannel0001", + { + {"admin_status", "up"}, + {"mtu", "9100"} + } + ); + lagMemberTable.set( + std::string("PortChannel0001") + lagMemberTable.getTableNameSeparator() + ports.begin()->first, + { {"status", "enabled"} }); + vlanTable.set("Vlan5", + { + {"admin_status", "up"}, + {"mtu", "9100"} + } + ); + vlanMemberTable.set( + std::string("Vlan5") + vlanMemberTable.getTableNameSeparator() + std::string("PortChannel0001"), + { {"tagging_mode", "untagged"} } + ); + + // refill consumer + gPortsOrch->addExistingData(&portTable); + gPortsOrch->addExistingData(&lagTable); + gPortsOrch->addExistingData(&lagMemberTable); + gPortsOrch->addExistingData(&vlanTable); + gPortsOrch->addExistingData(&vlanMemberTable); + + // save original api since we will spy + auto orig_lag_api = sai_lag_api; + sai_lag_api = new sai_lag_api_t(); + memcpy(sai_lag_api, orig_lag_api, sizeof(*sai_lag_api)); + + auto orig_bridge_api = sai_bridge_api; + sai_bridge_api = new sai_bridge_api_t(); + memcpy(sai_bridge_api, orig_bridge_api, sizeof(*sai_bridge_api)); + + bool bridgePortCalled = false; + bool bridgePortCalledBeforeLagMember = false; + + auto lagSpy = SpyOn(&sai_lag_api->create_lag_member); + lagSpy->callFake([&](sai_object_id_t *oid, sai_object_id_t swoid, uint32_t count, const sai_attribute_t * attrs) -> sai_status_t { + if (bridgePortCalled) { + bridgePortCalledBeforeLagMember = true; + } + return orig_lag_api->create_lag_member(oid, swoid, count, attrs); + } + ); + + auto bridgeSpy = SpyOn(&sai_bridge_api->create_bridge_port); + bridgeSpy->callFake([&](sai_object_id_t *oid, sai_object_id_t swoid, uint32_t count, const sai_attribute_t * attrs) -> sai_status_t { + bridgePortCalled = true; + return orig_bridge_api->create_bridge_port(oid, swoid, count, attrs); + } + ); + + static_cast(gPortsOrch)->doTask(); + + vector ts; + + // check LAG, VLAN tasks were proceesed + // port table may require one more doTask iteration + for (auto tableName: { + APP_LAG_TABLE_NAME, + APP_LAG_MEMBER_TABLE_NAME, + APP_VLAN_TABLE_NAME, + APP_VLAN_MEMBER_TABLE_NAME}) + { + auto exec = gPortsOrch->getExecutor(tableName); + auto consumer = static_cast(exec); + ts.clear(); + consumer->dumpPendingTasks(ts); + ASSERT_TRUE(ts.empty()); + } + + ASSERT_FALSE(bridgePortCalledBeforeLagMember); // bridge port created on lag before lag member was created + } + } From 86aceac3b66bfdcbbc67700c6cae46d80c5d4d23 Mon Sep 17 00:00:00 2001 From: Guohan Lu Date: Thu, 30 Jan 2020 22:57:41 +0000 Subject: [PATCH 37/63] Natmgrd changes in sonic-swss sub module to support NAT feature. #1059 Signed-off-by: Akhilesh Samineni akhilesh.samineni@broadcom.com --- .gitignore | 1 + cfgmgr/Makefile.am | 7 +- cfgmgr/intfmgr.cpp | 15 + cfgmgr/natmgr.cpp | 7404 ++++++++++++++++++++++++++++++++++++++++++++ cfgmgr/natmgr.h | 350 +++ cfgmgr/natmgrd.cpp | 173 ++ cfgmgr/shellcmd.h | 2 + 7 files changed, 7951 insertions(+), 1 deletion(-) create mode 100644 cfgmgr/natmgr.cpp create mode 100644 cfgmgr/natmgr.h create mode 100644 cfgmgr/natmgrd.cpp diff --git a/.gitignore b/.gitignore index 44f8eeab5c..8a8f46062b 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,7 @@ cfgmgr/vlanmgrd cfgmgr/vrfmgrd cfgmgr/nbrmgrd cfgmgr/vxlanmgrd +cfgmgr/natmgrd neighsyncd/neighsyncd portsyncd/portsyncd orchagent/orchagent diff --git a/cfgmgr/Makefile.am b/cfgmgr/Makefile.am index c1e3f06996..5a783ce5ff 100644 --- a/cfgmgr/Makefile.am +++ b/cfgmgr/Makefile.am @@ -3,7 +3,7 @@ CFLAGS_SAI = -I /usr/include/sai LIBNL_CFLAGS = -I/usr/include/libnl3 LIBNL_LIBS = -lnl-genl-3 -lnl-route-3 -lnl-3 -bin_PROGRAMS = vlanmgrd teammgrd portmgrd intfmgrd buffermgrd vrfmgrd nbrmgrd vxlanmgrd sflowmgrd +bin_PROGRAMS = vlanmgrd teammgrd portmgrd intfmgrd buffermgrd vrfmgrd nbrmgrd vxlanmgrd sflowmgrd natmgrd if DEBUG DBGFLAGS = -ggdb -DDEBUG @@ -55,3 +55,8 @@ sflowmgrd_SOURCES = sflowmgrd.cpp sflowmgr.cpp $(top_srcdir)/orchagent/orch.cpp sflowmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) sflowmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) sflowmgrd_LDADD = -lswsscommon + +natmgrd_SOURCES = natmgrd.cpp natmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +natmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) +natmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) +natmgrd_LDADD = -lswsscommon diff --git a/cfgmgr/intfmgr.cpp b/cfgmgr/intfmgr.cpp index 3d1915c1b8..a897216f4e 100644 --- a/cfgmgr/intfmgr.cpp +++ b/cfgmgr/intfmgr.cpp @@ -390,6 +390,7 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, string vrf_name = ""; string mtu = ""; string adminStatus = ""; + string nat_zone = ""; for (auto idx : data) { const auto &field = fvField(idx); @@ -404,6 +405,11 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, { adminStatus = value; } + + if (field == "nat_zone") + { + nat_zone = value; + } } if (op == SET_COMMAND) @@ -431,6 +437,15 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, { addLoopbackIntf(alias); } + else + { + /* Set nat zone */ + if (!nat_zone.empty()) + { + FieldValueTuple fvTuple("nat_zone", nat_zone); + data.push_back(fvTuple); + } + } if (!vrf_name.empty()) { diff --git a/cfgmgr/natmgr.cpp b/cfgmgr/natmgr.cpp new file mode 100644 index 0000000000..5df7a75084 --- /dev/null +++ b/cfgmgr/natmgr.cpp @@ -0,0 +1,7404 @@ +/* + * Copyright 2019 Broadcom Inc. + * + * 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. + */ + +#include +#include "logger.h" +#include "producerstatetable.h" +#include "macaddress.h" +#include "natmgr.h" +#include "exec.h" +#include "tokenize.h" +#include "converter.h" +#include "shellcmd.h" +#include "warm_restart.h" +#include "ipaddress.h" +#include "ipprefix.h" + +using namespace std; +using namespace swss; + +/* NatMgr Constructor */ +NatMgr::NatMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, const vector &tableNames) : + Orch(cfgDb, tableNames), + m_statePortTable(stateDb, STATE_PORT_TABLE_NAME), + m_stateLagTable(stateDb, STATE_LAG_TABLE_NAME), + m_stateVlanTable(stateDb, STATE_VLAN_TABLE_NAME), + m_stateInterfaceTable(stateDb, STATE_INTERFACE_TABLE_NAME), + m_appNatTableProducer(appDb, APP_NAT_TABLE_NAME), + m_appNaptTableProducer(appDb, APP_NAPT_TABLE_NAME), + m_appTwiceNatTableProducer(appDb, APP_NAT_TWICE_TABLE_NAME), + m_appTwiceNaptTableProducer(appDb, APP_NAPT_TWICE_TABLE_NAME), + m_appNatGlobalTableProducer(appDb, APP_NAT_GLOBAL_TABLE_NAME), + m_appNaptPoolIpTable(appDb, APP_NAPT_POOL_IP_TABLE_NAME) +{ + SWSS_LOG_ENTER(); + + SWSS_LOG_INFO("NatMgr Constructor ..!"); + + /* Set the Admin mode to disabled */ + natAdminMode = DISABLED; + + /* Set NAT default timeout as 600 seconds */ + m_natTimeout = NAT_TIMEOUT_DEFAULT; + + /* Set NAT default tcp timeout as 86400 seconds (1 Day) */ + m_natTcpTimeout = NAT_TCP_TIMEOUT_DEFAULT; + + /* Set NAT default udp timeout as 300 seconds */ + m_natUdpTimeout = NAT_UDP_TIMEOUT_DEFAULT; + + /* Clean the NAT iptables */ + std::string res; + const std::string cmds = std::string("") + IPTABLES_CMD + " -F -t nat "; + if (swss::exec(cmds, res)) + { + SWSS_LOG_ERROR("Command '%s' failed", cmds.c_str()); + } + + flushNotifier = std::make_shared(appDb, "FLUSHNATREQUEST"); +} + +/* To check the port init id done or not */ +bool NatMgr::isPortInitDone(DBConnector *app_db) +{ + bool portInit = 0; + long cnt = 0; + + while(!portInit) { + Table portTable(app_db, APP_PORT_TABLE_NAME); + std::vector tuples; + portInit = portTable.get("PortInitDone", tuples); + + if(portInit) + break; + sleep(1); + cnt++; + } + SWSS_LOG_NOTICE("PORT_INIT_DONE : %d %ld", portInit, cnt); + return portInit; +} + +/* To check the given port is State Ok or not */ +bool NatMgr::isPortStateOk(const string &port) +{ + vector temp; + + if (!port.compare(0, strlen(VLAN_PREFIX), VLAN_PREFIX)) + { + if (m_stateVlanTable.get(port, temp)) + { + SWSS_LOG_INFO("Vlan %s is ready", port.c_str()); + return true; + } + SWSS_LOG_INFO("Vlan %s is not yet ready", port.c_str()); + } + else if (!port.compare(0, strlen(LAG_PREFIX), LAG_PREFIX)) + { + if (m_stateLagTable.get(port, temp)) + { + SWSS_LOG_INFO("Lag %s is ready", port.c_str()); + return true; + } + SWSS_LOG_INFO("Lag %s is not yet ready", port.c_str()); + } + else if (!port.compare(0, strlen(ETHERNET_PREFIX), ETHERNET_PREFIX)) + { + if (m_statePortTable.get(port, temp)) + { + SWSS_LOG_INFO("Port %s is ready", port.c_str()); + return true; + } + SWSS_LOG_INFO("Port %s is not yet ready", port.c_str()); + } + else + { + SWSS_LOG_ERROR("Invalid Port %s ", port.c_str()); + } + return false; +} + +/* To check the give interface is State Ok or not */ +bool NatMgr::isIntfStateOk(const string &interface) +{ + vector temp; + + if (m_stateInterfaceTable.get(interface, temp)) + { + SWSS_LOG_INFO("Interface %s is ready", interface.c_str()); + return true; + } + + SWSS_LOG_INFO("Interface %s is not yet ready", interface.c_str()); + return false; +} + +/* To check the nat fetaure is enabled or not */ +bool NatMgr::isNatEnabled(void) +{ + if (natAdminMode == ENABLED) + { + return true; + } + + return false; +} + +/* To check the give global_ip is withing the Prefix subnet or not */ +bool NatMgr::isGlobalIpMatching(const string &prefix, const string &global_ip) +{ + IpAddress externalAddr(global_ip); + IpPrefix ip_prefix(prefix); + + auto ea = externalAddr.getIp(); + auto ia = ip_prefix.getIp(); + auto ia2 = ia.getIp(); + auto ma = ip_prefix.getMask(); + auto ma2 = ma.getIp(); + + /* Check global ip is within the given subnet */ + if ((ia2.ip_addr.ipv4_addr & ma2.ip_addr.ipv4_addr) == (ea.ip_addr.ipv4_addr & ma2.ip_addr.ipv4_addr)) + { + return true; + } + + return false; +} + +/* To check the given pool_name is mapped to any binding or not */ +bool NatMgr::isPoolMappedtoBinding(const string &pool_name, string &binding_name) +{ + /* Get all binding Info */ + for (auto it = m_natBindingInfo.begin(); it != m_natBindingInfo.end(); it++) + { + /* Check is it matches with given pool name */ + if (pool_name == (*it).second.pool_name) + { + /* Pool is mapped to Binding, return now */ + binding_name = (*it).first; + return true; + } + } + + return false; +} + +/* To check the given Static NAT entry is matched with any Static NAPT entry */ +bool NatMgr::isMatchesWithStaticNapt(const string &global_ip, string &local_ip) +{ + /* Get all Static NAPT entries */ + for (auto it = m_staticNaptEntry.begin(); it != m_staticNaptEntry.end(); it++) + { + vector keys = tokenize((*it).first, config_db_key_delimiter); + + /* Ensure that interface is having some port, otherwise Entry is not yet added */ + if ((keys[0] == global_ip) && ((*it).second.local_ip == local_ip) && + ((*it).second.interface != NONE_STRING)) + { + /* Matches with the Static NAPT entry */ + return true; + } + } + return false; +} + +/* To check the given Static NAPT entry is matched with any Static NAT entry */ +bool NatMgr::isMatchesWithStaticNat(const string &global_ip, string &local_ip) +{ + /* Get all Static NAT entries */ + for (auto it = m_staticNatEntry.begin(); it != m_staticNatEntry.end(); it++) + { + /* Ensure that interface is having some port, otherwise Entry is not yet added */ + if (((*it).first == global_ip) && ((*it).second.local_ip == local_ip) && + ((*it).second.interface != NONE_STRING)) + { + /* Matches with the Static NAT entry */ + return true; + } + } + return false; +} + +/* To get the configured interface for the given global ip address */ +bool NatMgr::getIpEnabledIntf(const string &global_ip, string &interface) +{ + /* Get all Ports from Ip Interface Info */ + for (auto it = m_natIpInterfaceInfo.begin(); it != m_natIpInterfaceInfo.end(); it++) + { + /* Get all IpAddress from Ip Interface Values from the key (Port) */ + for (auto ipPrefix = m_natIpInterfaceInfo[(*it).first].begin(); ipPrefix != m_natIpInterfaceInfo[(*it).first].end(); ipPrefix++) + { + /* Check the global ip address is in subnet */ + if (isGlobalIpMatching(*ipPrefix, global_ip) == true) + { + /* Matched with this interface, return now */ + interface = (*it).first; + return true; + } + } + } + + return false; +} + +/* This is ideally called on docker stop */ +void NatMgr::cleanupPoolIpTable(void) +{ + SWSS_LOG_INFO("Cleaning the NAPT Pool IP table from APP_DB"); + for (auto it = m_natPoolInfo.begin(); it != m_natPoolInfo.end(); it++) + { + /* Delete pool ip from APPL_DB */ + setNaptPoolIpTable(DELETE, ((*it).second).ip_range, ((*it).second).port_range); + } +} + +/* This is ideally called on docker stop */ +void NatMgr::cleanupMangleIpTables(void) +{ + SWSS_LOG_INFO("Cleaning the Mangle IpTables"); + for (auto it = m_natZoneInterfaceInfo.begin(); it != m_natZoneInterfaceInfo.end(); it++) + { + /* Delete the mangle iptables rules for non-loopback interface */ + if (strncmp((*it).first.c_str(), LOOPBACK_PREFIX, strlen(LOOPBACK_PREFIX))) + { + setMangleIptablesRules(DELETE, (*it).first, (*it).second); + } + } +} + +/* To Add/Delete NAPT pool ip table to APPL_DB */ +void NatMgr::setNaptPoolIpTable(const string &opCmd, const string &ip_range, const string &port_range) +{ + uint32_t ipv4_addr_low, ipv4_addr_high, ip, setIp; + char ipAddr[INET_ADDRSTRLEN]; + std::vector values; + + if (!port_range.empty() and (port_range != "NULL")) + { + swss::FieldValueTuple p("port_range", port_range); + values.push_back(p); + vector nat_ip = tokenize(ip_range, range_specifier); + + /* Check the pool is valid */ + if (nat_ip.empty()) + { + SWSS_LOG_INFO("NAT pool is not valid"); + return; + } + else if (nat_ip.size() == 2) + { + inet_pton(AF_INET, nat_ip[1].c_str(), &ipv4_addr_high); + ipv4_addr_high = ntohl(ipv4_addr_high); + inet_pton(AF_INET, nat_ip[0].c_str(), &ipv4_addr_low); + ipv4_addr_low = ntohl(ipv4_addr_low); + } + else + { + inet_pton(AF_INET, nat_ip[0].c_str(), &ipv4_addr_low); + ipv4_addr_high = ntohl(ipv4_addr_low); + ipv4_addr_low = ntohl(ipv4_addr_low); + } + + for (ip = ipv4_addr_low; ip <= ipv4_addr_high; ip++) + { + setIp = htonl(ip); + inet_ntop(AF_INET, &setIp, ipAddr, INET_ADDRSTRLEN); + if (opCmd == ADD) + { + m_appNaptPoolIpTable.set(ipAddr, values); + } + else + { + m_appNaptPoolIpTable.del(ipAddr); + } + } + } +} + +/* To Add a dummy conntrack entry for the Static Single NAT entry in the kernel */ +void NatMgr::addConntrackSingleNatEntry(const string &key) +{ + std::string res, cmds = std::string("") + CONNTRACK_CMD; + + if (m_staticNatEntry[key].nat_type == DNAT_NAT_TYPE) + { + SWSS_LOG_INFO("Add static NAT conntrack entry with src-ip %s, timeout %d", + m_staticNatEntry[key].local_ip.c_str(), m_natTimeout); + + cmds += (" -I -n " + key + ":1 -g 127.0.0.1:127" + " -p udp -t " + to_string(m_natTimeout) + + " --src " + m_staticNatEntry[key].local_ip + " --sport 1 --dst 127.0.0.1 --dport 127 -u ASSURED "); + } + else if (m_staticNatEntry[key].nat_type == SNAT_NAT_TYPE) + { + SWSS_LOG_INFO("Add static NAT conntrack entry with src-ip %s, timeout %d", + key.c_str(), m_natTimeout); + + cmds += (" -I -n " + m_staticNatEntry[key].local_ip + ":1 -g 127.0.0.1:127" + " -p udp -t " + to_string(m_natTimeout) + + " --src " + key + " --sport 1 --dst 127.0.0.1 --dport 127 -u ASSURED "); + } + + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Added the static NAT conntrack entry"); + } +} + +/* To Add a dummy conntrack entry for the Static Twice NAT entry in the kernel */ +void NatMgr::addConntrackTwiceNatEntry(const string &snatKey, const string &dnatKey) +{ + std::string res, cmds = std::string("") + CONNTRACK_CMD; + + SWSS_LOG_INFO("Add static Twice NAT conntrack entry with src-ip %s, dst-ip %s, timeout %u", + snatKey.c_str(), dnatKey.c_str(), m_natTimeout); + + cmds += (" -I -n " + m_staticNatEntry[snatKey].local_ip + ":1" + " -g " + m_staticNatEntry[dnatKey].local_ip + ":1" + + " -p udp" + " -t " + to_string(m_natTimeout) + " --src " + snatKey + " --sport 1" + " --dst " + dnatKey + + " --dport 1" + " -u ASSURED "); + + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Added the static Twice NAT conntrack entry"); + } +} + +/* To Add a dummy conntrack entry for the Static NAPT entry in the kernel, + * so that the port number is reserved and the same port is not allocated by the stack for any other dynamic entry */ +void NatMgr::addConntrackSingleNaptEntry(const string &key) +{ + int timeout = 0; + std::string res, prototype, state, cmds = std::string("") + CONNTRACK_CMD; + vector keys = tokenize(key, config_db_key_delimiter); + + if (keys[1] == to_upper(IP_PROTOCOL_UDP)) + { + prototype = IP_PROTOCOL_UDP; + timeout = m_natUdpTimeout; + state = ""; + } + else if (keys[1] == to_upper(IP_PROTOCOL_TCP)) + { + prototype = IP_PROTOCOL_TCP; + timeout = m_natTcpTimeout; + state = " --state ESTABLISHED "; + } + + if (m_staticNaptEntry[key].nat_type == DNAT_NAT_TYPE) + { + + SWSS_LOG_INFO("Add static NAPT conntrack entry with protocol %s, src-ip %s, src-port %s, timeout %d", + prototype.c_str(), m_staticNaptEntry[key].local_ip.c_str(), m_staticNaptEntry[key].local_port.c_str(), timeout); + + cmds += (" -I -n " + keys[0] + ":" + keys[2] + " -g 127.0.0.1:127" + " -p " + prototype + " -t " + to_string(timeout) + + " --src " + m_staticNaptEntry[key].local_ip + " --sport " + m_staticNaptEntry[key].local_port + " --dst 127.0.0.1 --dport 127 -u ASSURED " + state); + } + else if (m_staticNaptEntry[key].nat_type == SNAT_NAT_TYPE) + { + SWSS_LOG_INFO("Add static NAPT conntrack entry with protocol %s, src-ip %s, src-port %s, timeout %d", + prototype.c_str(), keys[0].c_str(), keys[2].c_str(), timeout); + + cmds += (" -I -n " + m_staticNaptEntry[key].local_ip + ":" + m_staticNaptEntry[key].local_port + " -g 127.0.0.1:127" + " -p " + prototype + " -t " + to_string(timeout) + + " --src " + keys[0] + " --sport " + keys[2] + " --dst 127.0.0.1 --dport 127 -u ASSURED " + state); + } + + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Added the static NAPT conntrack entry"); + } +} + +/* To Add a dummy conntrack entry for the Static Twice NAPT entry in the kernel */ +void NatMgr::addConntrackTwiceNaptEntry(const string &snatKey, const string &dnatKey) +{ + int timeout = 0; + std::string res, prototype, state, cmds = std::string("") + CONNTRACK_CMD; + vector snatKeys = tokenize(snatKey, config_db_key_delimiter); + vector dnatKeys = tokenize(dnatKey, config_db_key_delimiter); + + if (snatKeys[1] == to_upper(IP_PROTOCOL_UDP)) + { + prototype = IP_PROTOCOL_UDP; + timeout = m_natUdpTimeout; + state = ""; + } + else if (snatKeys[1] == to_upper(IP_PROTOCOL_TCP)) + { + prototype = IP_PROTOCOL_TCP; + timeout = m_natTcpTimeout; + state = " --state ESTABLISHED "; + } + + SWSS_LOG_DEBUG("Add static Twice NAPT conntrack entry with protocol %s, src-ip %s, src-port %s, dst-ip %s, dst-port %s, timeout %u", + prototype.c_str(), snatKeys[0].c_str(), snatKeys[2].c_str(), dnatKeys[0].c_str(), dnatKeys[2].c_str(), timeout); + + cmds += (" -I -n " + m_staticNaptEntry[snatKey].local_ip + ":" + m_staticNaptEntry[snatKey].local_port + " -g " + m_staticNaptEntry[dnatKey].local_ip + ":" + + m_staticNaptEntry[dnatKey].local_port + " -p " + prototype + " -t " + to_string(timeout) + + " --src " + snatKeys[0] + " --sport " + snatKeys[2] + " --dst " + dnatKeys[0] + " --dport " + dnatKeys[2] + " -u ASSURED " + state); + + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Added the static Twice NAPT conntrack entry"); + } +} + +/* To Delete conntrack entry for Static Single NAT entry */ +void NatMgr::deleteConntrackSingleNatEntry(const string &key) +{ + std::string res, cmds = std::string("") + CONNTRACK_CMD; + + if (m_staticNatEntry[key].nat_type == DNAT_NAT_TYPE) + { + SWSS_LOG_INFO("Delete static NAT conntrack entry with src-ip %s", m_staticNatEntry[key].local_ip.c_str()); + + cmds += (" -D -s " + m_staticNatEntry[key].local_ip + " -p udp" + " &> /dev/null"); + } + else if (m_staticNatEntry[key].nat_type == SNAT_NAT_TYPE) + { + SWSS_LOG_INFO("Delete static NAT conntrack entry with src-ip %s", key.c_str()); + + cmds += (" -D -s " + key + " -p udp" + " &> /dev/null"); + } + + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Deleted the Static NAT conntrack entry"); + } +} + +/* To Delete conntrack entry for Static Twice NAT entry */ +void NatMgr::deleteConntrackTwiceNatEntry(const string &snatKey, const string &dnatKey) +{ + std::string res, cmds = std::string("") + CONNTRACK_CMD; + + SWSS_LOG_INFO("Delete static Twice NAT conntrack entry with src-ip %s and dst-ip %s", snatKey.c_str(), dnatKey.c_str()); + + cmds += (" -D -s " + snatKey + " -d " + dnatKey + " &> /dev/null"); + + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Deleted the Static Twice NAT conntrack entry"); + } +} + +/* To Delete conntrack entry for Static Single NAPT entry */ +void NatMgr::deleteConntrackSingleNaptEntry(const string &key) +{ + std::string res, prototype, cmds = std::string("") + CONNTRACK_CMD; + vector keys = tokenize(key, config_db_key_delimiter); + + if (keys[1] == to_upper(IP_PROTOCOL_UDP)) + { + prototype = IP_PROTOCOL_UDP; + } + else if (keys[1] == to_upper(IP_PROTOCOL_TCP)) + { + prototype = IP_PROTOCOL_TCP; + } + + if (m_staticNaptEntry[key].nat_type == DNAT_NAT_TYPE) + { + SWSS_LOG_INFO("Delete static NAPT conntrack entry with protocol %s, src-ip %s, src-port %s", + prototype.c_str(), m_staticNaptEntry[key].local_ip.c_str(), m_staticNaptEntry[key].local_port.c_str()); + + cmds += (" -D -s " + m_staticNaptEntry[key].local_ip + " -p " + prototype + " --sport " + m_staticNaptEntry[key].local_port + " &> /dev/null"); + } + else if (m_staticNaptEntry[key].nat_type == SNAT_NAT_TYPE) + { + SWSS_LOG_INFO("Delete static NAPT conntrack entry with protocol %s, src-ip %s, src-port %s", + prototype.c_str(), keys[0].c_str(), keys[2].c_str()); + + cmds += (" -D -s " + keys[0] + " -p " + prototype + " --sport " + keys[2] + " &> /dev/null"); + } + + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Deleted the Static NAPT conntrack entry"); + } +} + +/* To Delete conntrack entry for Static Twice NAPT entry */ +void NatMgr::deleteConntrackTwiceNaptEntry(const string &snatKey, const string &dnatKey) +{ + std::string res, prototype, cmds = std::string("") + CONNTRACK_CMD; + vector snatKeys = tokenize(snatKey, config_db_key_delimiter); + vector dnatKeys = tokenize(dnatKey, config_db_key_delimiter); + + if (snatKeys[1] == to_upper(IP_PROTOCOL_UDP)) + { + prototype = IP_PROTOCOL_UDP; + } + else if (snatKeys[1] == to_upper(IP_PROTOCOL_TCP)) + { + prototype = IP_PROTOCOL_TCP; + } + + SWSS_LOG_INFO("Delete static Twice NAPT conntrack entry with protocol %s, src-ip %s, src-port %s, dst-ip %s, dst-port %s", + prototype.c_str(), snatKeys[0].c_str(), snatKeys[2].c_str(), dnatKeys[0].c_str(), dnatKeys[2].c_str()); + + cmds += (" -D -s " + snatKeys[0] + " -p " + prototype + " --orig-port-src " + snatKeys[2] + " -d " + dnatKeys[0] + " --orig-port-dst " + dnatKeys[2] + " &> /dev/null"); + + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Deleted the Static Twice NAPT conntrack entry"); + } +} + +/* To Delete conntrack entries for matching Pool ip address */ +void NatMgr::deleteConntrackDynamicEntries(const string &ip_range) +{ + std::string res, cmds; + + uint32_t ipv4_addr_low, ipv4_addr_high, ip, setIp; + char ipAddr[INET_ADDRSTRLEN]; + + vector nat_ip = tokenize(ip_range, range_specifier); + + /* Check the pool is valid */ + if (nat_ip.empty()) + { + SWSS_LOG_INFO("NAT pool is not valid"); + return; + } + else if (nat_ip.size() == 2) + { + inet_pton(AF_INET, nat_ip[1].c_str(), &ipv4_addr_high); + ipv4_addr_high = ntohl(ipv4_addr_high); + inet_pton(AF_INET, nat_ip[0].c_str(), &ipv4_addr_low); + ipv4_addr_low = ntohl(ipv4_addr_low); + } + else + { + inet_pton(AF_INET, nat_ip[0].c_str(), &ipv4_addr_low); + ipv4_addr_high = ntohl(ipv4_addr_low); + ipv4_addr_low = ntohl(ipv4_addr_low); + } + + for (ip = ipv4_addr_low; ip <= ipv4_addr_high; ip++) + { + setIp = htonl(ip); + inet_ntop(AF_INET, &setIp, ipAddr, INET_ADDRSTRLEN); + std::string ipAddrString(ipAddr); + + SWSS_LOG_INFO("Delete dynamic conntrack entry with translated-src-ip %s", ipAddr); + + cmds = (std::string("") + CONNTRACK_CMD + " -D -q " + ipAddrString + " &> /dev/null"); + + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Deleted the dynamic conntrack entry"); + } + } +} + +/* Iptable rules are added in the mangles table, to support use of Loopback IP as NAT Public IP which is a typical use-case in DC scenarios. The way it works is that: + * + * * The mangle table rules are processed first before the nat table rules. + * * Assign mark field on the packet using the mangles rules in the PREROUTING (ingress) and POSTROUTING (egress) stages. + * * The mark field is derived from the configured zone (zone + 1). Using 'mark'value of 0 has issues as that is implicit value for any packet traversing the kernel. + * * Match against the 'mark' value in the nat rules happens after the 'mangle' rule sets it. + * * Since packet doesn't go out of a Loopback interface, we configure zone value on the public interfaces same as on the Loopback interface (whose IP is used as NAT public IP). + * * So matching against the zone value is done while allocating NAT IPs. + * * + * * */ +bool NatMgr::setMangleIptablesRules(const string &opCmd, const string &interface, const string &nat_zone) +{ + SWSS_LOG_ENTER(); + + /* The command should be generated as: + * iptables -t mangle -opCmd PREROUTING -i port -j MARK --set-mark nat_zone + * iptables -t mangle -opCmd POSTROUTING -o port -j MARK --set-mark nat_zone + */ + std::string res; + int ret; + + const std::string cmds = std::string("") + + IPTABLES_CMD + " -t mangle " + "-" + opCmd + " PREROUTING -i " + interface + " -j MARK --set-mark " + nat_zone + " && " + + IPTABLES_CMD + " -t mangle " + "-" + opCmd + " POSTROUTING -o " + interface + " -j MARK --set-mark " + nat_zone ; + + ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + return false; + } + + return true; +} + +/* To Add arbitrary value for DNAT rule incase of fullcone */ +bool NatMgr::setFullConeDnatIptablesRule(const string &opCmd) +{ + /* This rule in the PREROUTING chain should be the default rule at the end of the list + * iptables -t nat -[A/D] PREROUTING -j DNAT --fullcone + */ + std::string res; + int ret; + + /* In case of fullcone, the --to-destination is ignored by the stack, giving an aribitrary value so that + * iptables doesn't fail for PREROUTING/DNAT rule */ + const std::string cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " PREROUTING " + " -j DNAT --to-destination 1.1.1.1 --fullcone"; + + ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + return false; + } + return true; +} + +/* To Add or Delete the Iptables rules for Static NAT entry */ +bool NatMgr::setStaticNatIptablesRules(const string &opCmd, const string &interface, const string &external_ip, const string &internal_ip, const string &nat_type) +{ + SWSS_LOG_ENTER(); + + /* The command should be generated as: + * iptables -t nat -opCmd PREROUTING -m mark --mark zone-value -j DNAT -d external_ip --to-destination internal_ip + * iptables -t nat -opCmd POSTROUTING -m mark --mark zone-value -j SNAT -s internal_ip --to-source external_ip + */ + std::string res; + std::string markStr = std::string(""); + int ret; + + markStr = " -m mark --mark " + m_natZoneInterfaceInfo[interface]; + + if (nat_type == DNAT_NAT_TYPE) + { + const std::string cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " PREROUTING " + markStr + " -j DNAT -d " + external_ip + " --to-destination " + internal_ip + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING " + markStr + " -j SNAT -s " + internal_ip + " --to-source " + external_ip ; + + ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + return false; + } + } + else + { + const std::string cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " PREROUTING" + " -j DNAT -d " + internal_ip + " --to-destination " + external_ip + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING" + " -j SNAT -s " + external_ip + " --to-source " + internal_ip ; + + ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + return false; + } + } + + return true; +} + +/* To Add or Delete the Iptables rules for Static NAPT entry */ +bool NatMgr::setStaticNaptIptablesRules(const string &opCmd, const string &interface, const string &prototype, const string &external_ip, + const string &external_port, const string &internal_ip, const string &internal_port, const string &nat_type) +{ + SWSS_LOG_ENTER(); + + /* The command should be generated as: + * iptables -t nat -opCmd PREROUTING -m mark --mark zone-value -p prototype -j DNAT -d external_ip --dport external_port --to-destination internal_ip:internal_port + * iptables -t nat -opCmd POSTROUTING -m mark --mark zone-value -p prototype -j SNAT -s internal_ip --sport internal_port --to-source external_ip:external_port + */ + std::string res; + std::string markStr = std::string(""); + int ret; + + markStr = " -m mark --mark " + m_natZoneInterfaceInfo[interface]; + + if (nat_type == DNAT_NAT_TYPE) + { + const std::string cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " PREROUTING " + markStr + " -p " + prototype + " -j DNAT -d " + external_ip + " --dport " + external_port + " --to-destination " + + internal_ip + ":" + internal_port + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING " + markStr + " -p " + prototype + " -j SNAT -s " + internal_ip + " --sport " + internal_port + " --to-source " + + external_ip + ":" + external_port; + + ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + return false; + } + } + else + { + const std::string cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " PREROUTING" + " -p " + prototype + " -j DNAT -d " + internal_ip + " --dport " + internal_port + " --to-destination " + + external_ip + ":" + external_port + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING" + " -p " + prototype + " -j SNAT -s " + external_ip + " --sport " + external_port + " --to-source " + + internal_ip + ":" + internal_port; + + ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + return false; + } + } + + return true; +} + +/* To Add or Delete the Iptables rules for Static Twice NAT entry */ +bool NatMgr::setStaticTwiceNatIptablesRules(const string &opCmd, const string &interface, const string &src_ip, const string &translated_src_ip, + const string &dest_ip, const string &translated_dest_ip) +{ + SWSS_LOG_ENTER(); + + /* The command should be generated as: + * iptables -t nat -opCmd PREROUTING -j DNAT -d translated_src --to-destination src -s translated_dst + * iptables -t nat -opCmd PREROUTING -m mark --mark zone-value -j DNAT -d dst --to-destination translated_dst -s src + * + * iptables -t nat -opCmd POSTROUTING -j SNAT -s src --to-source translated_src -d translated_dst + * iptables -t nat -opCmd POSTROUTING -m mark --mark zone-value -j SNAT -s translated_dst --to-source dst -d src + */ + + std::string res; + std::string markStr = std::string(""); + int ret; + + markStr = " -m mark --mark " + m_natZoneInterfaceInfo[interface]; + + const std::string cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " PREROUTING -j DNAT -d " + translated_src_ip + + " --to-destination " + src_ip + " -s " + translated_dest_ip + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " PREROUTING " + markStr + " -j DNAT -d " + dest_ip + + " --to-destination " + translated_dest_ip + " -s " + src_ip + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -j SNAT -s " + src_ip + + " --to-source " + translated_src_ip + " -d " + translated_dest_ip + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING " + markStr + " -j SNAT -s " + translated_dest_ip + + " --to-source " + dest_ip + " -d " + src_ip; + + ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + return false; + } + + return true; +} + +/* To Add or Delete the Iptables rules for Static Twice NAPT entry */ +bool NatMgr::setStaticTwiceNaptIptablesRules(const string &opCmd, const string &interface, const string &prototype, const string &src_ip, const string &src_port, + const string &translated_src_ip, const string &translated_src_port, const string &dest_ip, const string &dest_port, + const string &translated_dest_ip, const string &translated_dest_port) +{ + SWSS_LOG_ENTER(); + + /* The command should be generated as: + * iptables -t nat -opCmd PREROUTING -j DNAT -p udp -d translated_src --dport translated_src_l4_port --to-destination src:src_l4_port + * -s translated_dst --sport translated_dst_l4_port + * iptables -t nat -opCmd PREROUTING -m mark --mark zone-value -j DNAT -p udp -d dst --dport dst_l4_port --to-destination translated_dst:translated_dst_l4_port + * -s src --sport src_l4_port + * + * iptables -t nat -opCmd POSTROUTING -j SNAT -p udp -s src --sport src_l4_port --to-source translated_src:translated_src_l4_port + * -d translated_dst --dport translated_dst_l4_port + * iptables -t nat -opCmd POSTROUTING -m mark --mark zone-value -j SNAT -p udp -s translated_dst --sport translated_dst_l4_port --to-source dst:dst_l4_port + * -d src --dport src_l4_port + */ + + std::string res; + std::string markStr = std::string(""); + int ret; + + markStr = " -m mark --mark " + m_natZoneInterfaceInfo[interface]; + + const std::string cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " PREROUTING -p " + prototype + " -j DNAT -d " + translated_src_ip + " --dport " + translated_src_port + + " --to-destination " + src_ip + ":" + src_port + " -s " + translated_dest_ip + " --sport " + translated_dest_port + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " PREROUTING " + markStr + " -p " + prototype + " -j DNAT -d " + dest_ip + " --dport " + dest_port + + " --to-destination " + translated_dest_ip + ":" + translated_dest_port + " -s " + src_ip + " --sport " + src_port + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p " + prototype + " -j SNAT -s " + src_ip + " --sport " + src_port + + " --to-source " + translated_src_ip + ":" + translated_src_port + " -d " + translated_dest_ip + " --dport " + translated_dest_port + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING " + markStr + " -p " + prototype + " -j SNAT -s " + translated_dest_ip + " --sport " + translated_dest_port + + " --to-source " + dest_ip + ":" + dest_port + " -d " + src_ip + " --dport " +src_port; + + ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + return false; + } + + return true; +} + +/* To Add or Delete the Iptables rules for Dynamic NAT/NAPT without ACLs */ +bool NatMgr::setDynamicNatIptablesRulesWithoutAcl(const string &opCmd, const string &interface, const string &external_ip, + const string &external_port_range, const string &key) +{ + SWSS_LOG_ENTER(); + + /* The command should be generated as: + * + * iptables -t nat -opCmd POSTROUTING -p tcp -j SNAT -m mark --mark zone-value --to-source external_ip:external_port_range --fullcone + * iptables -t nat -opCmd POSTROUTING -p udp -j SNAT -m mark --mark zone-value --to-source external_ip:external_port_range --fullcone + * iptables -t nat -opCmd POSTROUTING -p icmp -j SNAT -m mark --mark zone-value --to-source external_ip:external_port_range --fullcone + */ + std::string res, cmd; + std::string externalString = EMPTY_STRING; + std::string fullcone = EMPTY_STRING; + std::string prototype = EMPTY_STRING; + std::string cmds = std::string(""); + std::string markStr = std::string(""); + + markStr = " -m mark --mark " + m_natZoneInterfaceInfo[interface]; + + if (external_port_range.empty()) + { + externalString = external_ip; + } + else + { + externalString = external_ip + ":" + external_port_range; + fullcone = " --fullcone"; + } + + /* Static Key empty means Single NAT */ + if (key.empty()) + { + /* Rules for Single NAT */ + cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p tcp -j SNAT " + markStr + " --to-source " + + externalString + fullcone + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p udp -j SNAT " + markStr + " --to-source " + + externalString + fullcone + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p icmp -j SNAT " + markStr + " --to-source " + + externalString + fullcone; + } + else + { + if (opCmd == ADD) + { + cmd = INSERT; + } + else + { + cmd = opCmd; + } + + vector keys = tokenize(key, config_db_key_delimiter); + if (keys.size() > 1) + { + if (keys[1] == to_upper(IP_PROTOCOL_UDP)) + { + prototype = " -p udp "; + } + else if (keys[1] == to_upper(IP_PROTOCOL_TCP)) + { + prototype = " -p tcp "; + } + + /* Rules for Double NAT */ + cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING " + prototype + " -j SNAT " + markStr + " --to-source " + + externalString + " -d " + keys[0] + " --dport " + keys[2] + fullcone + " && " + + IPTABLES_CMD + " -t nat " + "-" + cmd + " PREROUTING " + prototype + " -j DNAT -d " + m_staticNaptEntry[key].local_ip + " --dport " + + m_staticNaptEntry[key].local_port + " --to-destination " + keys[0] + ":" + keys[2] + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING " + prototype + " -j SNAT -s " + keys[0] + " --sport " + + keys[2] + " --to-source " + m_staticNaptEntry[key].local_ip + ":" + m_staticNaptEntry[key].local_port; + } + else + { + /* Rules for Double NAT */ + cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING " + prototype + " -j SNAT " + markStr + " --to-source " + + externalString + " -d " + key + fullcone + " && " + + IPTABLES_CMD + " -t nat " + "-" + cmd + " PREROUTING" + " -j DNAT -d " + m_staticNatEntry[key].local_ip + " --to-destination " + key + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING" + " -j SNAT -s " + key + " --to-source " + m_staticNatEntry[key].local_ip ; + } + } + + int ret = swss::exec(cmds, res); + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + return false; + } + + return true; +} + +/* To Add or Delete the Iptables rules for Dynamic NAT/NAPT with ACLs */ +bool NatMgr::setDynamicNatIptablesRulesWithAcl(const string &opCmd, const string &interface, const string &external_ip, + const string &external_port_range, natAclRule_t &natAclRuleId, + const string &key) +{ + SWSS_LOG_ENTER(); + + /* The command should be generated as: for example + * + * iptables -t nat -opCmd POSTROUTING -p tcp -s srcIpAddress -j RETURN + * iptables -t nat -opCmd POSTROUTING -p udp -s srcIpAddress -j RETURN + * iptables -t nat -opCmd POSTROUTING -p icmp -s srcIpAddress -j RETURN + * + * iptables -t nat -opCmd POSTROUTING -p tcp srcIpAddressString -j SNAT -m mark --mark zone-value --to-source external_ip:external_port_range --fullcone + * iptables -t nat -opCmd POSTROUTING -p udp srcIpAddressString -j SNAT -m mark --mark zone-value --to-source external_ip:external_port_range --fullcone + * iptables -t nat -opCmd POSTROUTING -p icmp srcIpAddressString -j SNAT -m mark --mark zone-value --to-source external_ip:external_port_range --fullcone + */ + + std::string res, cmd; + std::string srcIpAddressString = EMPTY_STRING, dstIpAddressString = EMPTY_STRING; + std::string srcPortString = EMPTY_STRING, dstPortString = EMPTY_STRING; + std::string externalString = EMPTY_STRING, fullcone = EMPTY_STRING; + std::string prototype = EMPTY_STRING; + std::string cmds = std::string(""); + vector keys; + std::string markStr = std::string(""); + + markStr = " -m mark --mark " + m_natZoneInterfaceInfo[interface]; + + if (external_port_range.empty()) + { + externalString = external_ip; + } + else + { + externalString = external_ip + ":" + external_port_range; + fullcone = " --fullcone"; + } + + if (natAclRuleId.src_ip_range != "None") + { + srcIpAddressString = " -s " + natAclRuleId.src_ip_range; + } + + if (natAclRuleId.dst_ip_range != "None") + { + dstIpAddressString = " -d " + natAclRuleId.dst_ip_range; + } + + if (natAclRuleId.src_l4_port_range != "None") + { + srcPortString = " --sport " + natAclRuleId.src_l4_port_range; + } + + if (natAclRuleId.dst_l4_port_range != "None") + { + dstPortString = " --dport " + natAclRuleId.dst_l4_port_range; + } + + /* Static Key not empty means Double NAT */ + if (!key.empty()) + { + /* Destination IP/Port address from ACL Rule not valid case for Double NAT */ + if (!dstIpAddressString.empty() or !dstPortString.empty()) + { + SWSS_LOG_WARN("Destination IP/Port is not valid for Twice NAT, skipped adding the ACL Rule"); + return true; + } + + keys = tokenize(key, config_db_key_delimiter); + if (keys.size() > 1) + { + /* Protocol from ACL Rule is not matching with static entry for Double NAT */ + if ((natAclRuleId.ip_protocol != "None") and (natAclRuleId.ip_protocol != keys[1])) + { + SWSS_LOG_WARN("Rule protocol %s is not matching with Static entry, skipped adding the ACL Rule", natAclRuleId.ip_protocol.c_str()); + return true; + } + + if (keys[1] == to_upper(IP_PROTOCOL_UDP)) + { + prototype = " -p udp "; + } + else if (keys[1] == to_upper(IP_PROTOCOL_TCP)) + { + prototype = " -p tcp "; + } + } + if (opCmd == ADD) + { + cmd = INSERT; + } + else + { + cmd = opCmd; + } + } + + if (natAclRuleId.packet_action == PACKET_ACTION_DO_NOT_NAT) + { + /* Rule are for all ip protocols */ + if (natAclRuleId.ip_protocol == "None") + { + /* Static Key empty means Single NAT */ + if (key.empty()) + { + /* Rules for Single NAT */ + cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p tcp" + srcIpAddressString + dstIpAddressString + + srcPortString + dstPortString + " -j RETURN" + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p udp" + srcIpAddressString + dstIpAddressString + + srcPortString + dstPortString + " -j RETURN" + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p icmp" + srcIpAddressString + dstIpAddressString + + srcPortString + dstPortString + " -j RETURN"; + } + else + { + /* Rules for Double NAT */ + if (keys.size() > 1) + { + cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p tcp" + srcIpAddressString + " -d " + keys[0] + + srcPortString + " --dport " + keys[2] + " -j RETURN" + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p udp" + srcIpAddressString + " -d " + keys[0] + + srcPortString + " --dport " + keys[2] + " -j RETURN" + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p icmp" + srcIpAddressString + " -d " + keys[0] + + srcPortString + " --dport " + keys[2] + " -j RETURN"; + } + else + { + cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p tcp" + srcIpAddressString + " -d " + keys[0] + + srcPortString + " -j RETURN" + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p udp" + srcIpAddressString + " -d " + keys[0] + + srcPortString + " -j RETURN" + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p icmp" + srcIpAddressString + " -d " + keys[0] + + srcPortString + " -j RETURN"; + } + + } + } + else + { + /* Static Key empty means Single NAT */ + if (key.empty()) + { + /* Rule for Single NAT */ + cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p " + natAclRuleId.ip_protocol + srcIpAddressString + + dstIpAddressString + srcPortString + dstPortString + " -j RETURN"; + } + else + { + if (keys.size() > 1) + { + /* Rules for Double NAT */ + cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p " + natAclRuleId.ip_protocol + srcIpAddressString + + " -d " + keys[0] + srcPortString + " --dport " + keys[2] + " -j RETURN"; + } + else + { + /* Rules for Double NAT */ + cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p " + natAclRuleId.ip_protocol + srcIpAddressString + + " -d " + keys[0] + srcPortString + " -j RETURN"; + } + } + } + } + else + { + /* Static Key empty means Single NAT */ + if (key.empty()) + { + /* Rules for all ip protocols */ + if (natAclRuleId.ip_protocol == "None") + { + cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p tcp" + srcIpAddressString + dstIpAddressString + srcPortString + dstPortString + + " -j SNAT " + markStr + " --to-source " + externalString + fullcone + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p udp" + srcIpAddressString + dstIpAddressString + srcPortString + dstPortString + + " -j SNAT " + markStr + " --to-source " + externalString + fullcone + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p icmp" + srcIpAddressString + dstIpAddressString + srcPortString + dstPortString + + " -j SNAT " + markStr + " --to-source " + externalString + fullcone; + } + else + { + cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING -p " + natAclRuleId.ip_protocol + srcIpAddressString + + dstIpAddressString + srcPortString + dstPortString + " -j SNAT " + markStr + " --to-source " + externalString + fullcone; + } + } + else + { + if (keys.size() > 1) + { + /* Rules for Double NAT */ + cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING " + prototype + " -j SNAT " + markStr + srcIpAddressString + srcPortString + + " --to-source " + externalString + " -d " + keys[0] + " --dport " + keys[2] + fullcone + " && " + + IPTABLES_CMD + " -t nat " + "-" + cmd + " PREROUTING " + prototype + " -j DNAT -d " + m_staticNaptEntry[key].local_ip + " --dport " + + m_staticNaptEntry[key].local_port + srcIpAddressString + srcPortString + " --to-destination " + keys[0] + ":" + keys[2] + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING " + prototype + " -j SNAT -s " + key[0] + " --sport " + + keys[2] + " --to-source " + m_staticNaptEntry[key].local_ip + ":" + m_staticNaptEntry[key].local_port; + } + else + { + /* Rules for Double NAT */ + cmds = std::string("") + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING " + prototype + " -j SNAT " + markStr + srcIpAddressString + + " --to-source " + externalString + " -d " + key + fullcone + " && " + + IPTABLES_CMD + " -t nat " + "-" + cmd + " PREROUTING" + " -j DNAT -d " + m_staticNatEntry[key].local_ip + srcIpAddressString + + " --to-destination " + key + " && " + + IPTABLES_CMD + " -t nat " + "-" + opCmd + " POSTROUTING" + " -j SNAT -s " + key + " --to-source " + m_staticNatEntry[key].local_ip ; + } + } + } + + int ret = swss::exec(cmds, res); + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + return false; + } + + return true; +} + +/* To add Static NAT entry based on Static Key if all valid conditions are met */ +void NatMgr::addStaticNatEntry(const string &key) +{ + /* Example: + * Entry is STATIC_NAT|65.55.42.1 and key is 65.55.42.1 + */ + + string interface = EMPTY_STRING; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s NAT entry addition to APPL_DB", key.c_str()); + return; + } + + /* Get the matching Ip interface for dnat type, otherwise return */ + if ((m_staticNatEntry[key].nat_type == DNAT_NAT_TYPE) and (!getIpEnabledIntf(key, interface))) + { + SWSS_LOG_INFO("L3 Interface is not yet enabled for %s, skipping NAT entry addition to APPL_DB", key.c_str()); + return; + } + + /* Check the Static NAT is conflicts with Static NAPT */ + if (isMatchesWithStaticNapt(key, m_staticNatEntry[key].local_ip)) + { + SWSS_LOG_ERROR("Invalid : %s conflicts with Static NAPT, skipping NAT entry addition to APPL_DB", key.c_str()); + return; + } + + m_staticNatEntry[key].interface = interface; + + if (m_staticNatEntry[key].twice_nat_id.empty()) + { + /* Add the new Static Single NAT entry */ + SWSS_LOG_INFO("Adding the Static Single NAT entry for %s", key.c_str()); + addStaticSingleNatEntry(key); + } + else + { + /* Add the new Static Twice NAT entry */ + SWSS_LOG_INFO("Adding the Static Twice NAT entry for %s", key.c_str()); + addStaticTwiceNatEntry(key); + } +} + +/* To add Static NAPT entry based on Static Key if all valid conditions are met */ +void NatMgr::addStaticNaptEntry(const string &key) +{ + /* Example: + * Entry is STATIC_NAPT|65.55.42.1|TCP|1024 and key is 65.55.42.1|TCP|1024 + */ + + string interface = EMPTY_STRING, prototype = EMPTY_STRING; + vector keys = tokenize(key, config_db_key_delimiter); + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s NAPT entry addition to APPL_DB", key.c_str()); + return; + } + + /* Get the matching Ip interface for dnat type, otherwise return */ + if ((m_staticNaptEntry[key].nat_type == DNAT_NAT_TYPE) and (!getIpEnabledIntf(keys[0], interface))) + { + SWSS_LOG_INFO("L3 Interface is not yet enabled for %s, skipping NAPT entry addition to APPL_DB", key.c_str()); + return; + } + + /* Check the Static NAPT is conflicts with Static NAT */ + if (isMatchesWithStaticNat(keys[0], m_staticNaptEntry[key].local_ip)) + { + SWSS_LOG_ERROR("Invalid : %s conflicts with Static NAT, skipping NAPT entry addition to APPL_DB", key.c_str()); + return; + } + + m_staticNaptEntry[key].interface = interface; + + if (m_staticNaptEntry[key].twice_nat_id.empty()) + { + /* Add the new Static Single NAPT entry */ + SWSS_LOG_INFO("Adding the Static Single NAPT entry for %s", key.c_str()); + addStaticSingleNaptEntry(key); + } + else + { + /* Add the new Static Twice NAPT entry */ + SWSS_LOG_INFO("Adding the Static Twice NAPT entry for %s", key.c_str()); + addStaticTwiceNaptEntry(key); + } +} + +/* To delete Static NAT entry based on Static Key if all valid conditions are met */ +void NatMgr::removeStaticNatEntry(const string &key) +{ + /* Example: + * Entry is STATIC_NAT|65.55.42.1 and key is 65.55.42.1 + */ + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s NAT entry deletion", key.c_str()); + return; + } + + if (m_staticNatEntry[key].twice_nat_id.empty()) + { + /* Remove the Static Single NAT Entry */ + SWSS_LOG_INFO("Deleting the Static Single NAT entry for %s", key.c_str()); + removeStaticSingleNatEntry(key); + } + else if ((m_staticNatEntry[key].twice_nat_added == true)) + { + /* Remove the Static Twice NAT entry */ + SWSS_LOG_INFO("Deleting the Static Twice NAT entry for %s", key.c_str()); + removeStaticTwiceNatEntry(key); + } + else + { + SWSS_LOG_INFO("No Static Twice NAT entry to delete for %s", key.c_str()); + m_staticNatEntry[key].interface = NONE_STRING; + } +} + +/* To delete Static NAPT entry based on Static Key if all valid conditions are met */ +void NatMgr::removeStaticNaptEntry(const string &key) +{ + /* Example: + * Entry is STATIC_NAPT|65.55.42.1|TCP|1024 and key is 65.55.42.1|TCP|1024 + */ + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s NAPT entry deletion", key.c_str()); + return; + } + + if (m_staticNaptEntry[key].twice_nat_id.empty()) + { + /* Remove the Static Single NAPT Entry */ + SWSS_LOG_INFO("Deleting the Static Single NAPT entry for %s", key.c_str()); + removeStaticSingleNaptEntry(key); + } + else if ((m_staticNaptEntry[key].twice_nat_added == true)) + { + /* Remove the Static Twice NAPT entry */ + SWSS_LOG_INFO("Deleting the Static Twice NAPT entry for %s", key.c_str()); + removeStaticTwiceNaptEntry(key); + } + else + { + SWSS_LOG_INFO("No Static Twice NAPT entry to delete for %s", key.c_str()); + m_staticNaptEntry[key].interface = NONE_STRING; + } +} + +/* To add Static NAT entries based on L3 Interface if all valid conditions are met */ +void NatMgr::addStaticNatEntries(const string port, const string ipPrefix) +{ + /* Example: + * Port is Ethernet1 and ipPrefix is 10.0.0.1/24 + */ + + string prototype, interface; + bool isEntryAdded = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping NAT entry addition to APPL_DB"); + return; + } + + /* Get all the Static NAT entries */ + for (auto it = m_staticNatEntry.begin(); it != m_staticNatEntry.end(); it++) + { + prototype = EMPTY_STRING, interface = EMPTY_STRING; + + /* Check interface is assigned, means Entry is already added, otherwise continue */ + if ((*it).second.interface != NONE_STRING) + { + continue; + } + + if ((port == NONE_STRING) and (ipPrefix == NONE_STRING)) + { + /* Get the matching Ip interface for dnat type, otherwise return */ + if (((*it).second.nat_type == DNAT_NAT_TYPE) and (!getIpEnabledIntf((*it).first, interface))) + { + continue; + } + } + else + { + /* Check Global ip is matching, otherwise continue */ + if (isGlobalIpMatching(ipPrefix, (*it).first) == false) + { + continue; + } + interface = port; + } + + /* Check the Static NAT is conflicts with Static NAPT */ + if (isMatchesWithStaticNapt((*it).first, (*it).second.local_ip)) + { + continue; + } + + (*it).second.interface = interface; + + isEntryAdded = true; + + if ((*it).second.twice_nat_id.empty()) + { + /* Add the new Static Single NAT entry */ + SWSS_LOG_INFO("Adding the Static Single NAT entry for %s", (*it).first.c_str()); + addStaticSingleNatEntry((*it).first); + } + else + { + /* Add the new Static Twice NAT entry */ + SWSS_LOG_INFO("Adding the Static Twice NAT entry for %s", (*it).first.c_str()); + addStaticTwiceNatEntry((*it).first); + } + } + + if (!isEntryAdded) + { + SWSS_LOG_INFO("No Static NAT entries to add"); + } +} + +/* To add Static NAPT entries based on L3 Interface if all valid conditions are met */ +void NatMgr::addStaticNaptEntries(const string port, const string ipPrefix) +{ + /* Example: + * Port is Ethernet1 and ipPrefix is 10.0.0.1/24 + */ + + string prototype, interface; + bool isEntryAdded = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping NAPT entry addition to APPL_DB"); + return; + } + + /* Get all the Static NAPT entries */ + for (auto it = m_staticNaptEntry.begin(); it != m_staticNaptEntry.end(); it++) + { + vector keys = tokenize((*it).first, config_db_key_delimiter); + vector fvVectorSnat, fvVectorDnat; + prototype = EMPTY_STRING, interface = EMPTY_STRING; + + /* Check interface is assigned, means Entry is already added otherwise continue*/ + if ((*it).second.interface != NONE_STRING) + { + continue; + } + + if ((port == NONE_STRING) and (ipPrefix == NONE_STRING)) + { + /* Get the matching Ip interface for dnat type, otherwise return */ + if (((*it).second.nat_type == DNAT_NAT_TYPE) and (!getIpEnabledIntf(keys[0], interface))) + { + continue; + } + } + else + { + /* Check Global ip is matching, otherwise continue */ + if (isGlobalIpMatching(ipPrefix, keys[0]) == false) + { + continue; + } + interface = port; + } + + /* Check the Static NAPT is conflicts with Static NAT */ + if (isMatchesWithStaticNat(keys[0], (*it).second.local_ip)) + { + continue; + } + + (*it).second.interface = interface; + + isEntryAdded = true; + + if ((*it).second.twice_nat_id.empty()) + { + /* Add the new Static Single NAPT entry */ + SWSS_LOG_INFO("Adding the Static Single NAPT entry for %s", (*it).first.c_str()); + addStaticSingleNaptEntry((*it).first); + } + else + { + /* Add the new Static Twice NAPT entry */ + SWSS_LOG_INFO("Adding the Static Twice NAPT entry for %s", (*it).first.c_str()); + addStaticTwiceNaptEntry((*it).first); + } + } + + if (!isEntryAdded) + { + SWSS_LOG_INFO("No Static NAPT entries to add"); + } +} + +/* To delete Static NAT entries based on L3 Interface if all valid conditions are met */ +void NatMgr::removeStaticNatEntries(const string port, const string ipPrefix) +{ + /* Example: + * Port is Ethernet1 and ipPrefix is 10.0.0.1/24 + */ + + string prototype, interface; + bool isEntryDeleted = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping NAT entry deletion from APPL_DB"); + return; + } + + /* Get all the Static NAT entries */ + for (auto it = m_staticNatEntry.begin(); it != m_staticNatEntry.end(); it++) + { + prototype = EMPTY_STRING, interface = EMPTY_STRING; + + if ((port == NONE_STRING) and (ipPrefix == NONE_STRING)) + { + /* Get the matching Ip interface for dnat type, otherwise return */ + if (((*it).second.nat_type == DNAT_NAT_TYPE) and (!getIpEnabledIntf((*it).first, interface))) + { + continue; + } + + /* Check interface is matching, otherwise continue */ + if ((*it).second.interface != interface) + { + continue; + } + } + else + { + /* Check interface is matching, otherwise continue */ + if ((*it).second.interface != port) + { + continue; + } + + /* Check Global ip is matching, otherwise continue */ + if (isGlobalIpMatching(ipPrefix, (*it).first) == false) + { + continue; + } + interface = port; + } + + isEntryDeleted = true; + + /* Remove the Static NAT Entry */ + SWSS_LOG_INFO("Deleting the Static NAT entry for %s", (*it).first.c_str()); + removeStaticNatEntry((*it).first); + } + + if (!isEntryDeleted) + { + SWSS_LOG_INFO("No Static NAT entries to delete"); + } +} + +/* To delete Static NAPT entries based on L3 Interface if all valid conditions are met */ +void NatMgr::removeStaticNaptEntries(const string port, const string ipPrefix) +{ + /* Example: + * Port is Ethernet1 and ipPrefix is 10.0.0.1/24 + */ + + string prototype, interface; + bool isEntryDeleted = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping NAPT entry deletion from APPL_DB"); + return; + } + + /* Get all the Static NAPT entries */ + for (auto it = m_staticNaptEntry.begin(); it != m_staticNaptEntry.end(); it++) + { + vector keys = tokenize((*it).first, config_db_key_delimiter); + prototype = EMPTY_STRING, interface = EMPTY_STRING; + + if ((port == NONE_STRING) and (ipPrefix == NONE_STRING)) + { + /* Get the matching Ip interface for dnat type, otherwise return */ + if (((*it).second.nat_type == DNAT_NAT_TYPE) and (!getIpEnabledIntf(keys[0], interface))) + { + continue; + } + + /* Check interface is matching, otherwise continue */ + if ((*it).second.interface != interface) + { + continue; + } + } + else + { + /* Check interface is matching, otherwise continue */ + if ((*it).second.interface != port) + { + continue; + } + + /* Check Global ip is matching, otherwise continue */ + if (isGlobalIpMatching(ipPrefix, keys[0]) == false) + { + continue; + } + interface = port; + } + + isEntryDeleted = true; + + /* Remove the Static NAPT Entry */ + SWSS_LOG_INFO("Deleting the Static NAPT entry for %s", (*it).first.c_str()); + removeStaticNaptEntry((*it).first); + } + + if (!isEntryDeleted) + { + SWSS_LOG_INFO("No Static NAPT entries to delete"); + } +} + +/* To add Static Single NAT entry based on Static Key */ +void NatMgr::addStaticSingleNatEntry(const string &key) +{ + /* Example: + * Entry is STATIC_NAT|65.55.42.1 and key is 65.55.42.1 + */ + + string appKeyDnat = EMPTY_STRING, appKeySnat = EMPTY_STRING; + string interface = m_staticNatEntry[key].interface; + vector fvVectorDnat, fvVectorSnat; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s Single NAT entry addition to APPL_DB", key.c_str()); + return; + } + + /* Create APPL_DB key and it's values */ + if (m_staticNatEntry[key].nat_type == DNAT_NAT_TYPE) + { + appKeyDnat += key; + FieldValueTuple p(TRANSLATED_IP, m_staticNatEntry[key].local_ip); + fvVectorDnat.push_back(p); + + appKeySnat += m_staticNatEntry[key].local_ip; + FieldValueTuple q(TRANSLATED_IP, key); + fvVectorSnat.push_back(q); + } + else if (m_staticNatEntry[key].nat_type == SNAT_NAT_TYPE) + { + appKeySnat += key; + FieldValueTuple p(TRANSLATED_IP, m_staticNatEntry[key].local_ip); + fvVectorSnat.push_back(p); + + appKeyDnat += m_staticNatEntry[key].local_ip; + FieldValueTuple q(TRANSLATED_IP, key); + fvVectorDnat.push_back(q); + } + + FieldValueTuple r(NAT_TYPE, DNAT_NAT_TYPE); + fvVectorDnat.push_back(r); + FieldValueTuple s(NAT_TYPE, SNAT_NAT_TYPE); + fvVectorSnat.push_back(s); + + FieldValueTuple t(ENTRY_TYPE, STATIC_ENTRY_TYPE); + fvVectorDnat.push_back(t); + fvVectorSnat.push_back(t); + + if (m_staticNatEntry[key].twice_nat_id != EMPTY_STRING) + { + FieldValueTuple t(TWICE_NAT_ID, m_staticNatEntry[key].twice_nat_id); + fvVectorDnat.push_back(t); + fvVectorSnat.push_back(t); + } + + /* Add it to APPL_DB */ + m_appNatTableProducer.set(appKeyDnat, fvVectorDnat); + m_appNatTableProducer.set(appKeySnat, fvVectorSnat); + + SWSS_LOG_INFO("Added Static NAT %s to APPL_DB", key.c_str()); + + /* Add a dummy conntrack entry for Static NAT entry */ + addConntrackSingleNatEntry(key); + + /* Add Static NAT iptables rule */ + if (!setStaticNatIptablesRules(INSERT, interface, key, m_staticNatEntry[key].local_ip, m_staticNatEntry[key].nat_type)) + { + SWSS_LOG_ERROR("Failed to add Static NAT iptables rules for %s", key.c_str()); + } + else + { + SWSS_LOG_INFO("Added Static NAT iptables rules for %s", key.c_str()); + } +} + +/* To add Static Twice NAT entry based on Static Key if all valid conditions are met */ +void NatMgr::addStaticTwiceNatEntry(const string &key) +{ + /* Example: + * Entry is STATIC_NAT|65.55.42.1 and key is 65.55.42.1 + */ + + string interface = EMPTY_STRING; + string src, translated_src, dest, translated_dest; + bool isEntryAdded = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s Twice NAT entry addition to APPL_DB", key.c_str()); + return; + } + + /* Get all the Static NAT entries */ + for (auto it = m_staticNatEntry.begin(); it != m_staticNatEntry.end(); it++) + { + vector fvVector, reversefvVector; + + /* Check for other entries, otherwise continue */ + if ((*it).first == key) + { + continue; + } + + /* Check the twice_nat_id is matched, otherwise continue */ + if ((*it).second.twice_nat_id != m_staticNatEntry[key].twice_nat_id) + { + continue; + } + + /* Check the twice NAT is not added, otherwise continue */ + if ((*it).second.twice_nat_added or m_staticNatEntry[key].twice_nat_added) + { + continue; + } + + /* Check interface is assigned, otherwise continue */ + if (((*it).second.interface == NONE_STRING) or (m_staticNatEntry[key].interface == NONE_STRING)) + { + continue; + } + + /* Check the nat type is different, otherwise continue */ + if ((*it).second.nat_type == m_staticNatEntry[key].nat_type) + { + continue; + } + + if (((*it).second.nat_type == DNAT_NAT_TYPE) and + (m_staticNatEntry[key].nat_type == SNAT_NAT_TYPE)) + { + src = key; + dest = (*it).first; + translated_src = m_staticNatEntry[key].local_ip; + translated_dest = (*it).second.local_ip; + interface = (*it).second.interface; + } + else if (((*it).second.nat_type == SNAT_NAT_TYPE) and + (m_staticNatEntry[key].nat_type == DNAT_NAT_TYPE)) + { + src = (*it).first; + dest = key; + translated_src = (*it).second.local_ip; + translated_dest = m_staticNatEntry[key].local_ip; + interface = m_staticNatEntry[key].interface; + } + + /* Create APPL_DB key and it's values */ + string appKey = src + ":" + dest; + string reverseAppKey = translated_dest + ":" + translated_src; + + FieldValueTuple p(TRANSLATED_SRC_IP, translated_src); + fvVector.push_back(p); + FieldValueTuple q(TRANSLATED_DST_IP, translated_dest); + fvVector.push_back(q); + FieldValueTuple r(ENTRY_TYPE, STATIC_ENTRY_TYPE); + fvVector.push_back(r); + + FieldValueTuple p1(TRANSLATED_SRC_IP, dest); + reversefvVector.push_back(p1); + FieldValueTuple q1(TRANSLATED_DST_IP, src); + reversefvVector.push_back(q1); + reversefvVector.push_back(r); + + (*it).second.twice_nat_added = true; + m_staticNatEntry[key].twice_nat_added = true; + + /* Add it to APPL_DB */ + m_appTwiceNatTableProducer.set(appKey, fvVector); + m_appTwiceNatTableProducer.set(reverseAppKey, reversefvVector); + SWSS_LOG_INFO("Added Static Twice NAT for %s and %s to APPL_DB", key.c_str(), (*it).first.c_str()); + + /* Add a dummy conntrack entry for Static Twice NAT entry */ + if (((*it).second.nat_type == DNAT_NAT_TYPE) and + (m_staticNatEntry[key].nat_type == SNAT_NAT_TYPE)) + { + addConntrackTwiceNatEntry(key, (*it).first); + } + else if (((*it).second.nat_type == SNAT_NAT_TYPE) and + (m_staticNatEntry[key].nat_type == DNAT_NAT_TYPE)) + { + addConntrackTwiceNatEntry((*it).first, key); + } + + /* Add Static NAT iptables rule */ + if (!setStaticTwiceNatIptablesRules(INSERT, interface, src, translated_src, dest, translated_dest)) + { + SWSS_LOG_ERROR("Failed to add Static Twice NAT iptables rules for %s and %s", key.c_str(), (*it).first.c_str()); + } + else + { + isEntryAdded = true; + SWSS_LOG_INFO("Added Static Twice NAT iptables rules for %s and %s", key.c_str(), (*it).first.c_str()); + } + break; + } + + if (!isEntryAdded) + { + SWSS_LOG_INFO("No Static Twice NAT entries to add"); + } + else + { + return; + } + + /* Get all Binding Info */ + for (auto it = m_natBindingInfo.begin(); it != m_natBindingInfo.end(); it++) + { + string pool_name = (*it).second.pool_name; + string port_range = EMPTY_STRING; + + /* Check the pool is present in cache, otherwise continue */ + if (m_natPoolInfo.find(pool_name) == m_natPoolInfo.end()) + { + continue; + } + + /* Check the twice_nat_id is matched, otherwise continue */ + if ((*it).second.twice_nat_id != m_staticNatEntry[key].twice_nat_id) + { + continue; + } + + /* Check the twice NAT is not added, otherwise continue */ + if ((*it).second.twice_nat_added or m_staticNatEntry[key].twice_nat_added) + { + continue; + } + + /* Check interface is assigned, means Entry is already added, otherwise continue */ + if (((*it).second.pool_interface == NONE_STRING) or (m_staticNatEntry[key].interface == NONE_STRING)) + { + continue; + } + + /* Check the nat type is same, otherwise continue */ + if ((*it).second.nat_type != m_staticNatEntry[key].nat_type) + { + continue; + } + + /* Check the port_range is not present, otherwise continue */ + port_range = m_natPoolInfo[pool_name].port_range; + if (!port_range.empty() and (port_range != "NULL")) + { + continue; + } + + vector nat_ip = tokenize(m_natPoolInfo[pool_name].ip_range, range_specifier); + + /* Check the pool is valid */ + if (nat_ip.empty()) + { + SWSS_LOG_INFO("NAT pool %s is not valid, skipping dynamic twice nat iptables rules addition for %s", pool_name.c_str(), (*it).first.c_str()); + continue; + } + + (*it).second.twice_nat_added = true; + (*it).second.static_key = key; + m_staticNatEntry[key].twice_nat_added = true; + m_staticNatEntry[key].binding_key = (*it).first; + isEntryAdded = true; + + setDynamicAllForwardOrAclbasedRules(ADD, (*it).second.pool_interface, m_natPoolInfo[pool_name].ip_range, + m_natPoolInfo[pool_name].port_range, (*it).second.acl_name, + (*it).first); + + break; + } + + if (!isEntryAdded) + { + SWSS_LOG_INFO("No Static-Dynamic Twice NAT entries to add"); + } + +} + +/* To add Static Single NAPT entry based on Static Key */ +void NatMgr::addStaticSingleNaptEntry(const string &key) +{ + /* Example: + * Entry is STATIC_NAPT|65.55.42.1|TCP|1024 and key is 65.55.42.1|TCP|1024 + */ + + string prototype = EMPTY_STRING, interface = m_staticNaptEntry[key].interface;; + vector keys = tokenize(key, config_db_key_delimiter); + string appKeyDnat = EMPTY_STRING, appKeySnat = EMPTY_STRING; + vector fvVectorDnat, fvVectorSnat; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s Twice NAT entry addition to APPL_DB", key.c_str()); + return; + } + + if (keys[1] == to_upper(IP_PROTOCOL_UDP)) + { + prototype = IP_PROTOCOL_UDP; + } + else if (keys[1] == to_upper(IP_PROTOCOL_TCP)) + { + prototype = IP_PROTOCOL_TCP; + } + + /* Create the APPL_DB key and it's values */ + if (m_staticNaptEntry[key].nat_type == DNAT_NAT_TYPE) + { + appKeyDnat += (keys[1] + DEFAULT_KEY_SEPARATOR + keys[0] + DEFAULT_KEY_SEPARATOR + keys[2]); + FieldValueTuple p(TRANSLATED_IP, m_staticNaptEntry[key].local_ip); + FieldValueTuple q(TRANSLATED_L4_PORT, m_staticNaptEntry[key].local_port); + fvVectorDnat.push_back(p); + fvVectorDnat.push_back(q); + + appKeySnat += (keys[1] + DEFAULT_KEY_SEPARATOR + m_staticNaptEntry[key].local_ip + DEFAULT_KEY_SEPARATOR + m_staticNaptEntry[key].local_port); + FieldValueTuple r(TRANSLATED_IP, keys[0]); + FieldValueTuple s(TRANSLATED_L4_PORT, keys[2]); + fvVectorSnat.push_back(r); + fvVectorSnat.push_back(s); + } + else if (m_staticNaptEntry[key].nat_type == SNAT_NAT_TYPE) + { + appKeySnat += (keys[1] + DEFAULT_KEY_SEPARATOR + keys[0] + DEFAULT_KEY_SEPARATOR + keys[2]); + FieldValueTuple p(TRANSLATED_IP, m_staticNaptEntry[key].local_ip); + FieldValueTuple q(TRANSLATED_L4_PORT, m_staticNaptEntry[key].local_port); + fvVectorSnat.push_back(p); + fvVectorSnat.push_back(q); + + appKeyDnat += (keys[1] + DEFAULT_KEY_SEPARATOR + m_staticNaptEntry[key].local_ip + DEFAULT_KEY_SEPARATOR + m_staticNaptEntry[key].local_port); + FieldValueTuple r(TRANSLATED_IP, keys[0]); + FieldValueTuple s(TRANSLATED_L4_PORT, keys[2]); + fvVectorDnat.push_back(r); + fvVectorDnat.push_back(s); + } + + FieldValueTuple t(NAT_TYPE, DNAT_NAT_TYPE); + fvVectorDnat.push_back(t); + FieldValueTuple u(NAT_TYPE, SNAT_NAT_TYPE); + fvVectorSnat.push_back(u); + + FieldValueTuple v(ENTRY_TYPE, STATIC_ENTRY_TYPE); + fvVectorDnat.push_back(v); + fvVectorSnat.push_back(v); + + /* Add it to APPL_DB */ + m_appNaptTableProducer.set(appKeyDnat, fvVectorDnat); + m_appNaptTableProducer.set(appKeySnat, fvVectorSnat); + + SWSS_LOG_INFO("Added Static NAPT %s to APPL_DB", key.c_str()); + + /* Delete any conntrack entry if exists */ + deleteConntrackSingleNaptEntry(key); + + /* Add a dummy conntrack entry for Static NAPT entry */ + addConntrackSingleNaptEntry(key); + + /* Add Static NAPT iptables rule */ + if (!setStaticNaptIptablesRules(INSERT, interface, prototype, keys[0], keys[2], + m_staticNaptEntry[key].local_ip, m_staticNaptEntry[key].local_port, + m_staticNaptEntry[key].nat_type)) + { + SWSS_LOG_ERROR("Failed to add Static NAPT iptables rules for %s", key.c_str()); + } + else + { + SWSS_LOG_INFO("Added Static NAPT iptables rules for %s", key.c_str()); + } +} + +/* To add Static Twice NAPT entry based on Static Key if all valid conditions are met */ +void NatMgr::addStaticTwiceNaptEntry(const string &key) +{ + /* Example: + * Entry is STATIC_NAPT|65.55.42.1|TCP|1024 and key is 65.55.42.1|TCP|1024 + */ + + string interface = EMPTY_STRING, prototype = EMPTY_STRING; + vector keys = tokenize(key, config_db_key_delimiter); + string src, translated_src, dest, translated_dest; + string src_port, translated_src_port, dest_port, translated_dest_port; + bool isEntryAdded = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s Twice NAT entry addition to APPL_DB", key.c_str()); + return; + } + + if (keys[1] == to_upper(IP_PROTOCOL_UDP)) + { + prototype = IP_PROTOCOL_UDP; + } + else if (keys[1] == to_upper(IP_PROTOCOL_TCP)) + { + prototype = IP_PROTOCOL_TCP; + } + + /* Get all the Static NAPT entries */ + for (auto it = m_staticNaptEntry.begin(); it != m_staticNaptEntry.end(); it++) + { + vector fvVector, reversefvVector; + vector entry_keys = tokenize((*it).first, config_db_key_delimiter); + + /* Check for other entries, otherwise continue */ + if ((*it).first == key) + { + continue; + } + + /* Check for both protocols are same, otherwise continue */ + if (entry_keys[1] != keys[1]) + { + continue; + } + + /* Check the twice_nat_id is matched, otherwise continue */ + if ((*it).second.twice_nat_id != m_staticNaptEntry[key].twice_nat_id) + { + continue; + } + + /* Check the twice NAT is not added, otherwise continue */ + if ((*it).second.twice_nat_added or m_staticNaptEntry[key].twice_nat_added) + { + continue; + } + + /* Check interface is assigned, means Entry is already added, otherwise continue */ + if (((*it).second.interface == NONE_STRING) or (m_staticNaptEntry[key].interface == NONE_STRING)) + { + continue; + } + + /* Check the nat type is different, otherwise continue */ + if ((*it).second.nat_type == m_staticNaptEntry[key].nat_type) + { + continue; + } + + if (((*it).second.nat_type == DNAT_NAT_TYPE) and + (m_staticNaptEntry[key].nat_type == SNAT_NAT_TYPE)) + { + src = keys[0]; + src_port = keys[2]; + dest = entry_keys[0]; + dest_port = entry_keys[2]; + translated_src = m_staticNaptEntry[key].local_ip; + translated_src_port = m_staticNaptEntry[key].local_port; + translated_dest = (*it).second.local_ip; + translated_dest_port = (*it).second.local_port; + interface = (*it).second.interface; + } + else if (((*it).second.nat_type == SNAT_NAT_TYPE) and + (m_staticNaptEntry[key].nat_type == DNAT_NAT_TYPE)) + { + src = entry_keys[0]; + src_port = entry_keys[2]; + dest = keys[0]; + dest_port = keys[2]; + translated_src = (*it).second.local_ip; + translated_src_port = (*it).second.local_port; + translated_dest = m_staticNaptEntry[key].local_ip; + translated_dest_port = m_staticNaptEntry[key].local_port; + interface = m_staticNaptEntry[key].interface; + } + + /* Create APPL_DB key and it's values */ + string appKey = keys[1] + ":" + src + ":" + src_port + ":" + dest + ":" + dest_port; + string reverseAppKey = keys[1] + ":" + translated_dest + ":" + translated_dest_port + ":" + translated_src + ":" + translated_src_port; + + FieldValueTuple p(TRANSLATED_SRC_IP, translated_src); + fvVector.push_back(p); + FieldValueTuple q(TRANSLATED_SRC_L4_PORT, translated_src_port); + fvVector.push_back(q); + FieldValueTuple r(TRANSLATED_DST_IP, translated_dest); + fvVector.push_back(r); + FieldValueTuple s(TRANSLATED_DST_L4_PORT, translated_dest_port); + fvVector.push_back(s); + FieldValueTuple t(ENTRY_TYPE, STATIC_ENTRY_TYPE); + fvVector.push_back(t); + + FieldValueTuple p1(TRANSLATED_SRC_IP, dest); + reversefvVector.push_back(p1); + FieldValueTuple q1(TRANSLATED_SRC_L4_PORT, dest_port); + reversefvVector.push_back(q1); + FieldValueTuple r1(TRANSLATED_DST_IP, src); + reversefvVector.push_back(r1); + FieldValueTuple s1(TRANSLATED_DST_L4_PORT, src_port); + reversefvVector.push_back(s1); + reversefvVector.push_back(t); + + (*it).second.twice_nat_added = true; + m_staticNaptEntry[key].twice_nat_added = true; + + /* Add it to APPL_DB */ + m_appTwiceNaptTableProducer.set(appKey, fvVector); + m_appTwiceNaptTableProducer.set(reverseAppKey, reversefvVector); + + SWSS_LOG_INFO("Added Static Twice NAPT for %s and %s to APPL_DB", key.c_str(), (*it).first.c_str()); + + if (((*it).second.nat_type == DNAT_NAT_TYPE) and + (m_staticNaptEntry[key].nat_type == SNAT_NAT_TYPE)) + { + /* Delete any conntrack entry if exists */ + deleteConntrackTwiceNaptEntry(key, (*it).first); + + /* Add a dummy conntrack entry for Static Twice NAPT entry */ + addConntrackTwiceNaptEntry(key, (*it).first); + } + else if (((*it).second.nat_type == SNAT_NAT_TYPE) and + (m_staticNaptEntry[key].nat_type == DNAT_NAT_TYPE)) + { + /* Delete any conntrack entry if exists */ + deleteConntrackTwiceNaptEntry((*it).first, key); + + /* Add a dummy conntrack entry for Static Twice NAPT entry */ + addConntrackTwiceNaptEntry((*it).first, key); + } + + /* Add Static NAPT iptables rule */ + if (!setStaticTwiceNaptIptablesRules(INSERT, interface, prototype, src, src_port, translated_src, translated_src_port, + dest, dest_port, translated_dest, translated_dest_port)) + { + SWSS_LOG_ERROR("Failed to add Static Twice NAT iptables rules for %s and %s", key.c_str(), (*it).first.c_str()); + } + else + { + isEntryAdded = true; + SWSS_LOG_INFO("Added Static Twice NAT iptables rules for %s and %s", key.c_str(), (*it).first.c_str()); + } + break; + } + + if (!isEntryAdded) + { + SWSS_LOG_INFO("No Static Twice NAPT entries to add"); + } + else + { + return; + } + + /* Get all Binding Info */ + for (auto it = m_natBindingInfo.begin(); it != m_natBindingInfo.end(); it++) + { + string pool_name = (*it).second.pool_name; + string port_range = EMPTY_STRING; + + /* Check the pool is present in cache, otherwise continue */ + if (m_natPoolInfo.find(pool_name) == m_natPoolInfo.end()) + { + continue; + } + + /* Check the twice_nat_id is matched, otherwise continue */ + if ((*it).second.twice_nat_id != m_staticNaptEntry[key].twice_nat_id) + { + continue; + } + + /* Check the twice NAT is not added, otherwise continue */ + if ((*it).second.twice_nat_added or m_staticNaptEntry[key].twice_nat_added) + { + continue; + } + + /* Check interface is assigned, means Entry is already added, otherwise continue */ + if (((*it).second.pool_interface == NONE_STRING) or (m_staticNaptEntry[key].interface == NONE_STRING)) + { + continue; + } + + /* Check the nat type is same, otherwise continue */ + if ((*it).second.nat_type != m_staticNaptEntry[key].nat_type) + { + continue; + } + + /* Check the port_range is present, otherwise continue */ + port_range = m_natPoolInfo[pool_name].port_range; + if (port_range.empty() or (port_range == "NULL")) + { + continue; + } + + vector nat_ip = tokenize(m_natPoolInfo[pool_name].ip_range, range_specifier); + + /* Check the pool is valid */ + if (nat_ip.empty()) + { + SWSS_LOG_INFO("NAT pool %s is not valid, skipping dynamic twice nat iptables rules addition for %s", pool_name.c_str(), (*it).first.c_str()); + continue; + } + + (*it).second.twice_nat_added = true; + (*it).second.static_key = key; + m_staticNaptEntry[key].twice_nat_added = true; + m_staticNaptEntry[key].binding_key = (*it).first; + isEntryAdded = true; + + setDynamicAllForwardOrAclbasedRules(ADD, (*it).second.pool_interface, m_natPoolInfo[pool_name].ip_range, + m_natPoolInfo[pool_name].port_range, (*it).second.acl_name, + (*it).first); + + break; + } + + if (!isEntryAdded) + { + SWSS_LOG_INFO("No Static-Dynamic Twice NAPT entries to add"); + } +} + +/* To delete Static Single NAT entry based on Static Key if all valid conditions are met */ +void NatMgr::removeStaticSingleNatEntry(const string &key) +{ + /* Example: + * Entry is STATIC_NAT|65.55.42.1 and key is 65.55.42.1 + */ + + string interface = EMPTY_STRING; + vector fvVectorDnat, fvVectorSnat; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s NAT entry deletion", key.c_str()); + return; + } + + /* Get the matching Ip interface for dnat type, otherwise return */ + if ((m_staticNatEntry[key].nat_type == DNAT_NAT_TYPE) and (!getIpEnabledIntf(key, interface))) + { + SWSS_LOG_INFO("L3 Interface is not yet enabled for %s, skipping NAT entry deletion", key.c_str()); + return; + } + + /* Check for the key interface is matching with the saved one, otherwise return */ + if (m_staticNatEntry[key].interface != interface) + { + SWSS_LOG_INFO("Interface is not matching for %s, skipping NAT entry deletion", key.c_str()); + m_staticNatEntry.erase(key); + return; + } + + /* Create the APPL_DB key and it's values */ + string appKeyDnat = EMPTY_STRING, appKeySnat = EMPTY_STRING; + + if (m_staticNatEntry[key].nat_type == DNAT_NAT_TYPE) + { + appKeyDnat += key; + appKeySnat += m_staticNatEntry[key].local_ip; + } + else if (m_staticNatEntry[key].nat_type == SNAT_NAT_TYPE) + { + appKeySnat += key; + appKeyDnat += m_staticNatEntry[key].local_ip; + } + + /* Delete conntrack entry */ + deleteConntrackSingleNatEntry(key); + + /* Delete it from APPL_DB */ + m_appNatTableProducer.del(appKeyDnat); + m_appNatTableProducer.del(appKeySnat); + + SWSS_LOG_INFO("Deleted Static NAT %s from APPL_DB", key.c_str()); + + /* Remove Static NAT iptables rule */ + if (!setStaticNatIptablesRules(DELETE, interface, key, m_staticNatEntry[key].local_ip, m_staticNatEntry[key].nat_type)) + { + SWSS_LOG_ERROR("Failed to delete Static NAT iptables rules for %s", key.c_str()); + } + else + { + SWSS_LOG_INFO("Deleted Static NAT iptables rules for %s", key.c_str()); + } + + m_staticNatEntry[key].interface = NONE_STRING; + + /* Add any static NAPT conflict entry if present */ + for (auto it = m_staticNaptEntry.begin(); it != m_staticNaptEntry.end(); it++) + { + vector keys = tokenize((*it).first, config_db_key_delimiter); + if ((keys[0] == key) and ((*it).second.local_ip == m_staticNatEntry[key].local_ip) and + ((*it).second.interface == NONE_STRING)) + { + /* Add the new Static NAPT entry */ + SWSS_LOG_INFO("Adding the Static NAPT entry for %s", key.c_str()); + addStaticNaptEntry((*it).first); + break; + } + } +} + +/* To delete Static Twice NAT entry based on Static Key if all valid conditions are met */ +void NatMgr::removeStaticTwiceNatEntry(const string &key) +{ + /* Example: + * Entry is STATIC_NAT|65.55.42.1 and key is 65.55.42.1 + */ + + string interface = EMPTY_STRING; + string src, translated_src, dest, translated_dest; + bool isEntryDeleted = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s Twice NAT entry deletion from APPL_DB", key.c_str()); + return; + } + + /* Get all the Static NAT entries */ + for (auto it = m_staticNatEntry.begin(); it != m_staticNatEntry.end(); it++) + { + /* Check for other entries, otherwise continue */ + if ((*it).first == key) + { + continue; + } + + /* Check the twice_nat_id is matched, otherwise continue */ + if ((*it).second.twice_nat_id != m_staticNatEntry[key].twice_nat_id) + { + continue; + } + + /* Check the twice NAT is not added, otherwise continue */ + if ((!(*it).second.twice_nat_added) or (!m_staticNatEntry[key].twice_nat_added)) + { + continue; + } + + /* Check interface is not assigned, means Entry is not added, otherwise continue */ + if (((*it).second.interface == NONE_STRING) or (m_staticNatEntry[key].interface == NONE_STRING)) + { + continue; + } + + /* Check the nat type is different, otherwise continue */ + if ((*it).second.nat_type == m_staticNatEntry[key].nat_type) + { + continue; + } + + if (((*it).second.nat_type == DNAT_NAT_TYPE) and + (m_staticNatEntry[key].nat_type == SNAT_NAT_TYPE)) + { + src = key; + dest = (*it).first; + translated_src = m_staticNatEntry[key].local_ip; + translated_dest = (*it).second.local_ip; + interface = (*it).second.interface; + } + else if (((*it).second.nat_type == SNAT_NAT_TYPE) and + (m_staticNatEntry[key].nat_type == DNAT_NAT_TYPE)) + { + src = (*it).first; + dest = key; + translated_src = (*it).second.local_ip; + translated_dest = m_staticNatEntry[key].local_ip; + interface = m_staticNatEntry[key].interface; + } + + string appKey = src + ":" + dest; + string reverseAppKey = translated_dest + ":" + translated_src; + + if (((*it).second.nat_type == DNAT_NAT_TYPE) and + (m_staticNatEntry[key].nat_type == SNAT_NAT_TYPE)) + { + /* Delete any conntrack entry */ + deleteConntrackTwiceNatEntry(key, (*it).first); + } + else if (((*it).second.nat_type == SNAT_NAT_TYPE) and + (m_staticNatEntry[key].nat_type == DNAT_NAT_TYPE)) + { + /* Delete any conntrack entry */ + deleteConntrackTwiceNatEntry((*it).first, key); + } + + /* Delete it from APPL_DB */ + m_appTwiceNatTableProducer.del(appKey); + m_appTwiceNatTableProducer.del(reverseAppKey); + + (*it).second.twice_nat_added = false; + m_staticNatEntry[key].twice_nat_added = false; + + SWSS_LOG_INFO("Deleted Static Twice NAT for %s and %s from APPL_DB", key.c_str(), (*it).first.c_str()); + + /* Delete Static NAT iptables rule */ + if (!setStaticTwiceNatIptablesRules(DELETE, interface, src, translated_src, dest, translated_dest)) + { + SWSS_LOG_ERROR("Failed to delete Static Twice NAT iptables rules for %s and %s", key.c_str(), (*it).first.c_str()); + } + else + { + SWSS_LOG_INFO("Deleted Static Twice NAT iptables rules for %s and %s", key.c_str(), (*it).first.c_str()); + isEntryDeleted = true; + } + + m_staticNatEntry[key].interface = NONE_STRING; + + return; + } + + if (!isEntryDeleted) + { + SWSS_LOG_INFO("No Static Twice NAT entries to delete"); + } + else + { + return; + } + + /* Get all Binding Info */ + for (auto it = m_natBindingInfo.begin(); it != m_natBindingInfo.end(); it++) + { + string port_range = EMPTY_STRING; + string pool_name = (*it).second.pool_name; + string acls_name = (*it).second.acl_name; + + /* Check the pool is present in cache, otherwise continue */ + if (m_natPoolInfo.find(pool_name) == m_natPoolInfo.end()) + { + continue; + } + + /* Check the twice_nat_id is matched, otherwise continue */ + if ((*it).second.twice_nat_id != m_staticNatEntry[key].twice_nat_id) + { + continue; + } + + /* Check the twice NAT is added, otherwise continue */ + if ((!(*it).second.twice_nat_added) or (!m_staticNatEntry[key].twice_nat_added)) + { + continue; + } + + /* Check interface is assigned, otherwise continue */ + if (((*it).second.pool_interface == NONE_STRING) or (m_staticNatEntry[key].interface == NONE_STRING)) + { + continue; + } + + /* Check the nat type is same, otherwise continue */ + if ((*it).second.nat_type != m_staticNatEntry[key].nat_type) + { + continue; + } + + /* Check the port_range is not present, otherwise continue */ + port_range = m_natPoolInfo[pool_name].port_range; + if (!port_range.empty() and (port_range != "NULL")) + { + continue; + } + + /* Check the key is matching, otherwise continue */ + if (m_staticNatEntry[key].binding_key != (*it).first) + { + continue; + } + + vector nat_ip = tokenize(m_natPoolInfo[pool_name].ip_range, range_specifier); + + /* Check the pool is valid */ + if (nat_ip.empty()) + { + SWSS_LOG_INFO("NAT pool %s is not valid, skipping dynamic twice nat iptables rules deletion for %s", pool_name.c_str(), (*it).first.c_str()); + continue; + } + + /* Delete Dynamic rules */ + setDynamicAllForwardOrAclbasedRules(DELETE, (*it).second.pool_interface, m_natPoolInfo[pool_name].ip_range, + m_natPoolInfo[pool_name].port_range, acls_name, (*it).first); + + (*it).second.twice_nat_added = false; + (*it).second.static_key = EMPTY_STRING; + m_staticNatEntry[key].twice_nat_added = false; + m_staticNatEntry[key].binding_key = EMPTY_STRING; + isEntryDeleted = true; + break; + } + + if (!isEntryDeleted) + { + SWSS_LOG_INFO("No Static-Dynamic Twice NAT entries to delete"); + } +} + +/* To delete Static Single NAPT entry based on Static Key if all valid conditions are met */ +void NatMgr::removeStaticSingleNaptEntry(const string &key) +{ + /* Example: + * Entry is STATIC_NAPT|65.55.42.1|TCP|1024 and key is 65.55.42.1|TCP|1024 + */ + + string interface = EMPTY_STRING, prototype = EMPTY_STRING; + vector keys = tokenize(key, config_db_key_delimiter); + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s NAPT entry deletion", key.c_str()); + return; + } + + /* Get the matching Ip interface for dnat type, otherwise return */ + if ((m_staticNaptEntry[key].nat_type == DNAT_NAT_TYPE) and (!getIpEnabledIntf(keys[0], interface))) + { + SWSS_LOG_INFO("L3 Interface is not yet enabled for %s, skipping NAPT entry deletion", key.c_str()); + return; + } + + /* Check for the key interface is matching with the save one, otherwise return */ + if (m_staticNaptEntry[key].interface != interface) + { + SWSS_LOG_INFO("Interface is not matching for %s, skipping NAPT entry deletion", key.c_str()); + return; + } + + if (keys[1] == to_upper(IP_PROTOCOL_UDP)) + { + prototype = IP_PROTOCOL_UDP; + } + else if (keys[1] == to_upper(IP_PROTOCOL_TCP)) + { + prototype = IP_PROTOCOL_TCP; + } + + /* Create the APPL_DB key and it's values */ + string appKeyDnat = EMPTY_STRING, appKeySnat = EMPTY_STRING; + + if (m_staticNaptEntry[key].nat_type == DNAT_NAT_TYPE) + { + appKeyDnat += (keys[1] + DEFAULT_KEY_SEPARATOR + keys[0] + DEFAULT_KEY_SEPARATOR + keys[2]); + appKeySnat += (keys[1] + DEFAULT_KEY_SEPARATOR + m_staticNaptEntry[key].local_ip + DEFAULT_KEY_SEPARATOR + m_staticNaptEntry[key].local_port); + } + else if (m_staticNaptEntry[key].nat_type == SNAT_NAT_TYPE) + { + appKeySnat += (keys[1] + DEFAULT_KEY_SEPARATOR + keys[0] + DEFAULT_KEY_SEPARATOR + keys[2]); + appKeyDnat += (keys[1] + DEFAULT_KEY_SEPARATOR + m_staticNaptEntry[key].local_ip + DEFAULT_KEY_SEPARATOR + m_staticNaptEntry[key].local_port); + } + + /* Delete conntrack entry */ + deleteConntrackSingleNaptEntry(key); + + /* Delete it from APPL_DB */ + m_appNaptTableProducer.del(appKeyDnat); + m_appNaptTableProducer.del(appKeySnat); + + SWSS_LOG_INFO("Deleted Static NAPT %s from APPL_DB", key.c_str()); + + /* Remove Static NAPT iptables rule */ + if (!setStaticNaptIptablesRules(DELETE, interface, prototype, keys[0], keys[2], + m_staticNaptEntry[key].local_ip, m_staticNaptEntry[key].local_port, + m_staticNaptEntry[key].nat_type)) + { + SWSS_LOG_ERROR("Failed to delete Static NAPT iptables rules for %s", key.c_str()); + } + else + { + SWSS_LOG_INFO("Deleted Static NAPT iptables rules for %s", key.c_str()); + } + + m_staticNaptEntry[key].interface = NONE_STRING; + + /* Add any static NAT conflict entry if present */ + for (auto it = m_staticNatEntry.begin(); it != m_staticNatEntry.end(); it++) + { + if (((*it).first == keys[0]) and ((*it).second.local_ip == m_staticNaptEntry[key].local_ip) and + ((*it).second.interface == NONE_STRING)) + { + /* Add the new Static NAT entry */ + SWSS_LOG_INFO("Adding the Static NAT entry for %s", key.c_str()); + addStaticNatEntry(keys[0]); + break; + } + } +} + +/* To delete Static Twice NAPT entry based on Static Key if all valid conditions are met */ +void NatMgr::removeStaticTwiceNaptEntry(const string &key) +{ + /* Example: + * Entry is STATIC_NAPT|65.55.42.1|TCP|1024 and key is 65.55.42.1|TCP|1024 + */ + + string interface = EMPTY_STRING, prototype = EMPTY_STRING; + vector keys = tokenize(key, config_db_key_delimiter); + string src, translated_src, dest, translated_dest; + string src_port, translated_src_port, dest_port, translated_dest_port; + bool isEntryDeleted = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s Twice NAPT entry deletion from APPL_DB", key.c_str()); + return; + } + + if (keys[1] == to_upper(IP_PROTOCOL_UDP)) + { + prototype = IP_PROTOCOL_UDP; + } + else if (keys[1] == to_upper(IP_PROTOCOL_TCP)) + { + prototype = IP_PROTOCOL_TCP; + } + + /* Get all the Static NAPT entries */ + for (auto it = m_staticNaptEntry.begin(); it != m_staticNaptEntry.end(); it++) + { + vector entry_keys = tokenize((*it).first, config_db_key_delimiter); + + /* Check for other entries, otherwise continue */ + if ((*it).first == key) + { + continue; + } + + /* Check for both protocols are same, otherwise continue */ + if (entry_keys[1] != keys[1]) + { + continue; + } + + /* Check the twice_nat_id is matched, otherwise continue */ + if ((*it).second.twice_nat_id != m_staticNaptEntry[key].twice_nat_id) + { + continue; + } + + /* Check the twice NAT is not added, otherwise continue */ + if ((!(*it).second.twice_nat_added) or (!m_staticNaptEntry[key].twice_nat_added)) + { + continue; + } + + /* Check interface is not assigned, means Entry is not added, otherwise continue */ + if (((*it).second.interface == NONE_STRING) or (m_staticNaptEntry[key].interface == NONE_STRING)) + { + continue; + } + + /* Check the nat type is different, otherwise continue */ + if ((*it).second.nat_type == m_staticNaptEntry[key].nat_type) + { + continue; + } + + if (((*it).second.nat_type == DNAT_NAT_TYPE) and + (m_staticNaptEntry[key].nat_type == SNAT_NAT_TYPE)) + { + src = keys[0]; + src_port = keys[2]; + dest = entry_keys[0]; + dest_port = entry_keys[2]; + translated_src = m_staticNaptEntry[key].local_ip; + translated_src_port = m_staticNaptEntry[key].local_port; + translated_dest = (*it).second.local_ip; + translated_dest_port = (*it).second.local_port; + interface = (*it).second.interface; + } + else if (((*it).second.nat_type == SNAT_NAT_TYPE) and + (m_staticNaptEntry[key].nat_type == DNAT_NAT_TYPE)) + { + src = entry_keys[0]; + src_port = entry_keys[2]; + dest = keys[0]; + dest_port = keys[2]; + translated_src = (*it).second.local_ip; + translated_src_port = (*it).second.local_port; + translated_dest = m_staticNaptEntry[key].local_ip; + translated_dest_port = m_staticNaptEntry[key].local_port; + interface = m_staticNaptEntry[key].interface; + } + + string appKey = keys[1] + ":" + src + ":" + src_port + ":" + dest + ":" + dest_port; + string reverseAppKey = keys[1] + ":" + translated_dest + ":" + translated_dest_port + ":" + translated_src + ":" + translated_src_port; + + if (((*it).second.nat_type == DNAT_NAT_TYPE) and + (m_staticNaptEntry[key].nat_type == SNAT_NAT_TYPE)) + { + /* Delete any conntrack entry */ + deleteConntrackTwiceNaptEntry(key, (*it).first); + } + else if (((*it).second.nat_type == SNAT_NAT_TYPE) and + (m_staticNaptEntry[key].nat_type == DNAT_NAT_TYPE)) + { + /* Delete any conntrack entry */ + deleteConntrackTwiceNaptEntry((*it).first, key); + } + + /* Delete it from APPL_DB */ + m_appTwiceNaptTableProducer.del(appKey); + m_appTwiceNaptTableProducer.del(reverseAppKey); + + (*it).second.twice_nat_added = false; + m_staticNaptEntry[key].twice_nat_added = false; + + SWSS_LOG_INFO("Deleted Static Twice NAPT for %s and %s from APPL_DB", key.c_str(), (*it).first.c_str()); + + /* Delete Static NAPT iptables rule */ + if (!setStaticTwiceNaptIptablesRules(DELETE, interface, prototype, src, src_port, translated_src, translated_src_port, + dest, dest_port, translated_dest, translated_dest_port)) + { + SWSS_LOG_ERROR("Failed to delete Static Twice NAPT iptables rules for %s and %s", key.c_str(), (*it).first.c_str()); + } + else + { + SWSS_LOG_INFO("Deleted Static Twice NAPT iptables rules for %s and %s", key.c_str(), (*it).first.c_str()); + isEntryDeleted = true; + } + + m_staticNaptEntry[key].interface = NONE_STRING; + + return; + } + + if (!isEntryDeleted) + { + SWSS_LOG_INFO("No Static Twice NAPT entries to delete"); + } + else + { + return; + } + + /* Get all Binding Info */ + for (auto it = m_natBindingInfo.begin(); it != m_natBindingInfo.end(); it++) + { + string port_range = EMPTY_STRING; + string pool_name = (*it).second.pool_name; + string acls_name = (*it).second.acl_name; + + /* Check the pool is present in cache, otherwise continue */ + if (m_natPoolInfo.find(pool_name) == m_natPoolInfo.end()) + { + continue; + } + + /* Check the twice_nat_id is matched, otherwise continue */ + if ((*it).second.twice_nat_id != m_staticNaptEntry[key].twice_nat_id) + { + continue; + } + + /* Check the twice NAT is added, otherwise continue */ + if ((!(*it).second.twice_nat_added) or (!m_staticNaptEntry[key].twice_nat_added)) + { + continue; + } + + /* Check interface is assigned, otherwise continue */ + if (((*it).second.pool_interface == NONE_STRING) or (m_staticNaptEntry[key].interface == NONE_STRING)) + { + continue; + } + + /* Check the nat type is same, otherwise continue */ + if ((*it).second.nat_type != m_staticNaptEntry[key].nat_type) + { + continue; + } + + /* Check the port_range is present, otherwise continue */ + port_range = m_natPoolInfo[pool_name].port_range; + if (port_range.empty() or (port_range == "NULL")) + { + continue; + } + + /* Check the key is matching, otherwise continue */ + if (m_staticNaptEntry[key].binding_key != (*it).first) + { + continue; + } + + vector nat_ip = tokenize(m_natPoolInfo[pool_name].ip_range, range_specifier); + + /* Check the pool is valid */ + if (nat_ip.empty()) + { + SWSS_LOG_INFO("NAT pool %s is not valid, skipping dynamic twice nat iptables rules deletion for %s", pool_name.c_str(), (*it).first.c_str()); + continue; + } + + /* Delete Dynamic rules */ + setDynamicAllForwardOrAclbasedRules(DELETE, (*it).second.pool_interface, m_natPoolInfo[pool_name].ip_range, + m_natPoolInfo[pool_name].port_range, acls_name, (*it).first); + + (*it).second.twice_nat_added = false; + (*it).second.static_key = EMPTY_STRING; + m_staticNatEntry[key].twice_nat_added = false; + m_staticNatEntry[key].binding_key = EMPTY_STRING; + isEntryDeleted = true; + break; + } + + if (!isEntryDeleted) + { + SWSS_LOG_INFO("No Static-Dynamic Twice NAPT entries to delete"); + } +} + +/* To add Static NAT Iptables based on L3 Interface if all valid conditions are met */ +void NatMgr::addStaticNatIptables(const string port) +{ + /* Example: + * Port is Ethernet1 + */ + bool isRulesAdded = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping NAT Iptables addition"); + return; + } + + /* Get all the Static NAT entries */ + for (auto it = m_staticNatEntry.begin(); it != m_staticNatEntry.end(); it++) + { + /* Check interface is same as given Port, otherwise continue */ + if ((*it).second.interface != port) + { + continue; + } + + isRulesAdded = true; + + if ((*it).second.twice_nat_id.empty()) + { + /* Add the new Static Single NAT Iptables */ + SWSS_LOG_INFO("Adding the Static Single NAT Iptables for %s", (*it).first.c_str()); + addStaticSingleNatIptables((*it).first); + } + else + { + /* Add the new Static Twice NAT Iptables */ + SWSS_LOG_INFO("Adding the Static Twice NAT Iptables for %s", (*it).first.c_str()); + addStaticTwiceNatIptables((*it).first); + } + } + + if (!isRulesAdded) + { + SWSS_LOG_INFO("No Static NAT iptables rules to add"); + } +} + +/* To add Static Single NAT iptables based on Static Key */ +void NatMgr::addStaticSingleNatIptables(const string &key) +{ + /* Example: + * Entry is STATIC_NAT|65.55.42.1 and key is 65.55.42.1 + */ + + string interface = m_staticNatEntry[key].interface; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s Single NAT Iptables addition", key.c_str()); + return; + } + + /* Add Static NAT iptables rule */ + if (!setStaticNatIptablesRules(INSERT, interface, key, m_staticNatEntry[key].local_ip, m_staticNatEntry[key].nat_type)) + { + SWSS_LOG_ERROR("Failed to add Static NAT iptables rules for %s", key.c_str()); + } + else + { + SWSS_LOG_INFO("Added Static NAT iptables rules for %s", key.c_str()); + } +} + +/* To add Static Twice NAT Iptables based on Static Key if all valid conditions are met */ +void NatMgr::addStaticTwiceNatIptables(const string &key) +{ + /* Example: + * Entry is STATIC_NAT|65.55.42.1 and key is 65.55.42.1 + */ + + string interface = EMPTY_STRING; + string src, translated_src, dest, translated_dest; + string src_port, translated_src_port, dest_port, translated_dest_port; + bool isRulesAdded = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s Twice NAT entry addition to APPL_DB", key.c_str()); + return; + } + + /* Get all the Static NAT entries */ + for (auto it = m_staticNatEntry.begin(); it != m_staticNatEntry.end(); it++) + { + /* Check for other entries, otherwise continue */ + if ((*it).first == key) + { + continue; + } + + /* Check the twice_nat_id is matched, otherwise continue */ + if ((*it).second.twice_nat_id != m_staticNatEntry[key].twice_nat_id) + { + continue; + } + + /* Check the twice NAT is added, otherwise continue */ + if (!(*it).second.twice_nat_added or !m_staticNatEntry[key].twice_nat_added) + { + continue; + } + + /* Check the nat type is different, otherwise continue */ + if ((*it).second.nat_type == m_staticNatEntry[key].nat_type) + { + continue; + } + + if (((*it).second.nat_type == DNAT_NAT_TYPE) and + (m_staticNatEntry[key].nat_type == SNAT_NAT_TYPE)) + { + src = key; + dest = (*it).first; + translated_src = m_staticNatEntry[key].local_ip; + translated_dest = (*it).second.local_ip; + interface = (*it).second.interface; + } + else if (((*it).second.nat_type == SNAT_NAT_TYPE) and + (m_staticNatEntry[key].nat_type == DNAT_NAT_TYPE)) + { + src = (*it).first; + dest = key; + translated_src = (*it).second.local_ip; + translated_dest = m_staticNatEntry[key].local_ip; + interface = m_staticNatEntry[key].interface; + } + + /* Add Static NAT iptables rule */ + if (!setStaticTwiceNatIptablesRules(INSERT, interface, src, translated_src, dest, translated_dest)) + { + SWSS_LOG_ERROR("Failed to add Static Twice NAT iptables rules for %s and %s", key.c_str(), (*it).first.c_str()); + } + else + { + isRulesAdded = true; + SWSS_LOG_INFO("Added Static Twice NAT iptables rules for %s and %s", key.c_str(), (*it).first.c_str()); + } + break; + } + + if (!isRulesAdded) + { + SWSS_LOG_INFO("No Static Twice NAT iptables rules to add"); + } + else + { + return; + } + + /* Get all Binding Info */ + for (auto it = m_natBindingInfo.begin(); it != m_natBindingInfo.end(); it++) + { + string pool_name = (*it).second.pool_name; + string port_range = EMPTY_STRING; + + /* Check the pool is present in cache, otherwise continue */ + if (m_natPoolInfo.find(pool_name) == m_natPoolInfo.end()) + { + continue; + } + + /* Check the twice_nat_id is matched, otherwise continue */ + if ((*it).second.twice_nat_id != m_staticNatEntry[key].twice_nat_id) + { + continue; + } + + /* Check the twice NAT is added, otherwise continue */ + if (!(*it).second.twice_nat_added or !m_staticNatEntry[key].twice_nat_added) + { + continue; + } + + /* Check the nat type is same, otherwise continue */ + if ((*it).second.nat_type != m_staticNatEntry[key].nat_type) + { + continue; + } + + /* Check the port_range is not present, otherwise continue */ + port_range = m_natPoolInfo[pool_name].port_range; + if (!port_range.empty() and (port_range != "NULL")) + { + continue; + } + + vector nat_ip = tokenize(m_natPoolInfo[pool_name].ip_range, range_specifier); + + /* Check the pool is valid */ + if (nat_ip.empty()) + { + SWSS_LOG_INFO("NAT pool %s is not valid, skipping dynamic twice nat iptables rules addition for %s", pool_name.c_str(), (*it).first.c_str()); + continue; + } + + isRulesAdded = true; + setDynamicAllForwardOrAclbasedRules(ADD, (*it).second.pool_interface, m_natPoolInfo[pool_name].ip_range, + m_natPoolInfo[pool_name].port_range, (*it).second.acl_name, + (*it).first); + break; + } + + if (!isRulesAdded) + { + SWSS_LOG_INFO("No Static-Dynamic Twice NAT iptables rules to add"); + } +} + +/* To add Static NAPT Iptables based on L3 Interface if all valid conditions are met */ +void NatMgr::addStaticNaptIptables(const string port) +{ + /* Example: + * Port is Ethernet1 + */ + + bool isRulesAdded = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping NAPT Iptables addition"); + return; + } + + /* Get all the Static NAPT entries */ + for (auto it = m_staticNaptEntry.begin(); it != m_staticNaptEntry.end(); it++) + { + /* Check interface is same as given Port, otherwise continue */ + if ((*it).second.interface != port) + { + continue; + } + + isRulesAdded = true; + + if ((*it).second.twice_nat_id.empty()) + { + /* Add the new Static Single NAPT iptables */ + SWSS_LOG_INFO("Adding the Static Single NAPT iptables for %s", (*it).first.c_str()); + addStaticSingleNaptIptables((*it).first); + } + else + { + /* Add the new Static Twice NAPT iptables */ + SWSS_LOG_INFO("Adding the Static Twice NAPT iptables for %s", (*it).first.c_str()); + addStaticTwiceNaptIptables((*it).first); + } + } + + if (!isRulesAdded) + { + SWSS_LOG_INFO("No Static NAPT iptables rules to add"); + } +} + +/* To add Static Single NAPT Iptables based on Static Key */ +void NatMgr::addStaticSingleNaptIptables(const string &key) +{ + /* Example: + * Entry is STATIC_NAPT|65.55.42.1|TCP|1024 and key is 65.55.42.1|TCP|1024 + */ + + string prototype = EMPTY_STRING, interface = m_staticNaptEntry[key].interface;; + vector keys = tokenize(key, config_db_key_delimiter); + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s Twice NAT Iptables addition ", key.c_str()); + return; + } + + if (keys[1] == to_upper(IP_PROTOCOL_UDP)) + { + prototype = IP_PROTOCOL_UDP; + } + else if (keys[1] == to_upper(IP_PROTOCOL_TCP)) + { + prototype = IP_PROTOCOL_TCP; + } + + /* Add Static NAPT iptables rule */ + if (!setStaticNaptIptablesRules(INSERT, interface, prototype, keys[0], keys[2], + m_staticNaptEntry[key].local_ip, m_staticNaptEntry[key].local_port, + m_staticNaptEntry[key].nat_type)) + { + SWSS_LOG_ERROR("Failed to add Static NAPT iptables rules for %s", key.c_str()); + } + else + { + SWSS_LOG_INFO("Added Static NAPT iptables rules for %s", key.c_str()); + } +} + +/* To add Static Twice NAPT Iptables based on Static Key if all valid conditions are met */ +void NatMgr::addStaticTwiceNaptIptables(const string &key) +{ + /* Example: + * Entry is STATIC_NAPT|65.55.42.1|TCP|1024 and key is 65.55.42.1|TCP|1024 + */ + + string interface = EMPTY_STRING, prototype = EMPTY_STRING; + vector keys = tokenize(key, config_db_key_delimiter); + string src, translated_src, dest, translated_dest; + string src_port, translated_src_port, dest_port, translated_dest_port; + bool isRulesAdded = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s Twice NAT entry addition to APPL_DB", key.c_str()); + return; + } + + if (keys[1] == to_upper(IP_PROTOCOL_UDP)) + { + prototype = IP_PROTOCOL_UDP; + } + else if (keys[1] == to_upper(IP_PROTOCOL_TCP)) + { + prototype = IP_PROTOCOL_TCP; + } + + /* Get all the Static NAPT entries */ + for (auto it = m_staticNaptEntry.begin(); it != m_staticNaptEntry.end(); it++) + { + vector entry_keys = tokenize((*it).first, config_db_key_delimiter); + + /* Check for other entries, otherwise continue */ + if ((*it).first == key) + { + continue; + } + + /* Check for both protocols are same, otherwise continue */ + if (entry_keys[1] != keys[1]) + { + continue; + } + + /* Check the twice_nat_id is matched, otherwise continue */ + if ((*it).second.twice_nat_id != m_staticNaptEntry[key].twice_nat_id) + { + continue; + } + + /* Check the twice NAT is added, otherwise continue */ + if (!(*it).second.twice_nat_added or !m_staticNaptEntry[key].twice_nat_added) + { + continue; + } + + /* Check the nat type is different, otherwise continue */ + if ((*it).second.nat_type == m_staticNaptEntry[key].nat_type) + { + continue; + } + + if (((*it).second.nat_type == DNAT_NAT_TYPE) and + (m_staticNaptEntry[key].nat_type == SNAT_NAT_TYPE)) + { + src = keys[0]; + src_port = keys[2]; + dest = entry_keys[0]; + dest_port = entry_keys[2]; + translated_src = m_staticNaptEntry[key].local_ip; + translated_src_port = m_staticNaptEntry[key].local_port; + translated_dest = (*it).second.local_ip; + translated_dest_port = (*it).second.local_port; + interface = (*it).second.interface; + } + else if (((*it).second.nat_type == SNAT_NAT_TYPE) and + (m_staticNaptEntry[key].nat_type == DNAT_NAT_TYPE)) + { + src = entry_keys[0]; + src_port = entry_keys[2]; + dest = keys[0]; + dest_port = keys[2]; + translated_src = (*it).second.local_ip; + translated_src_port = (*it).second.local_port; + translated_dest = m_staticNaptEntry[key].local_ip; + translated_dest_port = m_staticNaptEntry[key].local_port; + interface = m_staticNaptEntry[key].interface; + } + + /* Add Static NAPT iptables rule */ + if (!setStaticTwiceNaptIptablesRules(INSERT, interface, prototype, src, src_port, translated_src, translated_src_port, + dest, dest_port, translated_dest, translated_dest_port)) + { + SWSS_LOG_ERROR("Failed to add Static Twice NAT iptables rules for %s and %s", key.c_str(), (*it).first.c_str()); + } + else + { + isRulesAdded = true; + SWSS_LOG_INFO("Added Static Twice NAT iptables rules for %s and %s", key.c_str(), (*it).first.c_str()); + } + break; + } + + if (!isRulesAdded) + { + SWSS_LOG_INFO("No Static Twice NAPT iptables rules to add"); + } + else + { + return; + } + + /* Get all Binding Info */ + for (auto it = m_natBindingInfo.begin(); it != m_natBindingInfo.end(); it++) + { + string pool_name = (*it).second.pool_name; + string port_range = EMPTY_STRING; + + /* Check the pool is present in cache, otherwise continue */ + if (m_natPoolInfo.find(pool_name) == m_natPoolInfo.end()) + { + continue; + } + + /* Check the twice_nat_id is matched, otherwise continue */ + if ((*it).second.twice_nat_id != m_staticNaptEntry[key].twice_nat_id) + { + continue; + } + + /* Check the twice NAT is added, otherwise continue */ + if (!(*it).second.twice_nat_added or !m_staticNaptEntry[key].twice_nat_added) + { + continue; + } + + /* Check the nat type is same, otherwise continue */ + if ((*it).second.nat_type != m_staticNaptEntry[key].nat_type) + { + continue; + } + + /* Check the port_range is present, otherwise continue */ + port_range = m_natPoolInfo[pool_name].port_range; + if (port_range.empty() or (port_range == "NULL")) + { + continue; + } + + vector nat_ip = tokenize(m_natPoolInfo[pool_name].ip_range, range_specifier); + + /* Check the pool is valid */ + if (nat_ip.empty()) + { + SWSS_LOG_INFO("NAT pool %s is not valid, skipping dynamic twice nat iptables rules addition for %s", pool_name.c_str(), (*it).first.c_str()); + continue; + } + + isRulesAdded = true; + setDynamicAllForwardOrAclbasedRules(ADD, (*it).second.pool_interface, m_natPoolInfo[pool_name].ip_range, + m_natPoolInfo[pool_name].port_range, (*it).second.acl_name, + (*it).first); + break; + } + + if (!isRulesAdded) + { + SWSS_LOG_INFO("No Static-Dynamic Twice NAPT iptables rules to add"); + } +} + +/* To delete Static NAT Iptables based on L3 Interface if all valid conditions are met */ +void NatMgr::removeStaticNatIptables(const string port) +{ + /* Example: + * Port is Ethernet1 + */ + + bool isRulesDeleted = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping NAT iptables deletion"); + return; + } + + /* Get all the Static NAT entries */ + for (auto it = m_staticNatEntry.begin(); it != m_staticNatEntry.end(); it++) + { + /* Check interface is matching, otherwise continue */ + if ((*it).second.interface != port) + { + continue; + } + + isRulesDeleted = true; + + if ((*it).second.twice_nat_id.empty()) + { + /* Remove the Static Single NAT Iptables */ + SWSS_LOG_INFO("Deleting the Static Single NAT Iptables for %s", (*it).first.c_str()); + removeStaticSingleNatIptables((*it).first); + } + else if (((*it).second.twice_nat_added == true)) + { + /* Remove the Static Twice NAT Iptables */ + SWSS_LOG_INFO("Deleting the Static Twice NAT Iptables for %s", (*it).first.c_str()); + removeStaticTwiceNatIptables((*it).first); + } + } + + if (!isRulesDeleted) + { + SWSS_LOG_INFO("No Static NAT iptables rules to delete"); + } +} + +/* To delete Static Single NAT Iptables based on Static Key if all valid conditions are met */ +void NatMgr::removeStaticSingleNatIptables(const string &key) +{ + /* Example: + * Entry is STATIC_NAT|65.55.42.1 and key is 65.55.42.1 + */ + + string interface = m_staticNatEntry[key].interface; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s Static NAT iptables deletion", key.c_str()); + return; + } + + /* Remove Static NAT iptables rule */ + if (!setStaticNatIptablesRules(DELETE, interface, key, m_staticNatEntry[key].local_ip, m_staticNatEntry[key].nat_type)) + { + SWSS_LOG_ERROR("Failed to delete Static NAT iptables rules for %s", key.c_str()); + } + else + { + SWSS_LOG_INFO("Deleted Static NAT iptables rules for %s", key.c_str()); + } +} + +/* To delete Static Twice NAT Iptables based on Static Key if all valid conditions are met */ +void NatMgr::removeStaticTwiceNatIptables(const string &key) +{ + /* Example: + * Entry is STATIC_NAT|65.55.42.1 and key is 65.55.42.1 + */ + + string interface = EMPTY_STRING; + string src, translated_src, dest, translated_dest; + bool isRulesDeleted = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s Twice NAT iptables deletion", key.c_str()); + return; + } + + /* Get all the Static NAT entries */ + for (auto it = m_staticNatEntry.begin(); it != m_staticNatEntry.end(); it++) + { + /* Check for other entries, otherwise continue */ + if ((*it).first == key) + { + continue; + } + + /* Check the twice_nat_id is matched, otherwise continue */ + if ((*it).second.twice_nat_id != m_staticNatEntry[key].twice_nat_id) + { + continue; + } + + /* Check the twice NAT is added, otherwise continue */ + if ((!(*it).second.twice_nat_added) or (!m_staticNatEntry[key].twice_nat_added)) + { + continue; + } + + /* Check the nat type is different, otherwise continue */ + if ((*it).second.nat_type == m_staticNatEntry[key].nat_type) + { + continue; + } + + if (((*it).second.nat_type == DNAT_NAT_TYPE) and + (m_staticNatEntry[key].nat_type == SNAT_NAT_TYPE)) + { + src = key; + dest = (*it).first; + translated_src = m_staticNatEntry[key].local_ip; + translated_dest = (*it).second.local_ip; + interface = (*it).second.interface; + } + else if (((*it).second.nat_type == SNAT_NAT_TYPE) and + (m_staticNatEntry[key].nat_type == DNAT_NAT_TYPE)) + { + src = (*it).first; + dest = key; + translated_src = (*it).second.local_ip; + translated_dest = m_staticNatEntry[key].local_ip; + interface = m_staticNatEntry[key].interface; + } + + /* Delete Static NAT iptables rule */ + if (!setStaticTwiceNatIptablesRules(DELETE, interface, src, translated_src, dest, translated_dest)) + { + SWSS_LOG_ERROR("Failed to delete Static Twice NAT iptables rules for %s and %s", key.c_str(), (*it).first.c_str()); + } + else + { + isRulesDeleted = true; + SWSS_LOG_INFO("Deleted Static Twice NAT iptables rules for %s and %s", key.c_str(), (*it).first.c_str()); + } + break; + } + + if (!isRulesDeleted) + { + SWSS_LOG_INFO("No Static Twice NAT iptables rules to delete"); + } + else + { + return; + } + + /* Get all Binding Info */ + for (auto it = m_natBindingInfo.begin(); it != m_natBindingInfo.end(); it++) + { + string port_range = EMPTY_STRING; + string pool_name = (*it).second.pool_name; + string acls_name = (*it).second.acl_name; + + /* Check the pool is present in cache, otherwise continue */ + if (m_natPoolInfo.find(pool_name) == m_natPoolInfo.end()) + { + continue; + } + + /* Check the twice_nat_id is matched, otherwise continue */ + if ((*it).second.twice_nat_id != m_staticNatEntry[key].twice_nat_id) + { + continue; + } + + /* Check the twice NAT is added, otherwise continue */ + if ((!(*it).second.twice_nat_added) or (!m_staticNatEntry[key].twice_nat_added)) + { + continue; + } + + /* Check the nat type is same, otherwise continue */ + if ((*it).second.nat_type != m_staticNatEntry[key].nat_type) + { + continue; + } + + /* Check the port_range is not present, otherwise continue */ + port_range = m_natPoolInfo[pool_name].port_range; + if (!port_range.empty() and (port_range != "NULL")) + { + continue; + } + + /* Check the key is matching, otherwise continue */ + if (m_staticNatEntry[key].binding_key != (*it).first) + { + continue; + } + + vector nat_ip = tokenize(m_natPoolInfo[pool_name].ip_range, range_specifier); + + /* Check the pool is valid */ + if (nat_ip.empty()) + { + SWSS_LOG_INFO("NAT pool %s is not valid, skipping dynamic twice nat iptables rules deletion for %s", pool_name.c_str(), (*it).first.c_str()); + continue; + } + + /* Delete Dynamic rules */ + isRulesDeleted = true; + setDynamicAllForwardOrAclbasedRules(DELETE, (*it).second.pool_interface, m_natPoolInfo[pool_name].ip_range, + m_natPoolInfo[pool_name].port_range, acls_name, (*it).first); + break; + } + + if (!isRulesDeleted) + { + SWSS_LOG_INFO("No Static-Dynamic Twice NAT iptables rules to delete"); + } +} + +/* To delete Static NAPT iptables based on L3 Interface if all valid conditions are met */ +void NatMgr::removeStaticNaptIptables(const string port) +{ + /* Example: + * Port is Ethernet1 + */ + + bool isRulesDeleted = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping NAPT iptables deletion"); + return; + } + + /* Get all the Static NAPT entries */ + for (auto it = m_staticNaptEntry.begin(); it != m_staticNaptEntry.end(); it++) + { + /* Check interface is matching, otherwise continue */ + if ((*it).second.interface != port) + { + continue; + } + + isRulesDeleted = true; + + if ((*it).second.twice_nat_id.empty()) + { + /* Remove the Static Single NAPT Iptables */ + SWSS_LOG_INFO("Deleting the Static Single NAPT Iptables for %s", (*it).first.c_str()); + removeStaticSingleNaptIptables((*it).first); + } + else if (((*it).second.twice_nat_added == true)) + { + /* Remove the Static Twice NAPT Iptables */ + SWSS_LOG_INFO("Deleting the Static Twice NAPT iptables for %s", (*it).first.c_str()); + removeStaticTwiceNaptIptables((*it).first); + } + } + + if (!isRulesDeleted) + { + SWSS_LOG_INFO("No Static NAPT iptables rules to delete"); + } +} + +/* To delete Static Single NAPT Iptables based on Static Key if all valid conditions are met */ +void NatMgr::removeStaticSingleNaptIptables(const string &key) +{ + /* Example: + * Entry is STATIC_NAPT|65.55.42.1|TCP|1024 and key is 65.55.42.1|TCP|1024 + */ + + string interface = EMPTY_STRING, prototype = EMPTY_STRING; + vector keys = tokenize(key, config_db_key_delimiter); + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s Single NAPT iptables deletion", key.c_str()); + return; + } + + if (keys[1] == to_upper(IP_PROTOCOL_UDP)) + { + prototype = IP_PROTOCOL_UDP; + } + else if (keys[1] == to_upper(IP_PROTOCOL_TCP)) + { + prototype = IP_PROTOCOL_TCP; + } + + interface = m_staticNaptEntry[key].interface; + + /* Remove Static NAPT iptables rule */ + if (!setStaticNaptIptablesRules(DELETE, interface, prototype, keys[0], keys[2], + m_staticNaptEntry[key].local_ip, m_staticNaptEntry[key].local_port, + m_staticNaptEntry[key].nat_type)) + { + SWSS_LOG_ERROR("Failed to delete Static NAPT iptables rules for %s", key.c_str()); + } + else + { + SWSS_LOG_INFO("Deleted Static NAPT iptables rules for %s", key.c_str()); + } +} + +/* To delete Static Twice NAPT Iptables based on Static Key if all valid conditions are met */ +void NatMgr::removeStaticTwiceNaptIptables(const string &key) +{ + /* Example: + * Entry is STATIC_NAPT|65.55.42.1|TCP|1024 and key is 65.55.42.1|TCP|1024 + */ + + string interface = EMPTY_STRING, prototype = EMPTY_STRING; + vector keys = tokenize(key, config_db_key_delimiter); + string src, translated_src, dest, translated_dest; + string src_port, translated_src_port, dest_port, translated_dest_port; + bool isRulesDeleted = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping %s Twice NAPT iptables deletion", key.c_str()); + return; + } + + if (keys[1] == to_upper(IP_PROTOCOL_UDP)) + { + prototype = IP_PROTOCOL_UDP; + } + else if (keys[1] == to_upper(IP_PROTOCOL_TCP)) + { + prototype = IP_PROTOCOL_TCP; + } + + /* Get all the Static NAPT entries */ + for (auto it = m_staticNaptEntry.begin(); it != m_staticNaptEntry.end(); it++) + { + vector entry_keys = tokenize((*it).first, config_db_key_delimiter); + + /* Check for other entries, otherwise continue */ + if ((*it).first == key) + { + continue; + } + + /* Check for both protocols are same, otherwise continue */ + if (entry_keys[1] != keys[1]) + { + continue; + } + + /* Check the twice_nat_id is matched, otherwise continue */ + if ((*it).second.twice_nat_id != m_staticNaptEntry[key].twice_nat_id) + { + continue; + } + + /* Check the twice NAT is added, otherwise continue */ + if ((!(*it).second.twice_nat_added) or (!m_staticNaptEntry[key].twice_nat_added)) + { + continue; + } + + /* Check the nat type is different, otherwise continue */ + if ((*it).second.nat_type == m_staticNaptEntry[key].nat_type) + { + continue; + } + + if (((*it).second.nat_type == DNAT_NAT_TYPE) and + (m_staticNaptEntry[key].nat_type == SNAT_NAT_TYPE)) + { + src = keys[0]; + src_port = keys[2]; + dest = entry_keys[0]; + dest_port = entry_keys[2]; + translated_src = m_staticNaptEntry[key].local_ip; + translated_src_port = m_staticNaptEntry[key].local_port; + translated_dest = (*it).second.local_ip; + translated_dest_port = (*it).second.local_port; + interface = (*it).second.interface; + } + else if (((*it).second.nat_type == SNAT_NAT_TYPE) and + (m_staticNaptEntry[key].nat_type == DNAT_NAT_TYPE)) + { + src = entry_keys[0]; + src_port = entry_keys[2]; + dest = keys[0]; + dest_port = keys[2]; + translated_src = (*it).second.local_ip; + translated_src_port = (*it).second.local_port; + translated_dest = m_staticNaptEntry[key].local_ip; + translated_dest_port = m_staticNaptEntry[key].local_port; + interface = m_staticNaptEntry[key].interface; + } + + /* Delete Static NAPT iptables rule */ + if (!setStaticTwiceNaptIptablesRules(DELETE, interface, prototype, src, src_port, translated_src, translated_src_port, + dest, dest_port, translated_dest, translated_dest_port)) + { + SWSS_LOG_ERROR("Failed to delete Static Twice NAPT iptables rules for %s and %s", key.c_str(), (*it).first.c_str()); + } + else + { + isRulesDeleted = true; + SWSS_LOG_INFO("Deleted Static Twice NAPT iptables rules for %s and %s", key.c_str(), (*it).first.c_str()); + } + break; + } + + if (!isRulesDeleted) + { + SWSS_LOG_INFO("No Static Twice NAPT iptables rules to delete"); + } + else + { + return; + } + + /* Get all Binding Info */ + for (auto it = m_natBindingInfo.begin(); it != m_natBindingInfo.end(); it++) + { + string port_range = EMPTY_STRING; + string pool_name = (*it).second.pool_name; + string acls_name = (*it).second.acl_name; + + /* Check the pool is present in cache, otherwise continue */ + if (m_natPoolInfo.find(pool_name) == m_natPoolInfo.end()) + { + continue; + } + + /* Check the twice_nat_id is matched, otherwise continue */ + if ((*it).second.twice_nat_id != m_staticNaptEntry[key].twice_nat_id) + { + continue; + } + + /* Check the twice NAT is added, otherwise continue */ + if ((!(*it).second.twice_nat_added) or (!m_staticNaptEntry[key].twice_nat_added)) + { + continue; + } + + /* Check the nat type is same, otherwise continue */ + if ((*it).second.nat_type != m_staticNaptEntry[key].nat_type) + { + continue; + } + + /* Check the port_range is present, otherwise continue */ + port_range = m_natPoolInfo[pool_name].port_range; + if (port_range.empty() or (port_range == "NULL")) + { + continue; + } + + /* Check the key is matching, otherwise continue */ + if (m_staticNaptEntry[key].binding_key != (*it).first) + { + continue; + } + vector nat_ip = tokenize(m_natPoolInfo[pool_name].ip_range, range_specifier); + + /* Check the pool is valid */ + if (nat_ip.empty()) + { + SWSS_LOG_INFO("NAT pool %s is not valid, skipping dynamic twice nat iptables rules deletion for %s", pool_name.c_str(), (*it).first.c_str()); + continue; + } + + /* Delete Dynamic rules */ + isRulesDeleted = true; + setDynamicAllForwardOrAclbasedRules(DELETE, (*it).second.pool_interface, m_natPoolInfo[pool_name].ip_range, + m_natPoolInfo[pool_name].port_range, acls_name, (*it).first); + break; + } + + if (!isRulesDeleted) + { + SWSS_LOG_INFO("No Static-Dynamic Twice NAPT iptables rules to delete"); + } +} + +/* To Add or Delete Dynamic NAT/NAPT iptables rules if all valid conditions are met */ +void NatMgr::setDynamicAllForwardOrAclbasedRules(const string &opCmd, const string &pool_interface, const string &ip_range, + const string &port_range, const string &aclsName, + const string &dynamicKey) +{ + vector access_list; + bool setAllForwardRules = true; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping Dynamic Iptables setting"); + return; + } + + /* Add/Delete ACLs based only when aclName string is not empty */ + if (((opCmd == DELETE) and (m_natBindingInfo[dynamicKey].acl_interface != NONE_STRING) and (aclsName != EMPTY_STRING)) or + ((opCmd == ADD) and (aclsName != EMPTY_STRING))) + { + access_list = tokenize(aclsName, comma); + + /* Loop for all ACL Ids */ + for (string aclId : access_list) + { + /* Check the Acl-id is enabled otherwise continue */ + if (m_natAclTableInfo.find(aclId) == m_natAclTableInfo.end()) + { + SWSS_LOG_INFO("Acl-id %s is not yet enabled, skipping it", aclId.c_str()); + continue; + } + + SWSS_LOG_INFO("Acl-id %s is enabled", aclId.c_str()); + + bool isRuleSet = false; + + /* Get all ACL Rule Info */ + for (auto it = m_natAclRuleInfo.begin(); it != m_natAclRuleInfo.end(); it++) + { + vector aclRuleKeys = tokenize((*it).first, config_db_key_delimiter); + + /* Check aclId is matching, otherwise continue */ + if (aclRuleKeys[0] != aclId) + { + continue; + } + + SWSS_LOG_INFO("Rule-id %s is mapped to Acl-id %s", aclRuleKeys[1].c_str(), aclId.c_str()); + + /* Set pool ip to APPL_DB */ + setNaptPoolIpTable(opCmd, ip_range, port_range); + + /* Set dynamic iptables rule with acls*/ + if (!setDynamicNatIptablesRulesWithAcl(opCmd, pool_interface, ip_range, port_range, (*it).second, m_natBindingInfo[dynamicKey].static_key)) + { + SWSS_LOG_ERROR("Failed to %s dynamic iptables acl rules for Rule id %s for Table %s", opCmd == ADD ? "add" : "delete", + aclRuleKeys[1].c_str(), aclId.c_str()); + } + else + { + isRuleSet = true; + SWSS_LOG_INFO("%s dynamic iptables acl rules for Rule id %s for Table %s", opCmd == ADD ? "Added" : "Deleted", + aclRuleKeys[1].c_str(), aclId.c_str()); + } + + setAllForwardRules = false; + } + + /* If rule is set, save the port in the binding cache */ + if (isRuleSet and (opCmd == ADD)) + { + if (m_natBindingInfo[dynamicKey].acl_interface == NONE_STRING) + { + m_natBindingInfo[dynamicKey].acl_interface = m_natAclTableInfo[aclId]; + } + else + { + m_natBindingInfo[dynamicKey].acl_interface += (comma + m_natAclTableInfo[aclId]); + } + } + } + + /* After deletion, set acl_interface to None */ + if (opCmd == DELETE) + { + m_natBindingInfo[dynamicKey].acl_interface == NONE_STRING; + } + } + + /* To set all forward rules */ + if (setAllForwardRules) + { + /* Set pool ip to APPL_DB */ + setNaptPoolIpTable(opCmd, ip_range, port_range); + + /* Set dynamic iptables rule without acls*/ + if (!setDynamicNatIptablesRulesWithoutAcl(opCmd, pool_interface, ip_range, port_range, m_natBindingInfo[dynamicKey].static_key)) + { + SWSS_LOG_ERROR("Failed to %s dynamic iptables rules for %s", opCmd == ADD ? "add" : "delete", dynamicKey.c_str()); + } + else + { + SWSS_LOG_INFO("%s dynamic iptables rules for %s", opCmd == ADD ? "Added" : "Deleted", dynamicKey.c_str()); + } + } +} + +/* To Add Dynamic NAT rules based on Binding Key if all valid conditions are met */ +void NatMgr::addDynamicNatRule(const string &key) +{ + /* Example: + * Entry is NAT_BINDINGS|BindingName and key is BindingName + */ + + string pool_interface = EMPTY_STRING; + string pool_name = m_natBindingInfo[key].pool_name; + string acls_name = m_natBindingInfo[key].acl_name; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping dynamic nat rules addition for %s", key.c_str()); + return; + } + + /* Check the pool is present in cache, otherwise return */ + if (m_natPoolInfo.find(pool_name) == m_natPoolInfo.end()) + { + SWSS_LOG_INFO("Pool %s is not yet enabled, skipping dynamic nat rules addition for %s", pool_name.c_str(), key.c_str()); + return; + } + + vector nat_ip = tokenize(m_natPoolInfo[pool_name].ip_range, range_specifier); + + /* Check the pool is valid */ + if (nat_ip.empty()) + { + SWSS_LOG_INFO("NAT pool %s is not valid, skipping dynamic nat rules addition for %s", pool_name.c_str(), key.c_str()); + return; + } + + /* Get the matching Ip interface otherwise return */ + if (!getIpEnabledIntf(nat_ip[0], pool_interface)) + { + SWSS_LOG_INFO("L3 Interface is not yet enabled for %s, skipping dynamic nat rules addition", key.c_str()); + return; + } + + m_natBindingInfo[key].pool_interface = pool_interface; + + if (m_natBindingInfo[key].twice_nat_id.empty()) + { + /* Add Dynamic rules for Single NAT */ + SWSS_LOG_INFO("Adding dynamic single nat rules for %s", key.c_str()); + setDynamicAllForwardOrAclbasedRules(ADD, pool_interface, m_natPoolInfo[pool_name].ip_range, + m_natPoolInfo[pool_name].port_range, acls_name, key); + } + else + { + /* Add Dynamic rules for Twice NAT */ + SWSS_LOG_INFO("Adding dynamic twice nat rules for %s", key.c_str()); + addDynamicTwiceNatRule(key); + } +} + +/* To delete Dynamic NAT/NAPT iptables rules based on Binding Key if all valid conditions are met */ +void NatMgr::removeDynamicNatRule(const string &key) +{ + /* Example: + * Entry is NAT_BINDINGS|BindingName and key is BindingName + */ + + string pool_interface = m_natBindingInfo[key].pool_interface; + string pool_name = m_natBindingInfo[key].pool_name; + string acls_name = m_natBindingInfo[key].acl_name; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping dynamic nat rules deletion for %s", key.c_str()); + return; + } + + /* Check the pool is present in cache otherwise return */ + if (m_natPoolInfo.find(pool_name) == m_natPoolInfo.end()) + { + SWSS_LOG_INFO("Pool %s is not yet enabled, skipping dynamic nat rules deletion for %s", pool_name.c_str(), key.c_str()); + return; + } + + /* Check pool interface is valid, otherwise return */ + if (m_natBindingInfo[key].pool_interface == NONE_STRING) + { + SWSS_LOG_INFO("Pool interface is not enabled, skipping dynamic nat rules deletion for %s", key.c_str()); + return; + } + + vector nat_ip = tokenize(m_natPoolInfo[pool_name].ip_range, range_specifier); + + /* Check the pool is valid */ + if (nat_ip.empty()) + { + SWSS_LOG_INFO("NAT pool %s is not valid, skipping dynamic nat rules deletion for %s", pool_name.c_str(), key.c_str()); + return; + } + + if (m_natBindingInfo[key].twice_nat_id.empty()) + { + /* Delete Dynamic rules for Single NAT */ + SWSS_LOG_INFO("Deleting dynamic single nat rules for %s", key.c_str()); + setDynamicAllForwardOrAclbasedRules(DELETE, pool_interface, m_natPoolInfo[pool_name].ip_range, + m_natPoolInfo[pool_name].port_range, acls_name, key); + } + else + { + /* Delete Dynamic rules for Twice NAT */ + SWSS_LOG_INFO("Deleting dynamic twice nat rules for %s", key.c_str()); + deleteDynamicTwiceNatRule(key); + } + + m_natBindingInfo[key].pool_interface = NONE_STRING; + m_natBindingInfo[key].acl_interface = NONE_STRING; +} + +/* To Add Dynamic NAT/NAPT iptables rules based on ACLs if all valid conditions are met */ +void NatMgr::addDynamicNatRuleByAcl(const string &aclKey, bool isRuleId) +{ + /* Example: + * Key : (AclId | AclRuleId) or (AclId) + */ + + string aclTableId, aclRuleId; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping dynamic iptables rules addition for %s", aclKey.c_str()); + return; + } + + /* Get the aclTable and aclRule */ + if (isRuleId == true) + { + vector keys = tokenize(aclKey, config_db_key_delimiter); + aclTableId = keys[0]; + aclRuleId = keys[1]; + } + else + { + aclTableId = aclKey; + } + + /* Get all binding Info */ + for (auto it = m_natBindingInfo.begin(); it != m_natBindingInfo.end(); it++) + { + string pool_name = (*it).second.pool_name; + string acls_name = (*it).second.acl_name; + string poolInterface, aclInterface; + string port_range, ip_range; + bool isRuleSet = false; + + /* Check the pool is present in cache, otherwise continue */ + if (m_natPoolInfo.find(pool_name) == m_natPoolInfo.end()) + { + continue; + } + + /* Check if pool interface is valid, otherwise continue */ + if ((*it).second.pool_interface == NONE_STRING) + { + continue; + } + + /* Check the ACL Ids are configured, otherwise continue */ + if ((*it).second.acl_name == EMPTY_STRING) + { + continue; + } + + /* Check if the twice nat id is configured and not added */ + if ((!(*it).second.twice_nat_id.empty()) and ((*it).second.twice_nat_added)) + { + continue; + } + + poolInterface = (*it).second.pool_interface; + aclInterface = (*it).second.acl_interface; + ip_range = m_natPoolInfo[pool_name].ip_range; + port_range = m_natPoolInfo[pool_name].port_range; + + vector nat_ip = tokenize(m_natPoolInfo[pool_name].ip_range, range_specifier); + + /* Check the pool is valid */ + if (nat_ip.empty()) + { + SWSS_LOG_INFO("NAT pool %s is not valid, skipping dynamic iptables rules addition for %s", pool_name.c_str(), (*it).first.c_str()); + continue; + } + + vector access_list = tokenize(acls_name, comma); + + /* Get all aclIds */ + for (string aclId : access_list) + { + /* Check the Acl-id is matching with given one, otherwise continue */ + if (aclTableId != aclId) + { + continue; + } + + /* isRuleId is true means we have both AclTableId and AclRuleId */ + if (isRuleId == true) + { + /* Check the Acl-id is enabled otherwise continue */ + if (m_natAclTableInfo.find(aclTableId) == m_natAclTableInfo.end()) + { + SWSS_LOG_INFO("Acl-id %s is not yet enabled", aclTableId.c_str()); + return; + } + + /* aclInterface is None means delete all forward rule first, otherwise nothing */ + if (aclInterface == NONE_STRING) + { + /* Set pool ip to APPL_DB */ + setNaptPoolIpTable(DELETE, ip_range, port_range); + + /* Set dynamic iptables rule without acl */ + if (!setDynamicNatIptablesRulesWithoutAcl(DELETE, poolInterface, ip_range, port_range, (*it).second.static_key)) + { + SWSS_LOG_ERROR("Failed to remove dynamic iptables rules for %s", aclKey.c_str()); + } + else + { + SWSS_LOG_INFO("Deleted dynamic iptables rules for %s", aclKey.c_str()); + } + + (*it).second.acl_interface = m_natAclTableInfo[aclTableId]; + } + else + { + (*it).second.acl_interface = comma + m_natAclTableInfo[aclTableId]; + } + + /* Set pool ip to APPL_DB */ + setNaptPoolIpTable(ADD, ip_range, port_range); + + /* Set dynamic iptables rule with acls*/ + if (!setDynamicNatIptablesRulesWithAcl(ADD, poolInterface, ip_range, port_range, m_natAclRuleInfo[aclKey], (*it).second.static_key)) + { + SWSS_LOG_ERROR("Failed to add dynamic iptables acl rules for Rule id %s for Table %s", aclRuleId.c_str(), aclTableId.c_str()); + } + else + { + SWSS_LOG_INFO("Added dynamic iptables acl rules for Rule id %s for Table %s", aclRuleId.c_str(), aclTableId.c_str()); + } + return; + } + else + { + /* Get all AclRule Info */ + for (auto it2 = m_natAclRuleInfo.begin(); it2 != m_natAclRuleInfo.end(); it2++) + { + vector aclRuleKeys = tokenize((*it2).first, config_db_key_delimiter); + + /* Check the matching aclTableId, otherwise continue */ + if (aclRuleKeys[0] != aclTableId) + { + continue; + } + + SWSS_LOG_INFO("Rule-id %s is mapped to Acl-id %s", aclRuleKeys[1].c_str(), aclTableId.c_str()); + + /* Set pool ip to APPL_DB */ + setNaptPoolIpTable(ADD, ip_range, port_range); + + /* Add dynamic iptables rule with acls */ + if (!setDynamicNatIptablesRulesWithAcl(ADD, poolInterface, ip_range, port_range, (*it2).second, (*it).second.static_key)) + { + SWSS_LOG_ERROR("Failed to add dynamic iptables acl rules for Rule id %s for Table %s", aclRuleKeys[1].c_str(), aclTableId.c_str()); + } + else + { + isRuleSet = true; + SWSS_LOG_INFO("Added dynamic iptables acl rules for Rule id %s for Table %s", aclRuleKeys[1].c_str(), aclTableId.c_str()); + } + } + + /* aclInterface is None means have to delete the All forward rules */ + if ((aclInterface == NONE_STRING) and (isRuleSet == true)) + { + /* Set pool ip to APPL_DB */ + setNaptPoolIpTable(DELETE, ip_range, port_range); + + /* Delete dynamic iptables rule without acl */ + if (!setDynamicNatIptablesRulesWithoutAcl(DELETE, poolInterface, ip_range, port_range, (*it).second.static_key)) + { + SWSS_LOG_ERROR("Failed to remove dynamic iptables rules for %s", aclKey.c_str()); + } + else + { + SWSS_LOG_INFO("Deleted dynamic iptables rules for %s", aclKey.c_str()); + } + + (*it).second.acl_interface = m_natAclTableInfo[aclTableId]; + } + return; + } + } + } +} + +/* To Delete Dynamic NAT/NAPT iptables rules based on ACLs if all valid conditions are met */ +void NatMgr::removeDynamicNatRuleByAcl(const string &aclKey, bool isRuleId) +{ + /* Example: + * Key : (AclId | AclRuleId) or (AclId) + */ + + string aclTableId, aclRuleId; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping dynamic nat rules deletion for %s", aclKey.c_str()); + return; + } + + /* Get the aclTable and aclRule */ + if (isRuleId == true) + { + vector keys = tokenize(aclKey, config_db_key_delimiter); + aclTableId = keys[0]; + aclRuleId = keys[1]; + } + else + { + aclTableId = aclKey; + } + + /* Get all Binding Info */ + for (auto it = m_natBindingInfo.begin(); it != m_natBindingInfo.end(); it++) + { + string pool_name = (*it).second.pool_name; + string acls_name = (*it).second.acl_name; + string poolInterface, aclInterface; + string port_range, ip_range; + bool isRuleSet = false, isRulePresent = false; + + /* Check the pool is present in cache, otherwise continue */ + if (m_natPoolInfo.find(pool_name) == m_natPoolInfo.end()) + { + continue; + } + + /* Check if pool interface is valid, otherwise continue */ + if ((*it).second.pool_interface == NONE_STRING) + { + continue; + } + + /* Check the ACL Ids are configured, otherwise continue */ + if ((*it).second.acl_name == EMPTY_STRING) + { + continue; + } + + /* Check if the twice nat id is added */ + if ((!(*it).second.twice_nat_id.empty()) and (!(*it).second.twice_nat_added)) + { + continue; + } + + poolInterface = (*it).second.pool_interface; + aclInterface = (*it).second.acl_interface; + ip_range = m_natPoolInfo[pool_name].ip_range; + port_range = m_natPoolInfo[pool_name].port_range; + + vector nat_ip = tokenize(m_natPoolInfo[pool_name].ip_range, range_specifier); + + /* Check the pool is valid */ + if (nat_ip.empty()) + { + SWSS_LOG_INFO("NAT pool %s is not valid, skipping dynamic nat rules deletion for %s", pool_name.c_str(), (*it).first.c_str()); + continue; + } + + vector access_list = tokenize(acls_name, ','); + + /* Get all aclIds */ + for (string aclId : access_list) + { + /* Check the aclId is matching with given one, otherwise continue */ + if (aclTableId != aclId) + { + continue; + } + + /* isRuleId is true means we have both AclTableId and AclRuleId */ + if (isRuleId == true) + { + /* Check the Acl-id is enabled otherwise continue */ + if (m_natAclTableInfo.find(aclTableId) == m_natAclTableInfo.end()) + { + SWSS_LOG_INFO("Acl-id %s is not yet enabled", aclTableId.c_str()); + return; + } + + /* Set pool ip to APPL_DB */ + setNaptPoolIpTable(DELETE, ip_range, port_range); + + /* Delete dynamic iptables rule with acls*/ + if (!setDynamicNatIptablesRulesWithAcl(DELETE, poolInterface, ip_range, port_range, m_natAclRuleInfo[aclKey], (*it).second.static_key)) + { + SWSS_LOG_ERROR("Failed to delete dynamic iptables acl rules for Rule id %s for Table %s", aclRuleId.c_str(), aclTableId.c_str()); + } + else + { + SWSS_LOG_INFO("Deleted dynamic iptables acl rules for Rule id %s for Table %s", aclRuleId.c_str(), aclTableId.c_str()); + } + + /* Check any other rule matching in same Table-Id */ + for (auto it = m_natAclRuleInfo.begin(); it != m_natAclRuleInfo.end(); it++) + { + vector aclRuleKeys = tokenize((*it).first, config_db_key_delimiter); + + /* Check the matching aclTableId otherwise continue */ + if (aclRuleKeys[0] != aclTableId) + { + continue; + } + + SWSS_LOG_INFO("Rule-id %s is mapped to Acl-id %s", aclRuleKeys[1].c_str(), aclTableId.c_str()); + if (aclRuleKeys[1] == aclRuleId) + { + continue; + } + isRulePresent = true; + } + + if (isRulePresent == false) + { + /* Set pool ip to APPL_DB */ + setNaptPoolIpTable(ADD, ip_range, port_range); + + /* Set dynamic iptables rule without acl */ + if (!setDynamicNatIptablesRulesWithoutAcl(ADD, poolInterface, ip_range, port_range, (*it).second.static_key)) + { + SWSS_LOG_ERROR("Failed to add dynamic iptables rules for %s", aclKey.c_str()); + } + else + { + SWSS_LOG_INFO("Added dynamic iptables rules for %s", aclKey.c_str()); + } + + (*it).second.acl_interface = NONE_STRING; + } + return; + } + else + { + /* Get all AclRule Info */ + for (auto it2 = m_natAclRuleInfo.begin(); it2 != m_natAclRuleInfo.end(); it2++) + { + vector aclRuleKeys = tokenize((*it2).first, config_db_key_delimiter); + + /* Check the matching aclTableId, otherwise continue */ + if (aclRuleKeys[0] != aclTableId) + { + continue; + } + + SWSS_LOG_INFO("Rule-id %s is mapped to Acl-id %s", aclRuleKeys[1].c_str(), aclTableId.c_str()); + + /* Set pool ip to APPL_DB */ + setNaptPoolIpTable(DELETE, ip_range, port_range); + + /* Delete dynamic iptables rule with acls */ + if (!setDynamicNatIptablesRulesWithAcl(DELETE, poolInterface, ip_range, port_range, (*it2).second, (*it).second.static_key)) + { + SWSS_LOG_ERROR("Failed to delete dynamic iptables acl rules for Rule id %s for Table %s", aclRuleKeys[1].c_str(), aclTableId.c_str()); + } + else + { + isRuleSet = true; + SWSS_LOG_INFO("Deleted dynamic iptables acl rules for Rule id %s for Table %s", aclRuleKeys[1].c_str(), aclTableId.c_str()); + } + } + + /* If aclInterface is not None, add dynamic all forward rules */ + if ((aclInterface != NONE_STRING) and (isRuleSet == true)) + { + /* Set pool ip to APPL_DB */ + setNaptPoolIpTable(ADD, ip_range, port_range); + + /* Add dynamic iptables rule without acl */ + if (!setDynamicNatIptablesRulesWithoutAcl(ADD, poolInterface, ip_range, port_range, (*it).second.static_key)) + { + SWSS_LOG_ERROR("Failed to add dynamic iptables rules for %s", aclKey.c_str()); + } + else + { + SWSS_LOG_INFO("Added dynamic iptables rules for %s", aclKey.c_str()); + } + + (*it).second.acl_interface = NONE_STRING; + } + return; + } + } + } +} + +/* To Add Dynamic NAT iptables rules based on L3 Interface if all valid conditions are met */ +void NatMgr::addDynamicNatRules(const string port, const string ipPrefix) +{ + /* Example: + * Port is Ethernet1 and ipPrefix is 10.0.0.1/24 + */ + + bool isRuleAdded = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping dynamic nat rules addition"); + return; + } + + /* Get all Binding Info */ + for (auto it = m_natBindingInfo.begin(); it != m_natBindingInfo.end(); it++) + { + string pool_interface = EMPTY_STRING; + string pool_name = (*it).second.pool_name; + + /* Check the pool interface is valid, otherwise continue */ + if ((*it).second.pool_interface != NONE_STRING) + { + continue; + } + + /* Check the pool is present in cache, otherwise continue */ + if (m_natPoolInfo.find(pool_name) == m_natPoolInfo.end()) + { + continue; + } + + /* Check if the twice nat id is added */ + if ((!(*it).second.twice_nat_id.empty()) and ((*it).second.twice_nat_added)) + { + continue; + } + + vector nat_ip = tokenize(m_natPoolInfo[pool_name].ip_range, range_specifier); + + /* Check the pool is valid */ + if (nat_ip.empty()) + { + SWSS_LOG_INFO("NAT pool %s is not valid, skipping dynamic nat rules addition for %s", pool_name.c_str(), (*it).first.c_str()); + continue; + } + + if ((port == NONE_STRING) and (ipPrefix == NONE_STRING)) + { + /* Get the matching Ip interface otherwise return */ + if (!getIpEnabledIntf(nat_ip[0], pool_interface)) + { + continue; + } + + (*it).second.pool_interface = pool_interface; + } + else if ((port != NONE_STRING) and (ipPrefix == NONE_STRING)) + { + /* Get the matching Ip interface otherwise return */ + if (!getIpEnabledIntf(nat_ip[0], pool_interface)) + { + continue; + } + + if (pool_interface != port) + { + continue; + } + (*it).second.pool_interface = pool_interface; + } + else + { + /* Check global ip address is matching otherwise continue */ + if (isGlobalIpMatching(ipPrefix, nat_ip[0]) == false) + { + continue; + } + + pool_interface = port; + (*it).second.pool_interface = port; + } + + if ((*it).second.twice_nat_id.empty()) + { + /* Add Dynamic rules for Single NAT */ + SWSS_LOG_INFO("Adding dynamic single nat rules for %s", (*it).first.c_str()); + setDynamicAllForwardOrAclbasedRules(ADD, pool_interface, m_natPoolInfo[pool_name].ip_range, + m_natPoolInfo[pool_name].port_range, (*it).second.acl_name, + (*it).first); + } + else + { + /* Add Dynamic rules for Twice NAT */ + SWSS_LOG_INFO("Adding dynamic twice nat rules for %s", (*it).first.c_str()); + addDynamicTwiceNatRule((*it).first); + } + + if ((port != NONE_STRING) and (ipPrefix != NONE_STRING)) + { + /* Send notification to Orchagent to flush the conntrack entries */ + std::vector entry; + flushNotifier->send("ENTRIES", "ALL", entry); + SWSS_LOG_WARN("Added interface is part of Binded NAT Pool range, so it is clearing all the nat translations"); + } + + isRuleAdded = true; + } + + if (!isRuleAdded) + { + SWSS_LOG_INFO("No dynamic nat rules to add"); + } +} + +/* To delete Dynamic NAT/NAPT iptables rules based on L3 Interface if all valid conditions are met */ +void NatMgr::removeDynamicNatRules(const string port, const string ipPrefix) +{ + /* Example: + * Port is Ethernet1 and ipPrefix is 10.0.0.1/24 + */ + + bool isRuleDeleted = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping dynamic nat rules deletion"); + return; + } + + /* Get all Binding Info */ + for (auto it = m_natBindingInfo.begin(); it != m_natBindingInfo.end(); it++) + { + string pool_interface = EMPTY_STRING; + string pool_name = m_natBindingInfo[(*it).first].pool_name; + vector nat_ip = tokenize(m_natPoolInfo[pool_name].ip_range, range_specifier); + + /* Check interface is matching, otherwise continue */ + if ((*it).second.pool_interface == NONE_STRING) + { + continue; + } + + /* Check the pool is present in cache, otherwise continue */ + if (m_natPoolInfo.find(pool_name) == m_natPoolInfo.end()) + { + continue; + } + + /* Check if the twice nat id is added */ + if ((!(*it).second.twice_nat_id.empty()) and (!(*it).second.twice_nat_added)) + { + (*it).second.pool_interface = NONE_STRING; + continue; + } + + /* Check the pool is valid */ + if (nat_ip.empty()) + { + SWSS_LOG_INFO("NAT pool %s is not valid, skipping dynamic rules deletion for %s", pool_name.c_str(), (*it).first.c_str()); + continue; + } + + if ((port == NONE_STRING) and (ipPrefix == NONE_STRING)) + { + pool_interface = (*it).second.pool_interface; + } + else if ((port != NONE_STRING) and (ipPrefix == NONE_STRING)) + { + /* Check interface is matching, otherwise continue */ + if ((*it).second.pool_interface != port) + { + continue; + } + + pool_interface = port; + } + else + { + /* Check interface is matching, otherwise continue */ + if ((*it).second.pool_interface != port) + { + continue; + } + + /* Check the global ip address is matching otherwise continue */ + if (isGlobalIpMatching(ipPrefix, nat_ip[0]) == false) + { + continue; + } + + pool_interface = port; + } + + if ((*it).second.twice_nat_id.empty()) + { + /* Delete Dynamic rules for Single NAT */ + SWSS_LOG_INFO("Deleting dynamic single nat rules for %s", (*it).first.c_str()); + setDynamicAllForwardOrAclbasedRules(DELETE, pool_interface, m_natPoolInfo[pool_name].ip_range, + m_natPoolInfo[pool_name].port_range, (*it).second.acl_name, + (*it).first); + } + else + { + /* Delete Dynamic rules for Twice NAT */ + SWSS_LOG_INFO("Deleting dynamic twice nat rules for %s", (*it).first.c_str()); + deleteDynamicTwiceNatRule((*it).first); + } + + if ((port != NONE_STRING) and (ipPrefix != NONE_STRING)) + { + deleteConntrackDynamicEntries(m_natPoolInfo[pool_name].ip_range); + } + + (*it).second.pool_interface = NONE_STRING; + isRuleDeleted = true; + } + + if (!isRuleDeleted) + { + SWSS_LOG_INFO("No dynamic nat rules to delete"); + } +} + +/* To Add Dynamic Twice NAT/NAPT iptables rules based on Binding Key if all valid conditions are met */ +void NatMgr::addDynamicTwiceNatRule(const string &key) +{ + /* Example: + * Entry is NAT_BINDINGS|BindingName and key is BindingName + */ + + string port_range = EMPTY_STRING; + string pool_name = m_natBindingInfo[key].pool_name; + string acls_name = m_natBindingInfo[key].acl_name; + bool isRuleAdded = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping dynamic twice nat rules addition for %s", key.c_str()); + return; + } + + /* Check the twice NAT is added, otherwise return */ + if ((m_natBindingInfo[key].twice_nat_added) or (!m_natBindingInfo[key].static_key.empty())) + { + SWSS_LOG_INFO("Twice NAT is already added, skipping dynamic twice nat rules addition for %s", key.c_str()); + return; + } + + port_range = m_natPoolInfo[pool_name].port_range; + + if (!port_range.empty() and (port_range != "NULL")) + { + /* Check with Static NAPT entries */ + for (auto it = m_staticNaptEntry.begin(); it != m_staticNaptEntry.end(); it++) + { + /* Check the twice_nat_id is matched, otherwise continue */ + if (m_natBindingInfo[key].twice_nat_id != (*it).second.twice_nat_id) + { + continue; + } + + /* Check the twice NAT is not added, otherwise continue */ + if (((*it).second.twice_nat_added) or (!(*it).second.binding_key.empty())) + { + continue; + } + + /* Check interface is assigned, otherwise continue */ + if ((m_natBindingInfo[key].pool_interface == NONE_STRING) or ((*it).second.interface == NONE_STRING)) + { + continue; + } + + /* Check the nat type is equal, otherwise continue */ + if (m_natBindingInfo[key].nat_type != (*it).second.nat_type) + { + continue; + } + + (*it).second.twice_nat_added = true; + (*it).second.binding_key = key; + m_natBindingInfo[key].twice_nat_added = true; + m_natBindingInfo[key].static_key = (*it).first; + + /* Add Dynamic rules */ + setDynamicAllForwardOrAclbasedRules(ADD, m_natBindingInfo[key].pool_interface, m_natPoolInfo[pool_name].ip_range, + m_natPoolInfo[pool_name].port_range, acls_name, key); + + isRuleAdded = true; + break; + } + } + else + { + /* Get all the Static NAT entries */ + for (auto it = m_staticNatEntry.begin(); it != m_staticNatEntry.end(); it++) + { + /* Check the twice_nat_id is matched, otherwise continue */ + if (m_natBindingInfo[key].twice_nat_id != (*it).second.twice_nat_id) + { + continue; + } + + /* Check the twice NAT is not added, otherwise continue */ + if (((*it).second.twice_nat_added) or (!(*it).second.binding_key.empty())) + { + continue; + } + + /* Check interface is assigned, otherwise continue */ + if ((m_natBindingInfo[key].pool_interface == NONE_STRING) or ((*it).second.interface == NONE_STRING)) + { + continue; + } + + /* Check the nat type is equal, otherwise continue */ + if (m_natBindingInfo[key].nat_type != (*it).second.nat_type) + { + continue; + } + + (*it).second.twice_nat_added = true; + (*it).second.binding_key = key; + m_natBindingInfo[key].twice_nat_added = true; + m_natBindingInfo[key].static_key = (*it).first; + + /* Add Dynamic rules */ + setDynamicAllForwardOrAclbasedRules(ADD, m_natBindingInfo[key].pool_interface, m_natPoolInfo[pool_name].ip_range, + m_natPoolInfo[pool_name].port_range, acls_name, key); + + isRuleAdded = true; + break; + } + } + + if (!isRuleAdded) + { + SWSS_LOG_INFO("No dynamic twice nat rules to add"); + } +} + +/* To Delete Dynamic Twice NAT/NAPT iptables rules based on Binding Key if all valid conditions are met */ +void NatMgr::deleteDynamicTwiceNatRule(const string &key) +{ + /* Example: + * Entry is NAT_BINDINGS|BindingName and key is BindingName + */ + + string port_range = EMPTY_STRING; + string pool_name = m_natBindingInfo[key].pool_name; + string acls_name = m_natBindingInfo[key].acl_name; + bool isRuleDeleted = false; + + /* Check the NAT is enabled, otherwise return */ + if (!isNatEnabled()) + { + SWSS_LOG_INFO("NAT is not yet enabled, skipping dynamic twice nat rules deletion for %s", key.c_str()); + return; + } + + /* Check the twice NAT is not added, otherwise return */ + if ((!m_natBindingInfo[key].twice_nat_added) or (m_natBindingInfo[key].static_key.empty())) + { + SWSS_LOG_INFO("Twice NAT rule is not yet added, skipping dynamic twice nat rules deletion for %s", key.c_str()); + return; + } + + port_range = m_natPoolInfo[pool_name].port_range; + + if (!port_range.empty() and (port_range != "NULL")) + { + /* Check with Static NAPT entries */ + for (auto it = m_staticNaptEntry.begin(); it != m_staticNaptEntry.end(); it++) + { + /* Check the twice_nat_id is matched, otherwise continue */ + if (m_natBindingInfo[key].twice_nat_id != (*it).second.twice_nat_id) + { + continue; + } + + /* Check the twice NAT is added, otherwise continue */ + if ((!(*it).second.twice_nat_added) or ((*it).second.binding_key.empty())) + { + continue; + } + + /* Check interface is assigned, otherwise continue */ + if ((m_natBindingInfo[key].pool_interface == NONE_STRING) or ((*it).second.interface == NONE_STRING)) + { + continue; + } + + /* Check the nat type is equal, otherwise continue */ + if (m_natBindingInfo[key].nat_type != (*it).second.nat_type) + { + continue; + } + + /* Check the key is matching, otherwise continue */ + if (m_natBindingInfo[key].static_key != (*it).first) + { + continue; + } + + /* Delete Dynamic rules */ + setDynamicAllForwardOrAclbasedRules(DELETE, m_natBindingInfo[key].pool_interface, m_natPoolInfo[pool_name].ip_range, + m_natPoolInfo[pool_name].port_range, acls_name, key); + + (*it).second.twice_nat_added = false; + (*it).second.binding_key = EMPTY_STRING; + m_natBindingInfo[key].twice_nat_added = false; + m_natBindingInfo[key].static_key = EMPTY_STRING; + isRuleDeleted = true; + break; + } + } + else + { + /* Get all the Static NAT entries */ + for (auto it = m_staticNatEntry.begin(); it != m_staticNatEntry.end(); it++) + { + /* Check the twice_nat_id is matched, otherwise continue */ + if (m_natBindingInfo[key].twice_nat_id != (*it).second.twice_nat_id) + { + continue; + } + + /* Check the twice NAT is added, otherwise continue */ + if ((!(*it).second.twice_nat_added) or ((*it).second.binding_key.empty())) + { + continue; + } + + /* Check interface is assigned, otherwise continue */ + if ((m_natBindingInfo[key].pool_interface == NONE_STRING) or ((*it).second.interface == NONE_STRING)) + { + continue; + } + + /* Check the nat type is equal, otherwise continue */ + if (m_natBindingInfo[key].nat_type != (*it).second.nat_type) + { + continue; + } + + /* Check the key is matching, otherwise continue */ + if (m_natBindingInfo[key].static_key != (*it).first) + { + continue; + } + + /* Delete Dynamic rules */ + setDynamicAllForwardOrAclbasedRules(DELETE, m_natBindingInfo[key].pool_interface, m_natPoolInfo[pool_name].ip_range, + m_natPoolInfo[pool_name].port_range, acls_name, key); + + (*it).second.twice_nat_added = false; + (*it).second.binding_key = EMPTY_STRING; + m_natBindingInfo[key].twice_nat_added = false; + m_natBindingInfo[key].static_key = EMPTY_STRING; + isRuleDeleted = true; + break; + } + } + + if (!isRuleDeleted) + { + SWSS_LOG_INFO("No dynamic twice nat rules to delete"); + } +} + +/* To enable the NAT Feature */ +void NatMgr::enableNatFeature(void) +{ + /* Create APPL_DB key */ + string appKey = VALUES; + vector fvVector; + + FieldValueTuple p(NAT_ADMIN_MODE, "enabled"); + fvVector.push_back(p); + + if (m_natTcpTimeout != NAT_TCP_TIMEOUT_DEFAULT) + { + FieldValueTuple q(NAT_TCP_TIMEOUT, std::to_string(m_natTcpTimeout)); + fvVector.push_back(q); + } + + if (m_natUdpTimeout != NAT_UDP_TIMEOUT_DEFAULT) + { + FieldValueTuple r(NAT_UDP_TIMEOUT, std::to_string(m_natUdpTimeout)); + fvVector.push_back(r); + } + + if (m_natTimeout != NAT_TIMEOUT_DEFAULT) + { + FieldValueTuple s(NAT_TIMEOUT, std::to_string(m_natTimeout)); + fvVector.push_back(s); + } + + m_appNatGlobalTableProducer.set(appKey, fvVector); + SWSS_LOG_INFO("Enabled NAT Admin Mode to APPL_DB"); + + /* Add static NAT entries */ + SWSS_LOG_INFO("Adding Static NAT entries"); + addStaticNatEntries(); + + /* Add static NAPT entries */ + SWSS_LOG_INFO("Adding Static NAPT entries"); + addStaticNaptEntries(); + + /* Add dynamic NAT rules */ + SWSS_LOG_INFO("Adding Dynamic NAT rules"); + addDynamicNatRules(); + + /* Add full-cone PRE-ROUTING DNAT rule in the kernel */ + setFullConeDnatIptablesRule(ADD); +} + +/* To disable the NAT Feature */ +void NatMgr::disableNatFeature(void) +{ + /* Create APPL_DB key */ + string appKey = VALUES; + vector fvVector; + + FieldValueTuple s(NAT_ADMIN_MODE, DISABLED); + fvVector.push_back(s); + + /* Delete static NAT entries */ + SWSS_LOG_INFO("Deleting Static NAT entries"); + removeStaticNatEntries(); + + /* Delete static NAPT entries */ + SWSS_LOG_INFO("Deleting Static NAPT entries"); + removeStaticNaptEntries(); + + /* Delete dynamic NAT/NAPT iptables */ + removeDynamicNatRules(); + + m_appNatGlobalTableProducer.set(appKey, fvVector); + SWSS_LOG_INFO("Disabled NAT Admin Mode to APPL_DB"); + + /* Delete full-cone PRE-ROUTING DNAT rule in the kernel */ + setFullConeDnatIptablesRule(DELETE); +} + +/* To parse the received Static NAT Table and save it to cache */ +void NatMgr::doStaticNatTask(Consumer &consumer) +{ + auto it = consumer.m_toSync.begin(); + + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + string key = kfvKey(t), op = kfvOp(t); + vector keys = tokenize(key, config_db_key_delimiter); + const vector& data = kfvFieldsValues(t); + string global_ip, ipAddress; + string local_ip = EMPTY_STRING, interface = EMPTY_STRING; + string nat_type = EMPTY_STRING, twice_nat_id = EMPTY_STRING; + bool ipFound = false, natTypeFound = false, twiceNatFound = false, nonValueFound = false, isOverlap = false; + int ip_num = 0, nat_type_num = 0, twice_nat_num = 0, twice_nat_value; + uint32_t ipv4_addr, global_addr, local_addr, pool_addr_low, pool_addr_high; + + /* Example : Config_Db + * STATIC_NAT|65.55.42.1 + * local_ip: 10.0.0.1 + * nat_type: dnat + * twice_nat_id: 100 + */ + + /* Ensure the global_ip format is x.x.x.x, otherwise ignore */ + if (inet_pton(AF_INET, key.c_str(), &global_addr) != 1) + { + SWSS_LOG_ERROR("Invalid global address format, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the key size is 1 otherwise ignore */ + if (keys.size() != STATIC_NAT_KEY_SIZE) + { + SWSS_LOG_ERROR("Invalid key size %lu, skipping %s", keys.size(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + global_addr = ntohl(global_addr); + /* Ensure the global ip is not Zero, Broadcast, Loopback, Multicast and Reserved address */ + if (IS_ZERO_ADDR(global_addr) or IS_BROADCAST_ADDR(global_addr) or IS_LOOPBACK_ADDR(global_addr) or + IS_MULTICAST_ADDR(global_addr) or IS_RESERVED_ADDR(global_addr)) + { + SWSS_LOG_ERROR("Invalid global address, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Example : APPL_DB + * NAT_TABLE:10.0.0.1 + * translated_ip: 65.55.42.1 + * nat_type: dnat + * entry_type: static + */ + + if (op == SET_COMMAND) + { + SWSS_LOG_INFO("Set command for %s", key.c_str()); + + /* Get the Config_db key values */ + for (auto idx : data) + { + const auto &field = fvField(idx); + const auto &value = fvValue(idx); + if (field == LOCAL_IP) + { + local_ip = value; + ipFound = true; + ip_num++; + } + else if (field == NAT_TYPE) + { + nat_type = value; + natTypeFound = true; + nat_type_num++; + } + else if (field == TWICE_NAT_ID) + { + twice_nat_id = value; + twiceNatFound = true; + twice_nat_num++; + } + else + { + nonValueFound = true; + } + } + + /* Ensure the local_ip value is valid otherwise ignore */ + if ((ipFound == false) or (ip_num != 1) or nonValueFound == true) + { + SWSS_LOG_ERROR("Invalid local_ip values, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the nat_type value is valid otherwise ignore */ + if ((natTypeFound == true) and (nat_type_num != 1)) + { + SWSS_LOG_ERROR("Invalid nat_type value, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the twice_nat_id value is valid otherwise ignore */ + if ((twiceNatFound == true) and (twice_nat_num != 1)) + { + SWSS_LOG_ERROR("Invalid twice_nat_id value, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the non value is not present otherwise ignore */ + if (nonValueFound == true) + { + SWSS_LOG_ERROR("Invalid value, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the local_ip format is x.x.x.x, otherwise ignore */ + if (inet_pton(AF_INET, local_ip.c_str(), &local_addr) != 1) + { + SWSS_LOG_ERROR("Invalid local ip address format %s, skipping %s", local_ip.c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + local_addr = ntohl(local_addr); + /* Ensure the local ip is not Zero, Broadcast, Loopback, Multicast and Reserved address */ + if (IS_ZERO_ADDR(local_addr) or IS_BROADCAST_ADDR(local_addr) or IS_LOOPBACK_ADDR(local_addr) or + IS_MULTICAST_ADDR(local_addr) or IS_RESERVED_ADDR(local_addr)) + { + SWSS_LOG_ERROR("Invalid local address %s, skipping %s", local_ip.c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the nat_type value is snat or dnat otherwise ignore */ + if ((natTypeFound == true) and ((nat_type != SNAT_NAT_TYPE) and (nat_type != DNAT_NAT_TYPE))) + { + SWSS_LOG_ERROR("Invalid nat_type, it is neither snat or dnat, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + if (twiceNatFound == true) + { + /* Ensure the given twice_nat_id is integer, otherwise ignore */ + try + { + twice_nat_value = stoi(twice_nat_id); + } + catch(...) + { + SWSS_LOG_ERROR("Invalid twice_nat_id %s, skipping %s", twice_nat_id.c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the twice_nat_id is within the limits (1 to 9999), otherwise ignore */ + if ((twice_nat_value < TWICE_NAT_ID_MIN) or (twice_nat_value > TWICE_NAT_ID_MAX)) + { + SWSS_LOG_ERROR("Invalid twice_nat_id, not in limits, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + } + + /* check the global address is overlapping with any NAPT entry */ + global_ip = key; + if (nat_type == SNAT_NAT_TYPE) + { + global_ip = local_ip; + } + + for (auto it = m_staticNaptEntry.begin(); it != m_staticNaptEntry.end(); it++) + { + vector napt_keys = tokenize((*it).first, config_db_key_delimiter); + ipAddress = napt_keys[0]; + if ((*it).second.nat_type == SNAT_NAT_TYPE) + { + ipAddress = (*it).second.local_ip; + } + + if (ipAddress == global_ip) + { + isOverlap = true; + break; + } + } + + if (isOverlap) + { + SWSS_LOG_ERROR("Global Ip overlaps with static NAPT entry, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* check the global address is overlapping with any Dynamic Pool entry */ + ipv4_addr = global_addr; + if (nat_type == SNAT_NAT_TYPE) + { + ipv4_addr = local_addr; + } + + for (auto it = m_natPoolInfo.begin(); it != m_natPoolInfo.end(); it++) + { + vector nat_ip = tokenize((*it).second.ip_range, range_specifier); + + /* Check the pool is valid */ + if ((nat_ip.empty()) or (nat_ip.size() > 2)) + { + continue; + } + else if (nat_ip.size() == 2) + { + inet_pton(AF_INET, nat_ip[1].c_str(), &pool_addr_high); + pool_addr_high = ntohl(pool_addr_high); + + inet_pton(AF_INET, nat_ip[0].c_str(), &pool_addr_low); + pool_addr_low = ntohl(pool_addr_low); + } + else if (nat_ip.size() == 1) + { + inet_pton(AF_INET, nat_ip[0].c_str(), &pool_addr_low); + pool_addr_high = ntohl(pool_addr_low); + pool_addr_low = ntohl(pool_addr_low); + } + + if ((ipv4_addr >= pool_addr_low) and (ipv4_addr <= pool_addr_high)) + { + isOverlap = true; + break; + } + } + + if (isOverlap) + { + SWSS_LOG_ERROR("Global Ip overlaps with Dynamic Pool IP entry, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Check the key is already present in cache */ + if (m_staticNatEntry.find(key) != m_staticNatEntry.end()) + { + SWSS_LOG_INFO("Static NAT %s exists", key.c_str()); + + if (m_staticNatEntry[key].local_ip == local_ip) + { + /* Received the same Key and value, ignore */ + SWSS_LOG_ERROR("Duplicate Static NAT and it's values, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + else + { + SWSS_LOG_INFO("Static NAT %s with updated info", key.c_str()); + + /* Received key with new value */ + if (m_staticNatEntry[key].interface != NONE_STRING) + { + /* Remove the Static NAT Entry */ + SWSS_LOG_INFO("Deleting the Static NAT entry for %s", key.c_str()); + removeStaticNatEntry(key); + } + } + } + + /* New Key, Add it to cache */ + m_staticNatEntry[key].interface = NONE_STRING; + m_staticNatEntry[key].local_ip = local_ip; + if (nat_type.empty()) + { + m_staticNatEntry[key].nat_type = DNAT_NAT_TYPE; + } + else + { + m_staticNatEntry[key].nat_type = nat_type; + } + m_staticNatEntry[key].twice_nat_id = twice_nat_id; + m_staticNatEntry[key].twice_nat_added = false; + m_staticNatEntry[key].binding_key = EMPTY_STRING; + SWSS_LOG_INFO("Static NAT %s is added to cache", key.c_str()); + + /* Add the new Static NAT entry */ + SWSS_LOG_INFO("Adding the Static NAT entry for %s", key.c_str()); + addStaticNatEntry(key); + + it = consumer.m_toSync.erase(it); + } + else if (op == DEL_COMMAND) + { + SWSS_LOG_INFO("Del command for %s", key.c_str()); + + /* Check the key is already present in cache */ + if (m_staticNatEntry.find(key) != m_staticNatEntry.end()) + { + /* Remove the Static NAT Entry */ + SWSS_LOG_INFO("Deleting the Static NAT entry for %s", key.c_str()); + removeStaticNatEntry(key); + + /* Cleaned the cache */ + SWSS_LOG_INFO("Static NAT %s is removed from the cache", key.c_str()); + m_staticNatEntry.erase(key); + } + else + { + SWSS_LOG_ERROR("Invalid Static NAT %s from Config_db, do nothing", key.c_str()); + } + + it = consumer.m_toSync.erase(it); + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); + SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, t)).c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +/* To parse the received Static NAPT Table and save it to cache */ +void NatMgr::doStaticNaptTask(Consumer &consumer) +{ + auto it = consumer.m_toSync.begin(); + + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + string key = kfvKey(t), op = kfvOp(t); + vector keys = tokenize(key, config_db_key_delimiter); + const vector& data = kfvFieldsValues(t); + string global_ip, ipAddress; + string local_ip = EMPTY_STRING, local_port = EMPTY_STRING, interface = EMPTY_STRING; + string nat_type = EMPTY_STRING, twice_nat_id = EMPTY_STRING; + bool ipFound = false, portFound = false, natTypeFound = false, twiceNatFound = false, nonValueFound = false, isOverlap = false; + int ip_num = 0, port_num = 0, portValue = 0, nat_type_num = 0, twice_nat_num = 0, twice_nat_value; + uint32_t ipv4_addr; + + /* Example : Config_Db + * STATIC_NAPT|65.55.42.1|TCP|1024 + * local_ip: 10.0.0.1 + * local_port: 6000 + */ + + /* Ensure the key size is 3 otherwise ignore */ + if (keys.size() != STATIC_NAPT_KEY_SIZE) + { + SWSS_LOG_ERROR("Invalid key size %lu, skipping %s", keys.size(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the global ip format is x.x.x.x, otherwise ignore */ + if (inet_pton(AF_INET, keys[0].c_str(), &ipv4_addr) != 1) + { + SWSS_LOG_ERROR("Invalid global ip address format %s, skipping %s", keys[0].c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + ipv4_addr = ntohl(ipv4_addr); + /* Ensure the local ip is not Zero, Broadcast, Loopback, Multicast and Reserved address */ + if (IS_ZERO_ADDR(ipv4_addr) or IS_BROADCAST_ADDR(ipv4_addr) or IS_LOOPBACK_ADDR(ipv4_addr) or + IS_MULTICAST_ADDR(ipv4_addr) or IS_RESERVED_ADDR(ipv4_addr)) + { + SWSS_LOG_ERROR("Invalid global address %s, skipping %s", keys[0].c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the prototype is UDP or TCP otherwise ignore */ + if ((keys[1] != to_upper(IP_PROTOCOL_TCP)) and (keys[1] != to_upper(IP_PROTOCOL_UDP))) + { + SWSS_LOG_ERROR("Invalid ip prototype %s, skipping %s", keys[1].c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the given portValue is integer, otherwise ignore */ + try + { + portValue = stoi(keys[2]); + } + catch(...) + { + SWSS_LOG_ERROR("Invalid global port %s, skipping %s", keys[2].c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the global port is inbetween 1 to 65535, otherwise ignore */ + if ((portValue < L4_PORT_MIN) or (portValue > L4_PORT_MAX)) + { + SWSS_LOG_ERROR("Invalid global port value %d, skipping %s", portValue, key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Example : APPL_DB + * NAPT_TABLE:TCP:10.0.0.1:6000 + * translated_ip: 65.55.42.1 + * translated_port: 1024 + * nat_type: dnat + * entry_type: static + */ + + if (op == SET_COMMAND) + { + SWSS_LOG_INFO("Set command for %s", key.c_str()); + + /* Get the Config_db key values */ + for (auto idx : data) + { + const auto &field = fvField(idx); + const auto &value = fvValue(idx); + if (field == LOCAL_IP) + { + local_ip = value; + ipFound = true; + ip_num++; + } + else if (field == LOCAL_PORT) + { + local_port = value; + portFound = true; + port_num++; + } + else if (field == NAT_TYPE) + { + nat_type = value; + natTypeFound = true; + nat_type_num++; + } + else if (field == TWICE_NAT_ID) + { + twice_nat_id = value; + twiceNatFound = true; + twice_nat_num++; + } + else + { + nonValueFound = true; + } + } + + /* Ensure the local_ip value is valid otherwise ignore */ + if ((ipFound == true) and (ip_num != 1)) + { + SWSS_LOG_ERROR("Invalid local_ip value, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the local_port value is valid otherwise ignore */ + if ((portFound == true) and (port_num != 1)) + { + SWSS_LOG_ERROR("Invalid local_port value, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the nat_type value is valid otherwise ignore */ + if ((natTypeFound == true) and (nat_type_num != 1)) + { + SWSS_LOG_ERROR("Invalid nat_type value, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the twice_nat_id value is valid otherwise ignore */ + if ((twiceNatFound == true) and (twice_nat_num != 1)) + { + SWSS_LOG_ERROR("Invalid twice_nat_id value, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the non value is not present otherwise ignore */ + if (nonValueFound == true) + { + SWSS_LOG_ERROR("Invalid value, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the local ip format is x.x.x.x, otherwise ignore */ + if (inet_pton(AF_INET, local_ip.c_str(), &ipv4_addr) != 1) + { + SWSS_LOG_ERROR("Invalid local ip address format %s, skipping %s", local_ip.c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + ipv4_addr = ntohl(ipv4_addr); + /* Ensure the local ip is not Zero, Broadcast, Loopback, Multicast and Reserved address */ + if (IS_ZERO_ADDR(ipv4_addr) or IS_BROADCAST_ADDR(ipv4_addr) or IS_LOOPBACK_ADDR(ipv4_addr) or + IS_MULTICAST_ADDR(ipv4_addr) or IS_RESERVED_ADDR(ipv4_addr)) + { + SWSS_LOG_ERROR("Invalid local address %s, skipping %s", local_ip.c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the given local_port is integer, otherwise ignore */ + try + { + portValue = stoi(local_port); + } + catch(...) + { + SWSS_LOG_ERROR("Invalid local port %s, skipping %s", local_port.c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the local port is inbetween 1 to 65535, otherwise ignore */ + if ((portValue < L4_PORT_MIN) or (portValue > L4_PORT_MAX)) + { + SWSS_LOG_ERROR("Invalid internal port value %d, skipping %s", portValue, key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the nat_type value is snat or dnat otherwise ignore */ + if ((natTypeFound == true) and ((nat_type != SNAT_NAT_TYPE) and (nat_type != DNAT_NAT_TYPE))) + { + SWSS_LOG_ERROR("Invalid nat_type, it is neither snat or dnat, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + if (twiceNatFound == true) + { + /* Ensure the given twice_nat_id is integer, otherwise ignore */ + try + { + twice_nat_value = stoi(twice_nat_id); + } + catch(...) + { + SWSS_LOG_ERROR("Invalid twice_nat_id %s, skipping %s", twice_nat_id.c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the twice_nat_id is within the limits (1 to 9999), otherwise ignore */ + if ((twice_nat_value < TWICE_NAT_ID_MIN) or (twice_nat_value > TWICE_NAT_ID_MAX)) + { + SWSS_LOG_ERROR("Invalid twice_nat_id %d, not in limits, skipping %s", twice_nat_value, key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + } + + /* check the global address is overlapping with any NAT entry */ + global_ip = keys[0]; + if (nat_type == SNAT_NAT_TYPE) + { + global_ip = local_ip; + } + + for (auto it = m_staticNatEntry.begin(); it != m_staticNatEntry.end(); it++) + { + ipAddress = (*it).first; + if ((*it).second.nat_type == SNAT_NAT_TYPE) + { + ipAddress = (*it).second.local_ip; + } + + if (ipAddress == global_ip) + { + isOverlap = true; + break; + } + } + + if (isOverlap) + { + SWSS_LOG_ERROR("Global Ip overlaps with static NAT entry, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Check the key is already present in cache */ + if (m_staticNaptEntry.find(key) != m_staticNaptEntry.end()) + { + SWSS_LOG_INFO("Static NAPT %s exists", key.c_str()); + if ((m_staticNaptEntry[key].local_ip == local_ip) and + (m_staticNaptEntry[key].local_port == local_port)) + { + /* Received the same Key and value, ignore */ + SWSS_LOG_ERROR("Duplicate Static NAPT and it's values, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + else + { + SWSS_LOG_INFO("Static NAPT %s with updated info", key.c_str()); + + /* Received key with new value */ + if (m_staticNatEntry[key].interface != NONE_STRING) + { + /* Remove the Static NAPT Entry */ + SWSS_LOG_INFO("Deleting the Static NAPT entry for %s", key.c_str()); + removeStaticNaptEntry(key); + } + } + } + + /* New Key, Add it to cache */ + m_staticNaptEntry[key].interface = NONE_STRING; + m_staticNaptEntry[key].local_ip = local_ip; + m_staticNaptEntry[key].local_port = local_port; + if (nat_type.empty()) + { + m_staticNaptEntry[key].nat_type = DNAT_NAT_TYPE; + } + else + { + m_staticNaptEntry[key].nat_type = nat_type; + } + m_staticNaptEntry[key].twice_nat_id = twice_nat_id; + m_staticNaptEntry[key].twice_nat_added = false; + m_staticNaptEntry[key].binding_key = EMPTY_STRING; + SWSS_LOG_INFO("Static NAPT %s is added to cache", key.c_str()); + + /* Add the new Static NAT entry */ + SWSS_LOG_INFO("Adding the Static NAPT entry for %s", key.c_str()); + addStaticNaptEntry(key); + + it = consumer.m_toSync.erase(it); + } + else if (op == DEL_COMMAND) + { + SWSS_LOG_INFO("Del command for %s", key.c_str()); + + /* Check the key is already present in cache */ + if (m_staticNaptEntry.find(key) != m_staticNaptEntry.end()) + { + /* Remove the Static NAPT Entry */ + SWSS_LOG_INFO("Deleting the Static NAPT entry for %s", key.c_str()); + removeStaticNaptEntry(key); + + /* Cleaned the cache */ + SWSS_LOG_INFO("Static NAPT %s is removed from the cache", key.c_str()); + m_staticNaptEntry.erase(key); + } + else + { + SWSS_LOG_ERROR("Invalid Static NAPT %s from Config_db, do nothing", key.c_str()); + } + + it = consumer.m_toSync.erase(it); + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); + SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, t)).c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +/* To parse the received NAT Pool Table and save it to cache */ +void NatMgr::doNatPoolTask(Consumer &consumer) +{ + auto it = consumer.m_toSync.begin(); + + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + string key = kfvKey(t), op = kfvOp(t); + vector keys = tokenize(key, config_db_key_delimiter); + const vector& data = kfvFieldsValues(t); + string nat_ip = EMPTY_STRING, nat_port = EMPTY_STRING, binding_name = EMPTY_STRING, static_ip; + bool ipFound = false, portFound = false, nonValueFound = false, isOverlap = false; + int ip_num = 0, port_num = 0, portValue_low, portValue_high; + uint32_t ipv4_addr_low, ipv4_addr_high, static_address; + + /* Example : Config_Db + * NAT_POOL|PoolName + * nat_ip: 10.0.0.1-10.0.0.5 + * nat_port: 100-105 + */ + + /* Ensure the key size is 1 otherwise ignore */ + if (keys.size() != POOL_TABLE_KEY_SIZE) + { + SWSS_LOG_ERROR("Invalid key size %lu, skipping %s", keys.size(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + if (op == SET_COMMAND) + { + SWSS_LOG_INFO("Set command for %s", key.c_str()); + + /* Get the Config_db key values */ + for (auto idx : data) + { + const auto &field = fvField(idx); + const auto &value = fvValue(idx); + if (field == NAT_IP) + { + nat_ip = value; + ipFound = true; + ip_num++; + } + else if (field == NAT_PORT) + { + nat_port = value; + portFound = true; + port_num++; + } + else + { + nonValueFound = true; + } + } + + /* Ensure the "nat_ip" values are valid otherwise ignore */ + if (((ipFound == true) and (ip_num != 1)) or (ipFound == false)) + { + SWSS_LOG_ERROR("Invalid nat_ip values, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the "nat_port" values are valid otherwise ignore */ + if ((portFound == true) and (port_num != 1)) + { + SWSS_LOG_ERROR("Invalid key values, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the non value is not present, otherwise ignore */ + if (nonValueFound == true) + { + SWSS_LOG_ERROR("Invalid value, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the Pool name length is not more than 32 otherwise ignore */ + if (key.length() > 32) + { + SWSS_LOG_ERROR("Invalid pool name length - %lu, skipping %s", key.length(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the nat_ip is not empty */ + if (nat_ip.empty() or (nat_ip == "NULL")) + { + SWSS_LOG_ERROR("Invalid nat_ip, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + vector nat_ip_range = tokenize(nat_ip, range_specifier); + + /* Ensure the nat_ip_range is valid */ + if (nat_ip_range.empty()) + { + SWSS_LOG_ERROR("NAT pool ip range %s is not valid, skipping %s", nat_ip.c_str(), key.c_str()); + continue; + } + + /* Ensure the ip range size is not more than 2, otherwise ignore */ + if (nat_ip_range.size() > 2) + { + SWSS_LOG_ERROR("Invalid nat ip range size %lu, skipping %s", nat_ip_range.size(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + else if (nat_ip_range.size() == 2) + { + SWSS_LOG_INFO("Pool %s contains nat_ip range", key.c_str()); + + /* Ensure the ip format is x.x.x.x, otherwise ignore */ + if (inet_pton(AF_INET, nat_ip_range[1].c_str(), &ipv4_addr_high) != 1) + { + SWSS_LOG_ERROR("Invalid ip address format %s, skipping %s", nat_ip_range[1].c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + ipv4_addr_high = ntohl(ipv4_addr_high); + /* Ensure the ip address is not Zero, Broadcast, Loopback, Multicast and Reserved address */ + if (IS_ZERO_ADDR(ipv4_addr_high) or IS_BROADCAST_ADDR(ipv4_addr_high) or IS_LOOPBACK_ADDR(ipv4_addr_high) or + IS_MULTICAST_ADDR(ipv4_addr_high) or IS_RESERVED_ADDR(ipv4_addr_high)) + { + SWSS_LOG_ERROR("Invalid ip address %s, skipping %s", nat_ip_range[1].c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the ip format is x.x.x.x, otherwise ignore */ + if (inet_pton(AF_INET, nat_ip_range[0].c_str(), &ipv4_addr_low) != 1) + { + SWSS_LOG_ERROR("Invalid ip address format %s, skipping %s", nat_ip_range[0].c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + ipv4_addr_low = ntohl(ipv4_addr_low); + /* Ensure the ip address is not Zero, Broadcast, Loopback, Multicast and Reserved address */ + if (IS_ZERO_ADDR(ipv4_addr_low) or IS_BROADCAST_ADDR(ipv4_addr_low) or IS_LOOPBACK_ADDR(ipv4_addr_low) or + IS_MULTICAST_ADDR(ipv4_addr_low) or IS_RESERVED_ADDR(ipv4_addr_low)) + { + SWSS_LOG_ERROR("Invalid ip address %s, skipping %s", nat_ip_range[0].c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the ip range is proper */ + if (ipv4_addr_low >= ipv4_addr_high) + { + SWSS_LOG_ERROR("NAT pool ip range %s is not valid, skipping %s", nat_ip.c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + } + else + { + /* Ensure the ip format is x.x.x.x, otherwise ignore */ + if (inet_pton(AF_INET, nat_ip_range[0].c_str(), &ipv4_addr_low) != 1) + { + SWSS_LOG_ERROR("Invalid ip address format %s, skipping %s", nat_ip_range[0].c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + ipv4_addr_high = ntohl(ipv4_addr_low); + ipv4_addr_low = ntohl(ipv4_addr_low); + + /* Ensure the ip address is not Zero, Broadcast, Loopback, Multicast and Reserved address */ + if (IS_ZERO_ADDR(ipv4_addr_low) or IS_BROADCAST_ADDR(ipv4_addr_low) or IS_LOOPBACK_ADDR(ipv4_addr_low) or + IS_MULTICAST_ADDR(ipv4_addr_low) or IS_RESERVED_ADDR(ipv4_addr_low)) + { + SWSS_LOG_ERROR("Invalid ip address %s, skipping %s", nat_ip_range[0].c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + } + + /* Check the Pool table contains nat_port */ + if (!nat_port.empty() and (nat_port != "NULL")) + { + SWSS_LOG_INFO("Pool %s contains nat_port info", key.c_str()); + + vector nat_port_range = tokenize(nat_port, range_specifier); + + /* Ensure the port range size is not more than 2, otherwise ignore */ + if (nat_port_range.size() > 2) + { + SWSS_LOG_ERROR("Invalid nat port range size %lu, skipping %s", nat_port_range.size(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + else if (nat_port_range.size() == 2) + { + /* Ensure the given port is integer, otherwise ignore */ + try + { + portValue_low = stoi(nat_port_range[0]); + } + catch(...) + { + SWSS_LOG_ERROR("Invalid port %s, skipping %s", nat_port.c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the port is inbetween 1 to 65535, otherwise ignore */ + if ((portValue_low < L4_PORT_MIN) or (portValue_low > L4_PORT_MAX)) + { + SWSS_LOG_ERROR("Invalid port value %d, skipping %s", portValue_low, key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the given port is integer, otherwise ignore */ + try + { + portValue_high = stoi(nat_port_range[1]); + } + catch(...) + { + SWSS_LOG_ERROR("Invalid port %s, skipping %s", nat_port.c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the port is inbetween 1 to 65535, otherwise ignore */ + if ((portValue_high < L4_PORT_MIN) or (portValue_high > L4_PORT_MAX)) + { + SWSS_LOG_ERROR("Invalid port value %d, skipping %s", portValue_high, key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + if (portValue_low >= portValue_high) + { + SWSS_LOG_ERROR("Invalid nat port range %s, skipping %s", nat_port.c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + } + else + { + /* Ensure the given port is integer, otherwise ignore */ + try + { + portValue_low = stoi(nat_port_range[0]); + } + catch(...) + { + SWSS_LOG_ERROR("Invalid port %s, skipping %s", nat_port.c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the port is inbetween 1 to 65535, otherwise ignore */ + if ((portValue_low < L4_PORT_MIN) or (portValue_low > L4_PORT_MAX)) + { + SWSS_LOG_ERROR("Invalid port value %d, skipping %s", portValue_low, key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + } + } + + /* check the pool ip address is overlapping with any NAT entry */ + for (auto it = m_staticNatEntry.begin(); it != m_staticNatEntry.end(); it++) + { + static_ip = (*it).first; + if ((*it).second.nat_type == SNAT_NAT_TYPE) + { + static_ip = (*it).second.local_ip; + } + + inet_pton(AF_INET, static_ip.c_str(), &static_address); + static_address = ntohl(static_address); + + if ((static_address >= ipv4_addr_low) and (static_address <= ipv4_addr_high)) + { + isOverlap = true; + break; + } + } + + if (isOverlap) + { + SWSS_LOG_ERROR("Pool Ip address is overlaps with static NAT entry, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Check the key is already present in cache */ + if (m_natPoolInfo.find(key) != m_natPoolInfo.end()) + { + SWSS_LOG_INFO("Pool %s exists", key.c_str()); + + if ((m_natPoolInfo[key].ip_range == nat_ip) and (m_natPoolInfo[key].port_range == nat_port)) + { + /* Received the same Key and value, ignore */ + SWSS_LOG_ERROR("Duplicate Pool and it's values, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + else + { + SWSS_LOG_INFO("Pool %s with updated info", key.c_str()); + + /* Check this pool name on any nat binding */ + if (isPoolMappedtoBinding(key, binding_name)) + { + /* Remove the existing Dynamic NAT rules */ + SWSS_LOG_INFO("Deleting the Dynamic NAT/NAPT iptables rules for %s", key.c_str()); + removeDynamicNatRule(binding_name); + } + } + } + + /* New Key, Add it to cache */ + m_natPoolInfo[key].ip_range = nat_ip; + if (!nat_port.empty() and (nat_port != "NULL")) + { + m_natPoolInfo[key].port_range = nat_port; + } + else + { + m_natPoolInfo[key].port_range = EMPTY_STRING; + } + + /* Check this pool name on any nat binding */ + if (isPoolMappedtoBinding(key, binding_name)) + { + SWSS_LOG_INFO("Pool %s info is added to the cache", key.c_str()); + + /* Add the new Dynamic Nat Rules */ + SWSS_LOG_INFO("Adding the Dynamic NAT rules for %s", key.c_str()); + addDynamicNatRule(binding_name); + } + else + { + SWSS_LOG_INFO("Pool %s is not yet binded, saved it to cache", key.c_str()); + } + + it = consumer.m_toSync.erase(it); + } + else if (op == DEL_COMMAND) + { + SWSS_LOG_INFO("Del command for %s", key.c_str()); + + string binding_name = EMPTY_STRING; + + /* Check the key is already present in cache */ + if (m_natPoolInfo.find(key) != m_natPoolInfo.end()) + { + /* Check this pool name on any nat binding */ + if (isPoolMappedtoBinding(key, binding_name)) + { + SWSS_LOG_INFO("Pool %s is mapped on binding %s, deleting the dynamic iptables rules", key.c_str(), binding_name.c_str()); + /* Remove the existing Dynamic NAT rules */ + removeDynamicNatRule(binding_name); + } + + /* Clean the pool Info */ + m_natPoolInfo.erase(key); + SWSS_LOG_INFO("Pool %s is cleaned from the cache", key.c_str()); + } + else + { + SWSS_LOG_ERROR("Invalid NAT Pool %s from Config_db, do nothing", key.c_str()); + } + + it = consumer.m_toSync.erase(it); + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); + SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, t)).c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +/* To parse the received NAT Binding Table and save it to cache */ +void NatMgr::doNatBindingTask(Consumer &consumer) +{ + auto it = consumer.m_toSync.begin(); + + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + string key = kfvKey(t), op = kfvOp(t); + vector keys = tokenize(key, config_db_key_delimiter); + const vector& data = kfvFieldsValues(t); + string nat_pool = EMPTY_STRING, nat_acl = EMPTY_STRING; + string nat_type = NONE_STRING, twice_nat_id = EMPTY_STRING; + bool poolFound = false, aclFound = false, natTypeFound = false, twiceNatFound = false, nonValueFound = false; + int pool_num = 0, acl_num = 0, nat_type_num = 0, twice_nat_num = 0, twice_nat_value; + + /* Example : + * NAT_BINDINGS|bindingName + * nat_pool: poolName + * access_list: aclName,aclName2 + */ + + /* Ensure the key size is 1 otherwise ignore */ + if (keys.size() != BINDING_TABLE_KEY_SIZE) + { + SWSS_LOG_ERROR("Invalid key size %lu, skipping %s", keys.size(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the Binding name length is not more than 32 otherwise ignore */ + if (key.length() > 32) + { + SWSS_LOG_ERROR("Invalid binding name length - %lu, skipping %s", key.length(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + if (op == SET_COMMAND) + { + SWSS_LOG_INFO("Set command for %s", key.c_str()); + + /* Get the Config_db key values */ + for (auto idx : data) + { + const auto &field = fvField(idx); + const auto &value = fvValue(idx); + if (field == NAT_POOL) + { + nat_pool = value; + poolFound = true; + pool_num++; + } + else if (field == NAT_ACLS) + { + nat_acl = value; + aclFound = true; + acl_num++; + } + else if (field == NAT_TYPE) + { + nat_type = value; + natTypeFound = true; + nat_type_num++; + } + else if (field == TWICE_NAT_ID) + { + twice_nat_id = value; + twiceNatFound = true; + twice_nat_num++; + } + else + { + nonValueFound = true; + } + } + + /* Ensure the nat_pool value is valid, otherwise ignore */ + if ((poolFound == true) and (pool_num != 1)) + { + SWSS_LOG_ERROR("Invalid nat_pool values, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the access_list value is valid, otherwise ignore */ + if ((aclFound == true) and (acl_num != 1)) + { + SWSS_LOG_ERROR("Invalid access_list values, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the nat_type value is valid otherwise ignore */ + if ((natTypeFound == true) and (nat_type_num != 1)) + { + SWSS_LOG_ERROR("Invalid nat_type value, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the twice_nat_id value is valid otherwise ignore */ + if ((twiceNatFound == true) and (twice_nat_num != 1)) + { + SWSS_LOG_ERROR("Invalid twice_nat_id value, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the non value is not present, otherwise ignore */ + if (nonValueFound == true) + { + SWSS_LOG_ERROR("Invalid value, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the nat_type value is snat otherwise ignore */ + if ((natTypeFound == true) and (nat_type != SNAT_NAT_TYPE)) + { + SWSS_LOG_ERROR("Invalid nat_type %s, skipping %s", nat_type.c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + if (twice_nat_id == "NULL") + { + twiceNatFound = false; + twice_nat_id = EMPTY_STRING; + } + + if (twiceNatFound == true) + { + /* Ensure the given twice_nat_id is integer, otherwise ignore */ + try + { + twice_nat_value = stoi(twice_nat_id); + } + catch(...) + { + SWSS_LOG_ERROR("Invalid twice_nat_id %s, skipping %s", twice_nat_id.c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the twice_nat_id is within the limits (1 to 9999), otherwise ignore */ + if ((twice_nat_value < TWICE_NAT_ID_MIN) or (twice_nat_value > TWICE_NAT_ID_MAX)) + { + SWSS_LOG_ERROR("Invalid twice_nat_id %d, not in limits, skipping %s", twice_nat_value, key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + } + + /* Ensure the Pool name length is not more than 32 otherwise ignore */ + if (nat_pool.length() > 32) + { + SWSS_LOG_ERROR("Invalid pool name length - %lu, skipping %s", nat_pool.length(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Check the key is already present in cache */ + if (m_natBindingInfo.find(key) != m_natBindingInfo.end()) + { + SWSS_LOG_INFO("Binding %s exists", key.c_str()); + if ((m_natBindingInfo[key].pool_name == nat_pool) and (m_natBindingInfo[key].acl_name == nat_acl)) + { + /* Received the same Key and value, ignore */ + SWSS_LOG_ERROR("Duplicate Binding and it's values, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + else + { + /* Remove the existing Dynamic NAT rules */ + SWSS_LOG_INFO("Deleting the Dynamic NAT rules for %s", key.c_str()); + removeDynamicNatRule(key); + } + } + + /* New Key, Add it to cache */ + m_natBindingInfo[key].pool_name = nat_pool; + m_natBindingInfo[key].acl_name = nat_acl; + m_natBindingInfo[key].pool_interface = NONE_STRING; + m_natBindingInfo[key].acl_interface = NONE_STRING; + m_natBindingInfo[key].twice_nat_id = twice_nat_id; + m_natBindingInfo[key].twice_nat_added = false; + if (nat_type.empty()) + { + m_natBindingInfo[key].nat_type = SNAT_NAT_TYPE; + } + else + { + m_natBindingInfo[key].nat_type = nat_type; + } + m_natBindingInfo[key].static_key = EMPTY_STRING; + SWSS_LOG_INFO("Binding Info %s is added to the cache", key.c_str()); + + /* Add the new Dynamic Nat Rules */ + SWSS_LOG_INFO("Adding the Dynamic NAT rules for %s", key.c_str()); + addDynamicNatRule(key); + + it = consumer.m_toSync.erase(it); + } + else if (op == DEL_COMMAND) + { + SWSS_LOG_INFO("Del command for %s", key.c_str()); + + /* Check the key is already present in cache */ + if (m_natBindingInfo.find(key) != m_natBindingInfo.end()) + { + /* Remove the existing Dynamic NAT rules */ + SWSS_LOG_INFO("Deleting the Dynamic NAT rules for %s", key.c_str()); + removeDynamicNatRule(key); + + /* Clean the binding info */ + m_natBindingInfo.erase(key); + SWSS_LOG_INFO("Binding Info %s is cleaned from the cache", key.c_str()); + } + else + { + SWSS_LOG_ERROR("Invalid NAT Binding %s from Config_Db, do nothing", key.c_str()); + } + + it = consumer.m_toSync.erase(it); + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); + SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, t)).c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +/* To parse the received NAT Global Table and save it to cache */ +void NatMgr::doNatGlobalTask(Consumer &consumer) +{ + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + auto &t = it->second; + string key = kfvKey(t), op = kfvOp(t), adminMode; + bool tcpFound = false, udpFound = false, timeoutFound = false; + bool nonValueFound = false, adminModeFound = false; + int tcp_timeout = 0, udp_timeout = 0, timeout = 0; + int tcp_num = 0, udp_num = 0, timeout_num = 0, admin_mode_num = 0; + + /* Example : Config_DB + * NAT_GLOBAL|Values + * admin_mode: disabled + * nat_timeout: 600 + * nat_tcp_timeout: 300 + * nat_udp_timeout: 50 + */ + + /* Ensure the key is "Values" otherwise ignore */ + if (strcmp(key.c_str(), VALUES)) + { + SWSS_LOG_ERROR("Invalid key %s format. No Values", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Example : APPL_DB + * NAT_GLOBAL_TABLE:Values + * admin_mode: disabled + * nat_timeout : 600 + * nat_tcp_timeout : 300 + * nat_udp_timeout : 50 + */ + + if (op == SET_COMMAND) + { + SWSS_LOG_INFO("Set command for %s", key.c_str()); + + /* Create APPL_DB key */ + string appKey = VALUES; + vector fvVector; + + /* Get the Config_db values */ + for (auto i : kfvFieldsValues(t)) + { + if (fvField(i) == NAT_ADMIN_MODE) + { + adminMode = fvValue(i); + adminModeFound = true; + admin_mode_num++; + } + else if (fvField(i) == NAT_TCP_TIMEOUT) + { + /* Ensure the given tcp_timeout is integer, otherwise ignore */ + try + { + tcp_timeout = stoi(fvValue(i)); + } + catch(...) + { + SWSS_LOG_ERROR("Invalid tcp_timeout %s, skipping %s", fvValue(i).c_str(), key.c_str()); + continue; + } + tcpFound = true; + tcp_num++; + } + else if (fvField(i) == NAT_UDP_TIMEOUT) + { + /* Ensure the given udp_timeout is integer, otherwise ignore */ + try + { + udp_timeout = stoi(fvValue(i)); + } + catch(...) + { + SWSS_LOG_ERROR("Invalid udp_timeout %s, skipping %s", fvValue(i).c_str(), key.c_str()); + continue; + } + udpFound = true; + udp_num++; + } + else if (fvField(i) == NAT_TIMEOUT) + { + /* Ensure the given timeout is integer, otherwise ignore */ + try + { + timeout = stoi(fvValue(i)); + } + catch(...) + { + SWSS_LOG_ERROR("Invalid timeout %s, skipping %s", fvValue(i).c_str(), key.c_str()); + continue; + } + timeoutFound = true; + timeout_num++; + } + else + { + nonValueFound = true; + } + } + + /* Ensure the admin_mode value is valid otherwise ignore */ + if ((adminModeFound == true) and (admin_mode_num != 1)) + { + SWSS_LOG_ERROR("Invalid admin_mode, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the nat_tcp_timeout values is valid otherwise ignore */ + if ((tcpFound == true) and (tcp_num != 1)) + { + SWSS_LOG_ERROR("Invalid nat_tcp_timeout, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the nat_udp_timeout value is valid otherwise ignore */ + if ((udpFound == true) and (udp_num != 1)) + { + SWSS_LOG_ERROR("Invalid nat_udp_timeout, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the nat_timeout value is valid otherwise ignore */ + if ((timeoutFound == true) and (timeout_num != 1)) + { + SWSS_LOG_ERROR("Invalid nat_timeout, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the value is valid otherwise ignore */ + if (((tcpFound == false) and (udpFound == false) and (timeoutFound == false) and (adminModeFound == false)) or + (nonValueFound == true)) + { + SWSS_LOG_ERROR("Invalid, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the admin_mode is enabled/disabled, otherwise ignore */ + if ((adminModeFound == true) and ((adminMode != ENABLED) and (adminMode != DISABLED))) + { + SWSS_LOG_ERROR("Invalid admin_mode value %s, skipping %s", adminMode.c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the tcp timeout is inbetween 300 to 432000, otherwise ignore */ + if ((tcpFound == true) and ((tcp_timeout < NAT_TCP_TIMEOUT_MIN) or (tcp_timeout > NAT_TCP_TIMEOUT_MAX))) + { + SWSS_LOG_ERROR("Invalid tcp timeout value %d, skipping %s", tcp_timeout, key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the udp timeout is inbetween 120 to 600, otherwise ignore */ + if ((udpFound == true) and ((udp_timeout < NAT_UDP_TIMEOUT_MIN) or (udp_timeout > NAT_UDP_TIMEOUT_MAX))) + { + SWSS_LOG_ERROR("Invalid udp timeout value %d, skipping %s", udp_timeout, key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the timeout is inbetween 300 to 432000, otherwise ignore */ + if ((timeoutFound == true) and ((timeout < NAT_TIMEOUT_MIN) or (timeout > NAT_TIMEOUT_MAX))) + { + SWSS_LOG_ERROR("Invalid timeout value %d, skipping %s", timeout, key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + if ((tcpFound == true) and (tcp_timeout != m_natTcpTimeout)) + { + m_natTcpTimeout = tcp_timeout; + SWSS_LOG_INFO("NAT TCP Timeout %s is added to cache", key.c_str()); + if (isNatEnabled()) + { + FieldValueTuple s(NAT_TCP_TIMEOUT, std::to_string(tcp_timeout)); + fvVector.push_back(s); + } + } + + if ((udpFound == true) and (udp_timeout != m_natUdpTimeout)) + { + m_natUdpTimeout = udp_timeout; + SWSS_LOG_INFO("NAT UDP Timeout %s is added to cache", key.c_str()); + if (isNatEnabled()) + { + FieldValueTuple s(NAT_UDP_TIMEOUT, std::to_string(udp_timeout)); + fvVector.push_back(s); + } + } + + if ((timeoutFound == true) and (timeout != m_natTimeout)) + { + m_natTimeout = timeout; + SWSS_LOG_INFO("NAT Timeout %s is added to cache", key.c_str()); + if (isNatEnabled()) + { + FieldValueTuple s(NAT_TIMEOUT, std::to_string(timeout)); + fvVector.push_back(s); + } + } + + if ((isNatEnabled() == true) and ((timeoutFound == true) or (tcpFound == true) or (udpFound == true))) + { + m_appNatGlobalTableProducer.set(appKey, fvVector); + SWSS_LOG_INFO("Added NAT Values %s to APPL_DB", key.c_str()); + } + + if ((adminModeFound == true) and (adminMode != natAdminMode)) + { + if (adminMode == ENABLED) + { + SWSS_LOG_INFO("NAT Admin Mode enabled is added to cache"); + natAdminMode = adminMode; + enableNatFeature(); + } + else + { + disableNatFeature(); + natAdminMode = adminMode; + SWSS_LOG_INFO("NAT Admin Mode disabled is added to cache"); + } + } + } + else if (op == DEL_COMMAND) + { + /* Create APPL_DB key */ + string appKey = VALUES; + vector fvVector; + + /* Set NAT default timeout as 600 seconds */ + m_natTimeout = NAT_TIMEOUT_DEFAULT; + + /* Set NAT default tcp timeout as 86400 seconds (1 Day) */ + m_natTcpTimeout = NAT_TCP_TIMEOUT_DEFAULT; + + /* Set NAT default udp timeout as 300 seconds */ + m_natUdpTimeout = NAT_UDP_TIMEOUT_DEFAULT; + + if (natAdminMode == ENABLED) + { + FieldValueTuple p(NAT_TIMEOUT, std::to_string(m_natTimeout)); + FieldValueTuple q(NAT_UDP_TIMEOUT, std::to_string(m_natUdpTimeout)); + FieldValueTuple r(NAT_TCP_TIMEOUT, std::to_string(m_natTcpTimeout)); + fvVector.push_back(p); + fvVector.push_back(q); + fvVector.push_back(r); + m_appNatGlobalTableProducer.set(appKey, fvVector); + + disableNatFeature(); + natAdminMode = DISABLED; + SWSS_LOG_INFO("NAT Admin Mode disabled is added to cache"); + } + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); + } + + it = consumer.m_toSync.erase(it); + } +} + +/* To parse the received L3 Interface Table and save it to cache */ +void NatMgr::doNatIpInterfaceTask(Consumer &consumer) +{ + auto it = consumer.m_toSync.begin(); + + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + string key = kfvKey(t), nat_zone = "1"; + vector keys = tokenize(kfvKey(t), config_db_key_delimiter); + string op = kfvOp(t), port(keys[0]); + bool skipAddition = false, skipDeletion = false; + int prefixLen = 0, nat_zone_value = 1; + vector ipPrefixKeys; + + /* Example : Config_DB + * INTERFACE|Ethernet28|10.0.0.1/24 + * or + * INTERFACE|Ethernet28 + * { + * nat_zone = "0" + * } + */ + + /* Ensure the key size is 2 or 1, otherwise ignore */ + if ((keys.size() != L3_INTERFACE_KEY_SIZE) and (keys.size() != L3_INTERFACE_ZONE_SIZE)) + { + SWSS_LOG_INFO("Invalid key size %lu, skipping %s", keys.size(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + else + { + SWSS_LOG_INFO("Key size %lu for %s", keys.size(), key.c_str()); + } + + /* Ensure the key starts with "Vlan" or "Ethernet" or "PortChannel" or "Loopback", otherwise ignore */ + if ((strncmp(keys[0].c_str(), VLAN_PREFIX, strlen(VLAN_PREFIX))) and + (strncmp(keys[0].c_str(), ETHERNET_PREFIX, strlen(ETHERNET_PREFIX))) and + (strncmp(keys[0].c_str(), LOOPBACK_PREFIX, strlen(LOOPBACK_PREFIX))) and + (strncmp(keys[0].c_str(), LAG_PREFIX, strlen(LAG_PREFIX)))) + { + SWSS_LOG_INFO("Invalid key %s format, skipping %s", keys[0].c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + if (keys.size() == L3_INTERFACE_KEY_SIZE) + { + ipPrefixKeys = tokenize(keys[1], ip_address_delimiter); + + /* Ensure the ipPrefix key size is 2 otherwise ignore */ + if (ipPrefixKeys.size() != IP_PREFIX_SIZE) + { + SWSS_LOG_INFO("Invalid IpPrefix size %s, skipping %s", keys[1].c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the ip address is ipv4, otherwise ignore */ + try + { + IpAddress ipAddress(ipPrefixKeys[0]); + + if (!ipAddress.isV4()) + { + /* Ignore the IPv6 addresses */ + SWSS_LOG_INFO("IPv6 address is not supported, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + } + catch(...) + { + SWSS_LOG_INFO("Invalid ip address %s format, skipping %s", ipPrefixKeys[0].c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the given PrefixLen is integer, otherwise ignore */ + try + { + prefixLen = stoi(ipPrefixKeys[1]); + } + catch(...) + { + SWSS_LOG_ERROR("Invalid ip mask len %s, skipping %s", ipPrefixKeys[1].c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the ip mask len is valid, otherwise ignore */ + if ((prefixLen < IP_ADDR_MASK_LEN_MIN) or (prefixLen > IP_ADDR_MASK_LEN_MAX)) + { + SWSS_LOG_INFO("Invalid ip mask len %s, skipping %s", ipPrefixKeys[1].c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + } + + if (op == SET_COMMAND) + { + SWSS_LOG_INFO("Set command for %s", key.c_str()); + + /* + * Don't proceed if Port/LAG/VLAN is not ready yet. + * The pending task will be checked periodically and retried. + */ + if ((strncmp(keys[0].c_str(), LOOPBACK_PREFIX, strlen(LOOPBACK_PREFIX))) and + (!isPortStateOk(port))) + { + SWSS_LOG_INFO("Port is not ready, skipping %s", port.c_str()); + it++; + continue; + } + + if (keys.size() == L3_INTERFACE_ZONE_SIZE) + { + const vector& data = kfvFieldsValues(t); + + /* Get the Config_db key values */ + for (auto idx : data) + { + if (fvField(idx) == NAT_ZONE) + { + /* Ensure the given nat_zone is integer, otherwise ignore */ + try + { + nat_zone_value = stoi(fvValue(idx)); + } + catch(...) + { + SWSS_LOG_ERROR("Invalid nat_zone %s, skipping %s", fvValue(idx).c_str(), key.c_str()); + continue; + } + + /* Add plus 1 to avoid adding default zero mark in iptables mangle table */ + nat_zone_value++; + nat_zone = to_string(nat_zone_value); + break; + } + } + + /* Check the key is present in zone_interface cache */ + if (m_natZoneInterfaceInfo.find(port) != m_natZoneInterfaceInfo.end()) + { + /* Check the nat_zone is same or not */ + if (m_natZoneInterfaceInfo[port] != nat_zone) + { + /* Delete the mangle iptables rules for non-loopback interface */ + if (strncmp(keys[0].c_str(), LOOPBACK_PREFIX, strlen(LOOPBACK_PREFIX))) + { + setMangleIptablesRules(DELETE, port, m_natZoneInterfaceInfo[port]); + } + + /* Check the port is present in ip_interface cache */ + if (m_natIpInterfaceInfo.find(keys[0]) != m_natIpInterfaceInfo.end()) + { + /* Delete the Static NAT and NAPT iptables rules */ + SWSS_LOG_INFO("Deleting Static NAT iptables rules for %s", key.c_str()); + removeStaticNatIptables(keys[0]); + + SWSS_LOG_INFO("Deleting Static NAPT iptables rules for %s", key.c_str()); + removeStaticNaptIptables(keys[0]); + + /* Delete the Dynamic NAT rules */ + SWSS_LOG_INFO("Deleting Dynamic NAT rules for %s", key.c_str()); + removeDynamicNatRules(keys[0]); + } + + m_natZoneInterfaceInfo[port] = nat_zone; + + /* Add the mangle iptables rules for non-loopback interface */ + if (strncmp(keys[0].c_str(), LOOPBACK_PREFIX, strlen(LOOPBACK_PREFIX))) + { + setMangleIptablesRules(ADD, port, nat_zone); + } + + /* Check the port is present in ip_interface cache */ + if (m_natIpInterfaceInfo.find(keys[0]) != m_natIpInterfaceInfo.end()) + { + /* Add the Static NAT and NAPT iptables rules */ + SWSS_LOG_INFO("Adding Static NAT iptables rules for %s", key.c_str()); + addStaticNatIptables(keys[0]); + + SWSS_LOG_INFO("Adding Static NAPT iptables rules for %s", key.c_str()); + addStaticNaptIptables(keys[0]); + + /* Add the Dynamic NAT rules */ + SWSS_LOG_INFO("Adding Dynamic NAT rules for %s", key.c_str()); + addDynamicNatRules(keys[0]); + } + } + else + { + SWSS_LOG_INFO("Received same nat_zone %s, skipping %s", nat_zone.c_str(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + } + else + { + m_natZoneInterfaceInfo[port] = nat_zone; + + /* Add the mangle iptables rules for non-loopback interface */ + if (strncmp(keys[0].c_str(), LOOPBACK_PREFIX, strlen(LOOPBACK_PREFIX))) + { + setMangleIptablesRules(ADD, port, nat_zone); + } + } + } + else if (keys.size() == L3_INTERFACE_KEY_SIZE) + { + /* + * Don't proceed if Interface is not ready yet. + * The pending task will be checked periodically and retried. + */ + if (!isIntfStateOk(key)) + { + SWSS_LOG_INFO("Interface is not ready, skipping %s", key.c_str()); + it++; + continue; + } + + /* Check the key is present in ip_interface cache */ + if (m_natIpInterfaceInfo.find(port) != m_natIpInterfaceInfo.end()) + { + if (m_natIpInterfaceInfo[port].find(keys[1]) != m_natIpInterfaceInfo[port].end()) + { + SWSS_LOG_INFO("Duplicate Ip Interface, skipping %s", key.c_str()); + skipAddition = true; + } + else + { + for (auto it = m_natIpInterfaceInfo[port].begin(); it != m_natIpInterfaceInfo[port].end(); it++) + { + IpPrefix entry(keys[1]), prefix(*it); + + if (prefix.isAddressInSubnet(entry.getIp())) + { + SWSS_LOG_INFO("IP Address %s belongs to existing subnet, skipped adding entries", ipPrefixKeys[0].c_str()); + skipAddition = true; + break; + } + } + + m_natIpInterfaceInfo[port].insert(keys[1]); + SWSS_LOG_INFO("Ip Interface %s is added to the existing Port cache", key.c_str()); + } + } + else + { + m_natIpInterfaceInfo[port].insert(keys[1]); + SWSS_LOG_INFO("Ip Interface %s is added to the Port cache", key.c_str()); + } + + if (!skipAddition) + { + /* Add the Static NAT and NAPT entries */ + SWSS_LOG_INFO("Adding Static NAT entries for %s", key.c_str()); + addStaticNatEntries(keys[0], keys[1]); + + SWSS_LOG_INFO("Adding Static NAPT entries for %s", key.c_str()); + addStaticNaptEntries(keys[0], keys[1]); + + /* Add the Dynamic NAT rules */ + SWSS_LOG_INFO("Adding Dynamic NAT rules for %s", key.c_str()); + addDynamicNatRules(keys[0], keys[1]); + } + } + else + { + SWSS_LOG_INFO("Invalid key size %lu, skipping %s", keys.size(), key.c_str()); + } + it = consumer.m_toSync.erase(it); + } + else if (op == DEL_COMMAND) + { + SWSS_LOG_INFO("Del command for %s", key.c_str()); + + if (keys.size() == L3_INTERFACE_ZONE_SIZE) + { + /* Check the key is present in zone_interface cache*/ + if (m_natZoneInterfaceInfo.find(port) != m_natZoneInterfaceInfo.end()) + { + /* Set the mangle iptables rules for non-loopback interface */ + if (strncmp(keys[0].c_str(), LOOPBACK_PREFIX, strlen(LOOPBACK_PREFIX))) + { + setMangleIptablesRules(DELETE, port, m_natZoneInterfaceInfo[port]); + } + + SWSS_LOG_INFO("Nat Zone %s for Interface %s is cleaned from the cache", m_natZoneInterfaceInfo[port].c_str(), key.c_str()); + m_natZoneInterfaceInfo.erase(port); + } + else + { + SWSS_LOG_INFO("Zone Interface is not present in cache, skipping %s", key.c_str()); + } + } + else if (keys.size() == L3_INTERFACE_KEY_SIZE) + { + /* Check the key is present in ip_interface cache*/ + if (m_natIpInterfaceInfo.find(port) != m_natIpInterfaceInfo.end()) + { + if (m_natIpInterfaceInfo[port].find(keys[1]) != m_natIpInterfaceInfo[port].end()) + { + for (auto ipPrefix = m_natIpInterfaceInfo[port].begin(); ipPrefix != m_natIpInterfaceInfo[port].end(); ipPrefix++) + { + if (*ipPrefix == keys[1]) + { + continue; + } + + IpPrefix entry(keys[1]), prefix(*ipPrefix); + + if (prefix.isAddressInSubnet(entry.getIp())) + { + SWSS_LOG_INFO("IP Address %s belongs to existing subnet, skipping deleting the entries", ipPrefixKeys[0].c_str()); + skipDeletion = true; + break; + } + } + + if (!skipDeletion) + { + /* Delete the Static NAT and NAPT entries */ + SWSS_LOG_INFO("Deleting Static NAT entries for %s", key.c_str()); + removeStaticNatEntries(keys[0], keys[1]); + + SWSS_LOG_INFO("Deleting Static NAPT entries for %s", key.c_str()); + removeStaticNaptEntries(keys[0], keys[1]); + + /* Delete the Dynamic NAT rules */ + SWSS_LOG_INFO("Deleting Dynamic NAT rules for %s", key.c_str()); + removeDynamicNatRules(keys[0], keys[1]); + } + + m_natIpInterfaceInfo[port].erase(keys[1]); + SWSS_LOG_INFO("Ip Interface %s is cleaned from the existing Port cache", keys[1].c_str()); + + if (m_natIpInterfaceInfo[port].empty()) + { + m_natIpInterfaceInfo.erase(port); + SWSS_LOG_INFO("Ip Interface %s is cleaned from the cache", key.c_str()); + } + } + else + { + SWSS_LOG_INFO("Ip Interface %s from Config_Db not in cache, do nothing", keys[1].c_str()); + } + } + else + { + SWSS_LOG_INFO("Invalid Ip Interface %s from Config_Db, do nothing", key.c_str()); + } + } + else + { + SWSS_LOG_INFO("Invalid key size %lu, skipping %s", keys.size(), key.c_str()); + } + it = consumer.m_toSync.erase(it); + } + else + { + SWSS_LOG_INFO("Unknown operation type %s", op.c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +/* To parse the received ACL Table and save it to cache */ +void NatMgr::doNatAclTableTask(Consumer &consumer) +{ + auto it = consumer.m_toSync.begin(); + + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + string key = kfvKey(t), op = kfvOp(t), ports; + vector keys = tokenize(key, config_db_key_delimiter); + const vector& data = kfvFieldsValues(t); + bool isNatAclNotValid = false; + + /* Example : Config_DB + * ACL_TABLE|Table-Id + * policy_desc: nat_acl + * stage: INGRESS + * type: l3 + * ports: Ethernet10 + */ + + /* Ensure the key size is 1 otherwise ignore */ + if (keys.size() != ACL_TABLE_KEY_SIZE) + { + SWSS_LOG_INFO("Invalid key size %lu, skipping %s", keys.size(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + if (op == SET_COMMAND) + { + SWSS_LOG_INFO("Set command for %s", key.c_str()); + + /* Get the Config_db key values */ + for (auto idx : data) + { + const auto &field = to_upper(fvField(idx)); + const auto &value = fvValue(idx); + if (field == TABLE_TYPE) + { + /* Ensure the Table type is L3, otherwise ignore */ + if (to_upper(value) != TABLE_TYPE_L3) + { + isNatAclNotValid = true; + SWSS_LOG_INFO("Invalid table type %s, skipping %s", value.c_str(), key.c_str()); + break; + } + } + else if (field == TABLE_STAGE) + { + /* Ensure the Table stage is Ingress, otherwise ignore */ + if (to_upper(value) != TABLE_STAGE_INGRESS) + { + isNatAclNotValid = true; + SWSS_LOG_INFO("Invalid stage %s, skipping %s", value.c_str(), key.c_str()); + break; + } + } + else if (field == TABLE_PORTS) + { + ports = value; + } + } + + /* Ensure the Table ports starts with "Vlan" or "Ethernet" or "PortChannel" otherwise ignore */ + vector ports_list = tokenize(ports, comma); + for (string port : ports_list) + { + /* Ensure the key starts with "Vlan" or "Ethernet" or "PortChannel" otherwise ignore */ + if ((strncmp(port.c_str(), VLAN_PREFIX, strlen(VLAN_PREFIX))) and + (strncmp(port.c_str(), ETHERNET_PREFIX, strlen(ETHERNET_PREFIX))) and + (strncmp(port.c_str(), LAG_PREFIX, strlen(LAG_PREFIX)))) + { + SWSS_LOG_INFO("Invalid Port %s format, skipping %s", ports.c_str(), key.c_str()); + isNatAclNotValid = true; + break; + } + } + + /* Ensure the ACL Table is valid for NAT, otherwise ignore */ + if (isNatAclNotValid == true) + { + SWSS_LOG_INFO("Not a valid ACL Table for NAT, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Check the key is already present in cache */ + if (m_natAclTableInfo.find(key) != m_natAclTableInfo.end()) + { + SWSS_LOG_INFO("ACL Table %s exists, skipping", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* New Key, Add it to cache */ + m_natAclTableInfo[key] = ports; + SWSS_LOG_INFO("ACL Table Info %s is added to cache", key.c_str()); + + /* Add the new Dynamic Nat Rules */ + SWSS_LOG_INFO("Adding Dynamic NAT rules for %s", key.c_str()); + addDynamicNatRuleByAcl(key); + + it = consumer.m_toSync.erase(it); + } + else if (op == DEL_COMMAND) + { + SWSS_LOG_INFO("Del command for %s", key.c_str()); + + /* Check the key is already present in cache */ + if (m_natAclTableInfo.find(key) != m_natAclTableInfo.end()) + { + /* Remove the existing Dynamic NAT rules */ + SWSS_LOG_INFO("Deleting Dynamic NAT rules for %s", key.c_str()); + removeDynamicNatRuleByAcl(key); + + /* Clean the ACL Table info */ + m_natAclTableInfo.erase(key); + SWSS_LOG_INFO("ACL Table Info %s is cleaned from cache", key.c_str()); + } + else + { + SWSS_LOG_INFO("Invalid ACL Table %s from Config_Db, do nothing", key.c_str()); + } + + it = consumer.m_toSync.erase(it); + } + else + { + SWSS_LOG_INFO("Unknown operation type %s", op.c_str()); + SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, t)).c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +/* To parse the received ACL Rule Table and save it to cache */ +void NatMgr::doNatAclRuleTask(Consumer &consumer) +{ + auto it = consumer.m_toSync.begin(); + + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + string key = kfvKey(t), op = kfvOp(t); + vector keys = tokenize(key, config_db_key_delimiter); + const vector& data = kfvFieldsValues(t); + string aclSrcIpAddress = NONE_STRING, aclDstIpAddress = NONE_STRING, aclIpProtocol = NONE_STRING; + string aclSrcPort = NONE_STRING, aclDstPort = NONE_STRING, aclPacketAction; + bool isNatAclRuleNotValid = false, isPacketActionSet = false; + uint32_t ipv4_addr, aclPriority = 0; + uint8_t ip_protocol; + + /* Example : Config_DB + * ACL_Rule|Table-Id|Rule-Id + * priority: 55 + * ip_type: ipv4any + * src_ip: 10.10.0.26/32 + * dst_ip: 10.10.1.26/32 + * packet_action: forward + */ + + /* Ensure the key size is 2 otherwise ignore */ + if (keys.size() != ACL_RULE_TABLE_KEY_SIZE) + { + SWSS_LOG_INFO("Invalid key size %lu, skipping %s", keys.size(), key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + if (op == SET_COMMAND) + { + SWSS_LOG_INFO("Set command for %s", key.c_str()); + + /* Get the Config_db key values */ + for (auto idx : data) + { + const auto &field = to_upper(fvField(idx)); + const auto &value = fvValue(idx); + + if (field == ACTION_PACKET_ACTION) + { + /* Ensure the packet action is valid, otherwise ignore */ + if ((to_upper(value) != PACKET_ACTION_FORWARD) and (to_upper(value) != PACKET_ACTION_DO_NOT_NAT)) + { + isNatAclRuleNotValid = true; + SWSS_LOG_INFO("Invalid packet action %s for Packet Action Field, skipping %s", value.c_str(), key.c_str()); + break; + } + isPacketActionSet = true; + aclPacketAction = to_upper(value); + } + else if (field == MATCH_IP_TYPE) + { + /* Ensure the ip type is valid, otherwise ignore */ + if ((to_upper(value) != IP_TYPE_IP) and (to_upper(value) != IP_TYPE_IPv4ANY)) + { + isNatAclRuleNotValid = true; + SWSS_LOG_INFO("Invalid ip type %s for Matching IP Type Field, skipping %s", value.c_str(), key.c_str()); + break; + } + } + else if ((field == MATCH_SRC_IP) or (field == MATCH_DST_IP)) + { + vector acl_ip = tokenize(value, ip_address_delimiter); + + /* Ensure the ip format is x.x.x.x, otherwise ignore */ + if (inet_pton(AF_INET, acl_ip[0].c_str(), &ipv4_addr) != 1) + { + SWSS_LOG_INFO("Invalid ip address %s format for Matching IP Field, skipping %s", value.c_str(), key.c_str()); + isNatAclRuleNotValid = true; + break; + } + + /* Ensure the ip address is non-zero, otherwise ignore */ + if (ipv4_addr == 0) + { + SWSS_LOG_INFO("Invalid ip address %s for Matching IP Field, skipping %s", value.c_str(), key.c_str()); + isNatAclRuleNotValid = true; + break; + } + + if (acl_ip.size() > 1) + { + /* Ensure the mask length is valid otherwise ignore */ + if ((stoi(acl_ip[1]) < IP_ADDR_MASK_LEN_MIN) or (stoi(acl_ip[1]) > IP_ADDR_MASK_LEN_MAX)) + { + SWSS_LOG_INFO("Invalid ip address mask length for Matching IP Field, skipping %s", key.c_str()); + isNatAclRuleNotValid = true; + break; + } + } + + if (field == MATCH_SRC_IP) + { + aclSrcIpAddress = value; + } + else if (field == MATCH_DST_IP) + { + aclDstIpAddress = value; + } + } + else if (field == MATCH_IP_PROTOCOL) + { + ip_protocol = to_uint(value); + + /* Ensure the ip protocol is TCP, UDP or ICMP, otherwise ignore */ + if (ip_protocol == MATCH_IP_PROTOCOL_TCP) + { + aclIpProtocol = IP_PROTOCOL_TCP; + } + else if (ip_protocol == MATCH_IP_PROTOCOL_UDP) + { + aclIpProtocol = IP_PROTOCOL_UDP; + } + else if (ip_protocol == MATCH_IP_PROTOCOL_ICMP) + { + aclIpProtocol = IP_PROTOCOL_ICMP; + } + else + { + SWSS_LOG_INFO("Invalid ip protocol %d for Matching Ip Protocol Field, skipping %s", ip_protocol, key.c_str()); + isNatAclRuleNotValid = true; + break; + } + } + else if ((field == MATCH_L4_SRC_PORT) or (field == MATCH_L4_DST_PORT)) + { + /* Ensure the port is inbetween 1 to 65535, otherwise ignore */ + if ((stoi(value) < L4_PORT_MIN) or (stoi(value) > L4_PORT_MAX)) + { + SWSS_LOG_INFO("Invalid port value %s for Matching Port Field, skipping %s", value.c_str(), key.c_str()); + isNatAclRuleNotValid = true; + break; + } + + if (field == MATCH_L4_SRC_PORT) + { + aclSrcPort = value; + } + else if (field == MATCH_L4_DST_PORT) + { + aclDstPort = value; + } + } + else if ((field == MATCH_L4_SRC_PORT_RANGE) or (field == MATCH_L4_DST_PORT_RANGE)) + { + vector port_range = tokenize(value, range_specifier); + + /* Ensure the port range size is valid, otherwise ignore */ + if (port_range.size() != L4_PORT_RANGE_SIZE) + { + SWSS_LOG_INFO("Invalid port range size %lu for Matching Port Range Field, skipping %s", port_range.size(), key.c_str()); + isNatAclRuleNotValid = true; + break; + } + + /* Ensure the port is inbetween 1 to 65535, otherwise ignore */ + if ((stoi(port_range[0]) < L4_PORT_MIN) or (stoi(port_range[1]) > L4_PORT_MAX)) + { + SWSS_LOG_INFO("Invalid port range %s for Matching Port Range Field, skipping %s", value.c_str(), key.c_str()); + isNatAclRuleNotValid = true; + break; + } + + if (field == MATCH_L4_SRC_PORT_RANGE) + { + aclSrcPort = value; + } + else if (field == MATCH_L4_DST_PORT_RANGE) + { + aclDstPort = value; + } + } + else if (field == RULE_PRIORITY) + { + aclPriority = stoi(value); + } + else + { + isNatAclRuleNotValid = true; + break; + } + } + + /* Ensure the ACL Table Rule is valid for NAT, otherwise ignore */ + if (isNatAclRuleNotValid == true) + { + SWSS_LOG_INFO("Not a valid ACL Rule for NAT, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Ensure the ACL Rule has packet action for NAT, otherwise ignore */ + if (isPacketActionSet == false) + { + SWSS_LOG_INFO("Packet action is missing for NAT ACL, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + /* Check the key is already present in cache */ + if (m_natAclRuleInfo.find(key) != m_natAclRuleInfo.end()) + { + /* Remove the existing Dynamic NAT rules */ + SWSS_LOG_INFO("Deleting Dynamic NAT rules for %s", key.c_str()); + removeDynamicNatRuleByAcl(key, true); + + /* Clean the ACL Rule info */ + m_natAclRuleInfo.erase(key); + SWSS_LOG_INFO("ACL Rule Info %s is cleaned from cache", key.c_str()); + } + + /* Save the ACL Rule info to cache */ + m_natAclRuleInfo[key].packet_action = aclPacketAction; + m_natAclRuleInfo[key].priority = aclPriority; + m_natAclRuleInfo[key].src_ip_range = aclSrcIpAddress; + m_natAclRuleInfo[key].dst_ip_range = aclDstIpAddress; + m_natAclRuleInfo[key].src_l4_port_range = aclSrcPort; + m_natAclRuleInfo[key].dst_l4_port_range = aclDstPort; + m_natAclRuleInfo[key].ip_protocol = aclIpProtocol; + SWSS_LOG_INFO("ACL Rule Info %s is added to cache", key.c_str()); + + /* Add the new Dynamic Nat Rules */ + SWSS_LOG_INFO("Adding Dynamic NAT rules for %s", key.c_str()); + addDynamicNatRuleByAcl(key, true); + + it = consumer.m_toSync.erase(it); + } + else if (op == DEL_COMMAND) + { + SWSS_LOG_INFO("Del command for %s", key.c_str()); + + /* Check the key is already present in cache */ + if (m_natAclRuleInfo.find(key) != m_natAclRuleInfo.end()) + { + /* Remove the existing Dynamic NAT rules */ + SWSS_LOG_INFO("Deleting Dynamic NAT rules for %s", key.c_str()); + removeDynamicNatRuleByAcl(key, true); + + /* Clean the ACL Rule info */ + m_natAclRuleInfo.erase(key); + SWSS_LOG_INFO("ACL Rule Info %s is cleaned from cache", key.c_str()); + } + else + { + SWSS_LOG_INFO("Invalid ACL Rule %s from Config_Db, do nothing", key.c_str()); + } + + it = consumer.m_toSync.erase(it); + } + else + { + SWSS_LOG_INFO("Unknown operation type %s", op.c_str()); + SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, t)).c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +/* To parse the received Table Task */ +void NatMgr::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + string table_name = consumer.getTableName(); + + if (table_name == CFG_STATIC_NAT_TABLE_NAME) + { + SWSS_LOG_INFO("Received update from CFG_STATIC_NAT_TABLE_NAME"); + doStaticNatTask(consumer); + } + else if (table_name == CFG_STATIC_NAPT_TABLE_NAME) + { + SWSS_LOG_INFO("Received update from CFG_STATIC_NAPT_TABLE_NAME"); + doStaticNaptTask(consumer); + } + else if (table_name == CFG_NAT_POOL_TABLE_NAME) + { + SWSS_LOG_INFO("Received update from CFG_NAT_POOL_TABLE_NAME"); + doNatPoolTask(consumer); + } + else if (table_name == CFG_NAT_BINDINGS_TABLE_NAME) + { + SWSS_LOG_INFO("Received update from CFG_NAT_BINDINGS_TABLE_NAME"); + doNatBindingTask(consumer); + } + else if (table_name == CFG_NAT_GLOBAL_TABLE_NAME) + { + SWSS_LOG_INFO("Received update from CFG_NAT_GLOBAL_TABLE_NAME"); + doNatGlobalTask(consumer); + } + else if ((table_name == CFG_INTF_TABLE_NAME) || (table_name == CFG_LAG_INTF_TABLE_NAME) || + (table_name == CFG_VLAN_INTF_TABLE_NAME) || (table_name == CFG_LOOPBACK_INTERFACE_TABLE_NAME)) + { + SWSS_LOG_INFO("Received update from CFG_INTF_TABLE_NAME"); + doNatIpInterfaceTask(consumer); + } + else if (table_name == CFG_ACL_TABLE_TABLE_NAME) + { + SWSS_LOG_INFO("Received update from CFG_ACL_TABLE_TABLE_NAME"); + doNatAclTableTask(consumer); + } + else if (table_name == CFG_ACL_RULE_TABLE_NAME) + { + SWSS_LOG_INFO("Received update from CFG_ACL_RULE_TABLE_NAME"); + doNatAclRuleTask(consumer); + } + else + { + SWSS_LOG_ERROR("Unknown config table %s ", table_name.c_str()); + throw runtime_error("NatMgr doTask failure."); + } +} + diff --git a/cfgmgr/natmgr.h b/cfgmgr/natmgr.h new file mode 100644 index 0000000000..8f6222a98f --- /dev/null +++ b/cfgmgr/natmgr.h @@ -0,0 +1,350 @@ +/* + * Copyright 2019 Broadcom Inc. + * + * 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. + */ + +#ifndef __NATMGR__ +#define __NATMGR__ + +#include "dbconnector.h" +#include "producerstatetable.h" +#include "orch.h" +#include "notificationproducer.h" +#include +#include +#include +#include + +namespace swss { + +#define STATIC_NAT_KEY_SIZE 1 +#define LOCAL_IP "local_ip" +#define TRANSLATED_IP "translated_ip" +#define NAT_TYPE "nat_type" +#define SNAT_NAT_TYPE "snat" +#define DNAT_NAT_TYPE "dnat" +#define TWICE_NAT_ID "twice_nat_id" +#define TWICE_NAT_ID_MIN 1 +#define TWICE_NAT_ID_MAX 9999 +#define ENTRY_TYPE "entry_type" +#define STATIC_ENTRY_TYPE "static" +#define DYNAMIC_ENTRY_TYPE "dynamic" +#define STATIC_NAPT_KEY_SIZE 3 +#define LOCAL_PORT "local_port" +#define TRANSLATED_L4_PORT "translated_l4_port" +#define TRANSLATED_SRC_IP "translated_src_ip" +#define TRANSLATED_SRC_L4_PORT "translated_src_l4_port" +#define TRANSLATED_DST_IP "translated_dst_ip" +#define TRANSLATED_DST_L4_PORT "translated_dst_l4_port" +#define POOL_TABLE_KEY_SIZE 1 +#define NAT_IP "nat_ip" +#define NAT_PORT "nat_port" +#define BINDING_TABLE_KEY_SIZE 1 +#define NAT_POOL "nat_pool" +#define NAT_ACLS "access_list" +#define VALUES "Values" +#define NAT_ADMIN_MODE "admin_mode" +#define NAT_ZONE "nat_zone" +#define NAT_TIMEOUT "nat_timeout" +#define NAT_TIMEOUT_MIN 300 +#define NAT_TIMEOUT_MAX 432000 +#define NAT_TIMEOUT_DEFAULT 600 +#define NAT_TCP_TIMEOUT "nat_tcp_timeout" +#define NAT_TCP_TIMEOUT_MIN 300 +#define NAT_TCP_TIMEOUT_MAX 432000 +#define NAT_TCP_TIMEOUT_DEFAULT 86400 +#define NAT_UDP_TIMEOUT "nat_udp_timeout" +#define NAT_UDP_TIMEOUT_MIN 120 +#define NAT_UDP_TIMEOUT_MAX 600 +#define NAT_UDP_TIMEOUT_DEFAULT 300 +#define L3_INTERFACE_KEY_SIZE 2 +#define L3_INTERFACE_ZONE_SIZE 1 +#define VLAN_PREFIX "Vlan" +#define LAG_PREFIX "PortChannel" +#define ETHERNET_PREFIX "Ethernet" +#define LOOPBACK_PREFIX "Loopback" +#define ACL_TABLE_KEY_SIZE 1 +#define TABLE_TYPE "TYPE" +#define TABLE_STAGE "STAGE" +#define TABLE_PORTS "PORTS" +#define TABLE_TYPE_L3 "L3" +#define TABLE_STAGE_INGRESS "INGRESS" +#define ACL_RULE_TABLE_KEY_SIZE 2 +#define ACTION_PACKET_ACTION "PACKET_ACTION" +#define PACKET_ACTION_FORWARD "FORWARD" +#define PACKET_ACTION_DO_NOT_NAT "DO_NOT_NAT" +#define MATCH_IP_TYPE "IP_TYPE" +#define IP_TYPE_IP "IP" +#define IP_TYPE_IPv4ANY "IPV4ANY" +#define RULE_PRIORITY "PRIORITY" +#define MATCH_SRC_IP "SRC_IP" +#define MATCH_DST_IP "DST_IP" +#define MATCH_IP_PROTOCOL "IP_PROTOCOL" +#define MATCH_IP_PROTOCOL_ICMP 1 +#define MATCH_IP_PROTOCOL_TCP 6 +#define MATCH_IP_PROTOCOL_UDP 17 +#define MATCH_L4_SRC_PORT "L4_SRC_PORT" +#define MATCH_L4_DST_PORT "L4_DST_PORT" +#define MATCH_L4_SRC_PORT_RANGE "L4_SRC_PORT_RANGE" +#define MATCH_L4_DST_PORT_RANGE "L4_DST_PORT_RANGE" +#define IP_PREFIX_SIZE 2 +#define IP_ADDR_MASK_LEN_MIN 1 +#define IP_ADDR_MASK_LEN_MAX 32 +#define IP_PROTOCOL_ICMP "icmp" +#define IP_PROTOCOL_TCP "tcp" +#define IP_PROTOCOL_UDP "udp" +#define L4_PORT_MIN 1 +#define L4_PORT_MAX 65535 +#define L4_PORT_RANGE_SIZE 2 +#define EMPTY_STRING "" +#define NONE_STRING "None" +#define ADD "A" +#define INSERT "I" +#define DELETE "D" +#define ENABLED "enabled" +#define DISABLED "disabled" +#define IS_LOOPBACK_ADDR(ipaddr) ((ipaddr & 0xFF000000) == 0x7F000000) +#define IS_MULTICAST_ADDR(ipaddr) ((ipaddr >= 0xE0000000) and (ipaddr <= 0xEFFFFFFF)) +#define IS_RESERVED_ADDR(ipaddr) (ipaddr >= 0xF0000000) +#define IS_ZERO_ADDR(ipaddr) (ipaddr == 0) +#define IS_BROADCAST_ADDR(ipaddr) (ipaddr == 0xFFFFFFFF) + +const char ip_address_delimiter = '/'; + +/* Pool Info */ +typedef struct { + std::string ip_range; + std::string port_range; +} natPool_t; + +/* Binding Info */ +typedef struct { + std::string pool_name; + std::string acl_name; + std::string nat_type; + std::string twice_nat_id; + std::string pool_interface; + std::string acl_interface; + std::string static_key; + bool twice_nat_added; +} natBinding_t; + +/* Static NAT Entry Info */ +typedef struct { + std::string local_ip; + std::string nat_type; + std::string twice_nat_id; + std::string interface; + std::string binding_key; + bool twice_nat_added; +} staticNatEntry_t; + +/* Static NAPT Entry Info */ +typedef struct { + std::string local_ip; + std::string local_port; + std::string nat_type; + std::string twice_nat_id; + std::string interface; + std::string binding_key; + bool twice_nat_added; +} staticNaptEntry_t; + +/* NAT ACL Table Rules Info */ +typedef struct{ + std::string packet_action; + uint32_t priority; + std::string src_ip_range; + std::string dst_ip_range; + std::string src_l4_port_range; + std::string dst_l4_port_range; + std::string ip_protocol; +} natAclRule_t; + +/* Containers to store NAT Info */ + +/* To store NAT Pool configuration, + * Key is "Pool_name" + * Value is "natPool_t" + */ +typedef std::map natPool_map_t; + +/* To store NAT Binding configuration, + * Key is "Binding_name" + * Value is "natBinding_t" + */ +typedef std::map natBinding_map_t; + +/* To store Static NAT configuration, + * Key is "Global_ip" (Eg. 65.55.45.1) + * Value is "staticNatEntry_t" + */ +typedef std::map staticNatEntry_map_t; + +/* To store Static NAPT configuration, + * Key is "Global_ip|ip_protocol|Global_port" (Eg. 65.55.45.1|TCP|500) + * Value is "staticNaptEntry_t" + */ +typedef std::map staticNaptEntry_map_t; + +/* To store NAT Ip Interface configuration, + * Key is "Port" (Eg. Ethernet1) + * Value is "ip_address_list" (Eg. 10.0.0.1/24,20.0.0.1/24) + */ +typedef std::map> natIpInterface_map_t; + +/* To store NAT ACL Table configuration, + * Key is "ACL_Table_Id" (Eg. 1) + * Value is "ports" (Eg. Ethernet4,Vlan10) + */ +typedef std::map natAclTable_map_t; + +/* To store NAT ACL Rules configuration, + * Key is "ACL_Tabel_Id|ACL_Rule_Id" (Eg. 1|1) + * Value is "natAclRule_t" + */ +typedef std::map natAclRule_map_t; + +/* To store NAT Zone Interface configuration, + * Key is "Port" (Eg. Ethernet1) + * Value is "nat_zone" (Eg. "1") + */ +typedef std::map natZoneInterface_map_t; + +/* Define NatMgr Class inherited from Orch Class */ +class NatMgr : public Orch +{ +public: + /* NatMgr Constructor */ + NatMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, const std::vector &tableNames); + using Orch::doTask; + + /* Function to be called from signal handler on nat docker stop */ + void cleanupPoolIpTable(); + void cleanupMangleIpTables(); + bool isPortInitDone(DBConnector *app_db); + +private: + /* Declare APPL_DB, CFG_DB and STATE_DB tables */ + ProducerStateTable m_appNatTableProducer, m_appNaptTableProducer, m_appNatGlobalTableProducer; + ProducerStateTable m_appTwiceNatTableProducer, m_appTwiceNaptTableProducer; + Table m_statePortTable, m_stateLagTable, m_stateVlanTable, m_stateInterfaceTable, m_appNaptPoolIpTable; + std::shared_ptr flushNotifier; + + /* Declare containers to store NAT Info */ + int m_natTimeout; + int m_natTcpTimeout; + int m_natUdpTimeout; + std::string natAdminMode; + + natPool_map_t m_natPoolInfo; + natBinding_map_t m_natBindingInfo; + staticNatEntry_map_t m_staticNatEntry; + staticNaptEntry_map_t m_staticNaptEntry; + natIpInterface_map_t m_natIpInterfaceInfo; + natZoneInterface_map_t m_natZoneInterfaceInfo; + natAclTable_map_t m_natAclTableInfo; + natAclRule_map_t m_natAclRuleInfo; + + /* Declare doTask related fucntions */ + void doTask(Consumer &consumer); + void doStaticNatTask(Consumer &consumer); + void doStaticNaptTask(Consumer &consumer); + void doNatPoolTask(Consumer &consumer); + void doNatBindingTask(Consumer &consumer); + void doNatGlobalTask(Consumer &consumer); + void doNatIpInterfaceTask(Consumer &consumer); + void doNatAclTableTask(Consumer &consumer); + void doNatAclRuleTask(Consumer &consumer); + + /* Declare all NAT functionality member functions*/ + void enableNatFeature(void); + void disableNatFeature(void); + void addConntrackSingleNatEntry(const std::string &key); + void addConntrackSingleNaptEntry(const std::string &key); + void deleteConntrackSingleNatEntry(const std::string &key); + void deleteConntrackSingleNaptEntry(const std::string &key); + void addConntrackTwiceNatEntry(const std::string &snatKey, const std::string &dnatKey); + void addConntrackTwiceNaptEntry(const std::string &snatKey, const std::string &dnatKey); + void deleteConntrackTwiceNatEntry(const std::string &snatKey, const std::string &dnatKey); + void deleteConntrackTwiceNaptEntry(const std::string &snatKey, const std::string &dnatKey); + void deleteConntrackDynamicEntries(const std::string &ip_range); + void addStaticNatEntry(const std::string &key); + void addStaticNaptEntry(const std::string &key); + void addStaticSingleNatEntry(const std::string &key); + void addStaticSingleNaptEntry(const std::string &key); + void addStaticSingleNatIptables(const std::string &key); + void addStaticSingleNaptIptables(const std::string &key); + void addStaticTwiceNatEntry(const std::string &key); + void addStaticTwiceNaptEntry(const std::string &key); + void addStaticTwiceNatIptables(const std::string &key); + void addStaticTwiceNaptIptables(const std::string &key); + void removeStaticNatEntry(const std::string &key); + void removeStaticNaptEntry(const std::string &key); + void removeStaticSingleNatEntry(const std::string &key); + void removeStaticSingleNaptEntry(const std::string &key); + void removeStaticSingleNatIptables(const std::string &key); + void removeStaticSingleNaptIptables(const std::string &key); + void removeStaticTwiceNatEntry(const std::string &key); + void removeStaticTwiceNaptEntry(const std::string &key); + void removeStaticTwiceNatIptables(const std::string &key); + void removeStaticTwiceNaptIptables(const std::string &key); + void addStaticNatEntries(const std::string port = NONE_STRING, const std::string ipPrefix = NONE_STRING); + void addStaticNaptEntries(const std::string port = NONE_STRING, const std::string ipPrefix = NONE_STRING); + void removeStaticNatEntries(const std::string port = NONE_STRING, const std::string ipPrefix = NONE_STRING); + void removeStaticNaptEntries(const std::string port= NONE_STRING, const std::string ipPrefix = NONE_STRING); + void addStaticNatIptables(const std::string port); + void addStaticNaptIptables(const std::string port); + void removeStaticNatIptables(const std::string port); + void removeStaticNaptIptables(const std::string port); + void addDynamicNatRule(const std::string &key); + void removeDynamicNatRule(const std::string &key); + void addDynamicNatRuleByAcl(const std::string &key, bool isRuleId = false); + void removeDynamicNatRuleByAcl(const std::string &key, bool isRuleId = false); + void addDynamicNatRules(const std::string port = NONE_STRING, const std::string ipPrefix = NONE_STRING); + void removeDynamicNatRules(const std::string port = NONE_STRING, const std::string ipPrefix = NONE_STRING); + void addDynamicTwiceNatRule(const std::string &key); + void deleteDynamicTwiceNatRule(const std::string &key); + void setDynamicAllForwardOrAclbasedRules(const std::string &opCmd, const std::string &pool_interface, const std::string &ip_range, + const std::string &port_range, const std::string &acls_name, const std::string &dynamicKey); + + bool isNatEnabled(void); + bool isPortStateOk(const std::string &alias); + bool isIntfStateOk(const std::string &alias); + bool isPoolMappedtoBinding(const std::string &pool_name, std::string &binding_name); + bool isMatchesWithStaticNat(const std::string &global_ip, std::string &local_ip); + bool isMatchesWithStaticNapt(const std::string &global_ip, std::string &local_ip); + bool isGlobalIpMatching(const std::string &intf_keys, const std::string &global_ip); + bool getIpEnabledIntf(const std::string &global_ip, std::string &interface); + void setNaptPoolIpTable(const std::string &opCmd, const std::string &nat_ip, const std::string &nat_port); + bool setFullConeDnatIptablesRule(const std::string &opCmd); + bool setMangleIptablesRules(const std::string &opCmd, const std::string &interface, const std::string &nat_zone); + bool setStaticNatIptablesRules(const std::string &opCmd, const std::string &interface, const std::string &external_ip, const std::string &internal_ip, const std::string &nat_type); + bool setStaticNaptIptablesRules(const std::string &opCmd, const std::string &interface, const std::string &prototype, const std::string &external_ip, + const std::string &external_port, const std::string &internal_ip, const std::string &internal_port, const std::string &nat_type); + bool setStaticTwiceNatIptablesRules(const std::string &opCmd, const std::string &interface, const std::string &src_ip, const std::string &translated_src_ip, + const std::string &dest_ip, const std::string &translated_dest_ip); + bool setStaticTwiceNaptIptablesRules(const std::string &opCmd, const std::string &interface, const std::string &prototype, const std::string &src_ip, const std::string &src_port, + const std::string &translated_src_ip, const std::string &translated_src_port, const std::string &dest_ip, const std::string &dest_port, + const std::string &translated_dest_ip, const std::string &translated_dest_port); + bool setDynamicNatIptablesRulesWithAcl(const std::string &opCmd, const std::string &interface, const std::string &external_ip, + const std::string &external_port_range, natAclRule_t &natAclRuleId, const std::string &static_key); + bool setDynamicNatIptablesRulesWithoutAcl(const std::string &opCmd, const std::string &interface, const std::string &external_ip, + const std::string &external_port_range, const std::string &static_key); + +}; + +} + +#endif diff --git a/cfgmgr/natmgrd.cpp b/cfgmgr/natmgrd.cpp new file mode 100644 index 0000000000..5a685d9f6c --- /dev/null +++ b/cfgmgr/natmgrd.cpp @@ -0,0 +1,173 @@ +/* + * Copyright 2019 Broadcom Inc. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "dbconnector.h" +#include "select.h" +#include "exec.h" +#include "schema.h" +#include "macaddress.h" +#include "producerstatetable.h" +#include "notificationproducer.h" +#include "natmgr.h" +#include "shellcmd.h" +#include "warm_restart.h" + +using namespace std; +using namespace swss; + +/* select() function timeout retry time, in millisecond */ +#define SELECT_TIMEOUT 1000 + +/* + * Following global variables are defined here for the purpose of + * using existing Orch class which is to be refactored soon to + * eliminate the direct exposure of the global variables. + * + * Once Orch class refactoring is done, these global variables + * should be removed from here. + */ +int gBatchSize = 0; +bool gSwssRecord = false; +bool gLogRotate = false; +ofstream gRecordOfs; +string gRecordFile; +mutex gDbMutex; +NatMgr *natmgr = NULL; + +std::shared_ptr cleanupNotifier; + +void sigterm_handler(int signo) +{ + int ret = 0; + std::string res; + const std::string iptablesFlushNat = "iptables -t nat -F"; + const std::string conntrackFlush = "conntrack -F"; + + SWSS_LOG_NOTICE("Got SIGTERM"); + + /*If there are any iptables and conntrack entries, clean them */ + ret = swss::exec(iptablesFlushNat, res); + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", iptablesFlushNat.c_str(), ret); + } + ret = swss::exec(conntrackFlush, res); + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", conntrackFlush.c_str(), ret); + } + + /* Send notification to Orchagent to clean up the REDIS and ASIC database */ + if (cleanupNotifier != NULL) + { + SWSS_LOG_NOTICE("Sending notification to orchagent to cleanup NAT entries in REDIS/ASIC"); + + std::vector entry; + + cleanupNotifier->send("nat_cleanup", "all", entry); + } + + if (natmgr) + { + natmgr->cleanupMangleIpTables(); + natmgr->cleanupPoolIpTable(); + } +} + +int main(int argc, char **argv) +{ + Logger::linkToDbNative("natmgrd"); + SWSS_LOG_ENTER(); + + SWSS_LOG_NOTICE("--- Starting natmgrd ---"); + + try + { + vector cfg_tables = { + CFG_STATIC_NAT_TABLE_NAME, + CFG_STATIC_NAPT_TABLE_NAME, + CFG_NAT_POOL_TABLE_NAME, + CFG_NAT_BINDINGS_TABLE_NAME, + CFG_NAT_GLOBAL_TABLE_NAME, + CFG_INTF_TABLE_NAME, + CFG_LAG_INTF_TABLE_NAME, + CFG_VLAN_INTF_TABLE_NAME, + CFG_LOOPBACK_INTERFACE_TABLE_NAME, + CFG_ACL_TABLE_TABLE_NAME, + CFG_ACL_RULE_TABLE_NAME + }; + + DBConnector cfgDb(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector appDb(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector stateDb(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + + cleanupNotifier = std::make_shared(&appDb, "NAT_DB_CLEANUP_NOTIFICATION"); + + if (signal(SIGTERM, sigterm_handler) == SIG_ERR) + { + SWSS_LOG_ERROR("failed to setup SIGTERM action handler"); + exit(1); + } + + natmgr = new NatMgr(&cfgDb, &appDb, &stateDb, cfg_tables); + + natmgr->isPortInitDone(&appDb); + + std::vector cfgOrchList = {natmgr}; + + swss::Select s; + for (Orch *o : cfgOrchList) + { + s.addSelectables(o->getSelectables()); + } + + SWSS_LOG_NOTICE("starting main loop"); + while (true) + { + Selectable *sel; + int ret; + + ret = s.select(&sel, SELECT_TIMEOUT); + if (ret == Select::ERROR) + { + SWSS_LOG_NOTICE("Error: %s!", strerror(errno)); + continue; + } + if (ret == Select::TIMEOUT) + { + natmgr->doTask(); + continue; + } + + auto *c = (Executor *)sel; + c->execute(); + } + } + catch(const std::exception &e) + { + SWSS_LOG_ERROR("Runtime error: %s", e.what()); + } + return -1; +} + diff --git a/cfgmgr/shellcmd.h b/cfgmgr/shellcmd.h index a8a7afa51c..31fd7e3270 100644 --- a/cfgmgr/shellcmd.h +++ b/cfgmgr/shellcmd.h @@ -12,6 +12,8 @@ #define GREP_CMD "/bin/grep" #define TEAMD_CMD "/usr/bin/teamd" #define TEAMDCTL_CMD "/usr/bin/teamdctl" +#define IPTABLES_CMD "/sbin/iptables" +#define CONNTRACK_CMD "/usr/sbin/conntrack" #define EXEC_WITH_ERROR_THROW(cmd, res) ({ \ int ret = swss::exec(cmd, res); \ From dc7e5f03977053f73bfd93c2bbb95861a44c04cc Mon Sep 17 00:00:00 2001 From: Guohan Lu Date: Thu, 30 Jan 2020 22:55:01 +0000 Subject: [PATCH 38/63] Natsyncd changes in sonic-swss submodule to support NAT feature. (#1126) --- .gitignore | 1 + Makefile.am | 2 +- configure.ac | 1 + natsyncd/Makefile.am | 16 + natsyncd/natsync.cpp | 996 ++++++++++++++++++++++++++++++ natsyncd/natsync.h | 112 ++++ natsyncd/natsyncd.cpp | 100 +++ neighsyncd/neighsync.cpp | 20 +- neighsyncd/neighsync.h | 5 +- neighsyncd/neighsyncd.cpp | 2 +- warmrestart/warmRestartAssist.cpp | 161 ++--- warmrestart/warmRestartAssist.h | 25 +- 12 files changed, 1348 insertions(+), 93 deletions(-) create mode 100644 natsyncd/Makefile.am create mode 100644 natsyncd/natsync.cpp create mode 100644 natsyncd/natsync.h create mode 100644 natsyncd/natsyncd.cpp diff --git a/.gitignore b/.gitignore index 8a8f46062b..2065bc635a 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,7 @@ cfgmgr/vxlanmgrd cfgmgr/natmgrd neighsyncd/neighsyncd portsyncd/portsyncd +natsyncd/natsyncd orchagent/orchagent orchagent/routeresync orchagent/orchagent_restart_check diff --git a/Makefile.am b/Makefile.am index 9f31144e40..4f1572daeb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = fpmsyncd neighsyncd portsyncd orchagent swssconfig cfgmgr tests +SUBDIRS = fpmsyncd neighsyncd portsyncd natsyncd orchagent swssconfig cfgmgr tests if HAVE_LIBTEAM SUBDIRS += teamsyncd diff --git a/configure.ac b/configure.ac index 04899b0249..7faa50b995 100644 --- a/configure.ac +++ b/configure.ac @@ -79,6 +79,7 @@ AC_CONFIG_FILES([ orchagent/Makefile fpmsyncd/Makefile neighsyncd/Makefile + natsyncd/Makefile portsyncd/Makefile teamsyncd/Makefile swssconfig/Makefile diff --git a/natsyncd/Makefile.am b/natsyncd/Makefile.am new file mode 100644 index 0000000000..1740d8b097 --- /dev/null +++ b/natsyncd/Makefile.am @@ -0,0 +1,16 @@ +INCLUDES = -I $(top_srcdir) -I $(top_srcdir)/warmrestart + +bin_PROGRAMS = natsyncd + +if DEBUG +DBGFLAGS = -ggdb -DDEBUG +else +DBGFLAGS = -g +endif + +natsyncd_SOURCES = natsyncd.cpp natsync.cpp $(top_srcdir)/warmrestart/warmRestartAssist.cpp + +natsyncd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) +natsyncd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) +natsyncd_LDADD = -lnl-3 -lnl-route-3 -lnl-nf-3 -lswsscommon + diff --git a/natsyncd/natsync.cpp b/natsyncd/natsync.cpp new file mode 100644 index 0000000000..1db37c5c26 --- /dev/null +++ b/natsyncd/natsync.cpp @@ -0,0 +1,996 @@ +/* + * Copyright 2019 Broadcom Inc. + * + * 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. + */ + +#include +#include +#include +#include + +#include "logger.h" +#include "dbconnector.h" +#include "producerstatetable.h" +#include "ipaddress.h" +#include "netmsg.h" +#include "linkcache.h" + +#include "natsync.h" +#include "warm_restart.h" + +using namespace std; +using namespace swss; + +#define NL_IP_ADDR(addrp) (((struct nl_ip_addr *)addrp)->a_addr) +#define IS_LOOPBACK_ADDR(ipaddr) ((ipaddr & 0xFF000000) == 0x7F000000) + +#define CT_UDP_EXPIRY_TIMEOUT 600 /* Max conntrack timeout in the user configurable range */ + +NatSync::NatSync(RedisPipeline *pipelineAppDB, DBConnector *appDb, DBConnector *stateDb, NfNetlink *nfnl) : + m_natTable(appDb, APP_NAT_TABLE_NAME), + m_naptTable(appDb, APP_NAPT_TABLE_NAME), + m_natTwiceTable(appDb, APP_NAT_TWICE_TABLE_NAME), + m_naptTwiceTable(appDb, APP_NAPT_TWICE_TABLE_NAME), + m_natCheckTable(appDb, APP_NAT_TABLE_NAME), + m_naptCheckTable(appDb, APP_NAPT_TABLE_NAME), + m_twiceNatCheckTable(appDb, APP_NAT_TWICE_TABLE_NAME), + m_twiceNaptCheckTable(appDb, APP_NAPT_TWICE_TABLE_NAME), + m_naptPoolCheckTable(appDb, APP_NAPT_POOL_IP_TABLE_NAME), + m_stateNatRestoreTable(stateDb, STATE_NAT_RESTORE_TABLE_NAME) +{ + nfsock = nfnl; + + m_AppRestartAssist = new AppRestartAssist(pipelineAppDB, "natsyncd", "nat", DEFAULT_NATSYNC_WARMSTART_TIMER); + if (m_AppRestartAssist) + { + m_AppRestartAssist->registerAppTable(APP_NAT_TABLE_NAME, &m_natTable); + m_AppRestartAssist->registerAppTable(APP_NAPT_TABLE_NAME, &m_naptTable); + m_AppRestartAssist->registerAppTable(APP_NAT_TWICE_TABLE_NAME, &m_natTwiceTable); + m_AppRestartAssist->registerAppTable(APP_NAPT_TWICE_TABLE_NAME, &m_naptTwiceTable); + } +} + +/* To check the port init is done or not */ +bool NatSync::isPortInitDone(DBConnector *app_db) +{ + bool portInit = 0; + long cnt = 0; + + while(!portInit) { + Table portTable(app_db, APP_PORT_TABLE_NAME); + std::vector tuples; + portInit = portTable.get("PortInitDone", tuples); + + if(portInit) + break; + sleep(1); + cnt++; + } + sleep(5); + SWSS_LOG_NOTICE("PORT_INIT_DONE : %d %ld", portInit, cnt); + return portInit; +} + +// Check if nat conntrack entries are restored in kernel +bool NatSync::isNatRestoreDone() +{ + SWSS_LOG_ENTER(); + + string value; + + m_stateNatRestoreTable.hget("Flags", "restored", value); + if (value == "true") + { + SWSS_LOG_NOTICE("Conntrack table restore for NAT entries to kernel is complete"); + return true; + } + return false; +} + +std::string ctStatusStr(uint32_t ct_status) +{ + string ct_status_str = ""; + + if (ct_status & IPS_EXPECTED) + ct_status_str += "EXPECTED,"; + + if (!(ct_status & IPS_SEEN_REPLY)) + ct_status_str += "NOREPLY,"; + + if (ct_status & IPS_ASSURED) + ct_status_str += "ASSURED,"; + + if (!(ct_status & IPS_CONFIRMED)) + ct_status_str += "NOTSENT,"; + + if (ct_status & IPS_SRC_NAT) + ct_status_str += "SNAT,"; + + if (ct_status & IPS_DST_NAT) + ct_status_str += "DNAT,"; + + if (ct_status & IPS_SEQ_ADJUST) + ct_status_str += "SEQADJUST,"; + + if (!(ct_status & IPS_SRC_NAT_DONE)) + ct_status_str += "SNAT_INIT,"; + + if (!(ct_status & IPS_DST_NAT_DONE)) + ct_status_str += "DNAT_INIT,"; + + if (ct_status & IPS_DYING) + ct_status_str += "DYING,"; + + if (ct_status & IPS_FIXED_TIMEOUT) + ct_status_str += "FIXED_TIMEOUT"; + + return ct_status_str; +} + +/* Parse the valid conntrack notifications that can be added to hardware NAT table */ +int NatSync::parseConnTrackMsg(const struct nfnl_ct *ct, struct naptEntry &entry) +{ + SWSS_LOG_ENTER(); + + string ct_status_str; + char proto_str[32] = {0}; + + /* Only IPv4 family connections are handled */ + if (nfnl_ct_get_family(ct) != AF_INET) + { + SWSS_LOG_DEBUG("Conntrack entry protocol is not AF_INET (%d)", entry.protocol); + return -1; + } + + /* If the connection is not subjected to either SNAT or DNAT, + * we are not interested in those connection entries. + */ + entry.ct_status = nfnl_ct_get_status(ct); + + if (! ((entry.ct_status & IPS_SRC_NAT_DONE) || (entry.ct_status & IPS_DST_NAT_DONE))) + { + SWSS_LOG_DEBUG("Conntrack entry is not SNAT or DNAT"); + return -1; + } + + entry.orig_src_ip = NL_IP_ADDR(nfnl_ct_get_src(ct, 0)); + entry.orig_dest_ip = NL_IP_ADDR(nfnl_ct_get_dst(ct, 0)); + entry.nat_src_ip = NL_IP_ADDR(nfnl_ct_get_dst(ct, 1)); + entry.nat_dest_ip = NL_IP_ADDR(nfnl_ct_get_src(ct, 1)); + + /* Ignore those conntrack entries that correspond to internal loopback socket + * connections in the system. ie., if source ip or destination ip are in 127.0.0.X. + * Ideally, such connections would not have been subjected to SNAT/DNAT and should + * have been ignored in the above check already. + */ + if (((IS_LOOPBACK_ADDR(ntohl(entry.orig_src_ip.getV4Addr()))) && + (IS_LOOPBACK_ADDR(ntohl(entry.orig_dest_ip.getV4Addr())))) || + ((IS_LOOPBACK_ADDR(ntohl(entry.nat_src_ip.getV4Addr()))) && + (IS_LOOPBACK_ADDR(ntohl(entry.nat_dest_ip.getV4Addr()))))) + { + SWSS_LOG_DEBUG("Conntrack entry is a loopback entry, ignoring it."); + return -1; + } + + entry.orig_src_l4_port = nfnl_ct_get_src_port(ct, 0); + entry.orig_dst_l4_port = nfnl_ct_get_dst_port(ct, 0); + entry.nat_src_l4_port = nfnl_ct_get_dst_port(ct, 1); + entry.nat_dst_l4_port = nfnl_ct_get_src_port(ct, 1); + + entry.protocol = nfnl_ct_get_proto(ct); + entry.conntrack_id = nfnl_ct_get_id(ct); + ct_status_str = ctStatusStr(nfnl_ct_get_status(ct)); + + nl_ip_proto2str(entry.protocol, proto_str, sizeof(proto_str)); + + if ((entry.protocol == IPPROTO_TCP) || (entry.protocol == IPPROTO_UDP)) + { + SWSS_LOG_INFO("Conntrack entry : protocol %s, src %s:%d, dst %s:%d, natted src %s:%d, dst %s:%d, CT status %s", + proto_str, entry.orig_src_ip.to_string().c_str(), entry.orig_src_l4_port, + entry.orig_dest_ip.to_string().c_str(), entry.orig_dst_l4_port, + entry.nat_src_ip.to_string().c_str(), entry.nat_src_l4_port, + entry.nat_dest_ip.to_string().c_str(), entry.nat_dst_l4_port, ct_status_str.c_str()); + } + else if (entry.protocol == IPPROTO_ICMP) + { + /* Don't add ICMP NAT entries to hardware */ + SWSS_LOG_INFO("Conntrack entry : protocol icmp, src %s, dst %s, icmp_type %d, code %d, icmp_id %d, \ + natted src %s, dst %s, icmp_type %d, code %d, icmp_id %d, CT status %s", + entry.orig_src_ip.to_string().c_str(), entry.orig_dest_ip.to_string().c_str(), + nfnl_ct_get_icmp_type(ct, 0), nfnl_ct_get_icmp_code(ct, 0), nfnl_ct_get_icmp_id(ct, 0), + entry.nat_src_ip.to_string().c_str(), entry.nat_dest_ip.to_string().c_str(), + nfnl_ct_get_icmp_type(ct, 1), nfnl_ct_get_icmp_code(ct, 1), nfnl_ct_get_icmp_id(ct, 1), + ct_status_str.c_str()); + + return -1; + } + if (! (entry.ct_status & IPS_CONFIRMED)) + { + SWSS_LOG_INFO("Conntrack entry is not CONFIRMED (not went out of the box, don't process them)"); + return -1; + } + return 0; +} + +/* Process the netfiliter conntrack notifications from the kernel via netlink */ +void NatSync::onMsg(int nlmsg_type, struct nl_object *obj) +{ + SWSS_LOG_ENTER(); + + struct nfnl_ct *ct = (struct nfnl_ct *)obj; + struct naptEntry napt; + + nlmsg_type = NFNL_MSG_TYPE(nlmsg_type); + + SWSS_LOG_DEBUG("Conntrack entry notification, msg type :%s (%d)", + (((nlmsg_type == IPCTNL_MSG_CT_NEW) ? "CT_NEW" : ((nlmsg_type == IPCTNL_MSG_CT_DELETE) ? "CT_DELETE" : "OTHER"))), + nlmsg_type); + + if ((nlmsg_type != IPCTNL_MSG_CT_NEW) && (nlmsg_type != IPCTNL_MSG_CT_DELETE)) + { + SWSS_LOG_DEBUG("Conntrack entry notification, msg type not NEW or DELETE, ignoring"); + + } + + /* Parse the conntrack notification from the kernel */ + if (-1 == parseConnTrackMsg(ct, napt)) + { + return; + } + + if (nlmsg_type == IPCTNL_MSG_CT_NEW) + { + if ((napt.protocol == IPPROTO_TCP) && (napt.ct_status & IPS_ASSURED)) + { + addNatEntry(ct, napt, 1); + } + else if (napt.protocol == IPPROTO_UDP) + { + if (0 == addNatEntry(ct, napt, 1)) + { + if (! (napt.ct_status & IPS_ASSURED)) + { + /* Update the connection tracking entry status to ASSURED for UDP connection. + * Since application takes care of timing it out, and we don't want the kernel + * to age the UDP entries prematurely. + */ + napt.ct_status |= (IPS_SEEN_REPLY | IPS_ASSURED); + + nfnl_ct_set_status(ct, napt.ct_status); + nfnl_ct_set_timeout(ct, CT_UDP_EXPIRY_TIMEOUT); + + updateConnTrackEntry(ct); + } + } + } + } + else if ((nlmsg_type == IPCTNL_MSG_CT_DELETE) && (napt.ct_status & IPS_ASSURED)) + { + /* Delete only ASSURED NAT entries from APP_DB */ + addNatEntry(ct, napt, 0); + } +} + +/* Conntrack notifications from the kernel don't have a flag to indicate if the + * NAT is NAPT or basic NAT. The original L4 port and the translated L4 port may + * be the same and still can be the NAPT (can happen if the original L4 port is + * already in the L4 port pool range). To find out if it is a case of NAPT, we + * check if the Nat'ted IP is one of the NAPT pool range IP addresses, or if + * there is no static or dynamic NAT for that IP address. If both checks are not + * met, then it is the case of NAT basic translation where only IP address is NAT'ted. */ +bool NatSync::matchingSnaptPoolExists(const IpAddress &natIp) +{ + string key = natIp.to_string(); + std::vector values; + + if (m_naptPoolCheckTable.get(key, values)) + { + SWSS_LOG_INFO("Matching pool IP exists for NAT IP %s", key.c_str()); + return true; + } + + return false; +} + +bool NatSync::matchingSnaptEntryExists(const naptEntry &entry) +{ + string key = entry.orig_src_ip.to_string() + ":" + to_string(entry.orig_src_l4_port); + string reverseEntryKey = entry.nat_src_ip.to_string() + ":" + to_string(entry.nat_src_l4_port); + std::vector values; + + if (m_naptCheckTable.get(key, values) || m_naptCheckTable.get(reverseEntryKey, values)) + { + SWSS_LOG_INFO("Matching SNAPT entry exists for key %s or reverse key %s", + key.c_str(), reverseEntryKey.c_str()); + return true; + } + return false; +} + +bool NatSync::matchingDnaptEntryExists(const naptEntry &entry) +{ + string key = entry.orig_dest_ip.to_string() + ":" + to_string(entry.orig_dst_l4_port); + string reverseEntryKey = entry.nat_dest_ip.to_string() + ":" + to_string(entry.nat_dst_l4_port); + std::vector values; + + if (m_naptCheckTable.get(key, values) || m_naptCheckTable.get(reverseEntryKey, values)) + { + SWSS_LOG_INFO("Matching DNAPT entry exists for key %s or reverse key %s", + key.c_str(), reverseEntryKey.c_str()); + return true; + } + return false; +} + +/* Add the NAT entries to APP_DB based on the criteria: + * ---------------------------------------------------- + * - If only source ip changed, add it as SNAT entry. + * - If only destination ip changed, add it as DNAT entry. + * - If SNAT happened and the l4 port changes or part of any dynamic pool range + * or if there is matching static or dynamic NAPT entry, add it as SNAPT entry. + * - If DNAT happened and the l4 port changes or if there is matching static + * or dynamic NAPT entry, add it as DNAPT entry. + * - If SNAT and DNAT happened, add it as Twice NAT entry. + * - If SNAPT and DNAPT conditions are met or if there is no static + * or dynamic Twice NAT entry, then it is a Twice NAPT entry. + */ +int NatSync::addNatEntry(struct nfnl_ct *ct, struct naptEntry &entry, bool addFlag) +{ + SWSS_LOG_ENTER(); + + bool src_ip_natted = (entry.orig_src_ip != entry.nat_src_ip); + bool dst_ip_natted = (entry.orig_dest_ip != entry.nat_dest_ip); + bool src_port_natted = (src_ip_natted && ((entry.orig_src_l4_port != entry.nat_src_l4_port) || + (matchingSnaptPoolExists(entry.nat_src_ip)) || + (matchingSnaptEntryExists(entry)))); + bool dst_port_natted = (dst_ip_natted && ((entry.orig_dst_l4_port != entry.nat_dst_l4_port) || + (matchingDnaptEntryExists(entry)))); + bool entryExists = 0, reverseEntryExists = 0; + + SWSS_LOG_INFO("Flags: src natted %d, dst natted %d, src port natted %d, dst port natted %d", src_ip_natted, dst_ip_natted, src_port_natted, dst_port_natted); + + std::vector fvVector, reverseFvVector; + string protostr = ((entry.protocol == IPPROTO_TCP) ? "TCP:" : "UDP:"); + string opStr = ((addFlag) ? "CREATE" : "DELETE"); + string key = "", reverseEntryKey = ""; + + FieldValueTuple snat_type("nat_type", "snat"); + FieldValueTuple dnat_type("nat_type", "dnat"); + FieldValueTuple dynamic_entry("entry_type", "dynamic"); + + if (src_ip_natted && dst_ip_natted) + { + if (addFlag) + { + FieldValueTuple translated_src_ip("translated_src_ip", entry.nat_src_ip.to_string()); + FieldValueTuple translated_dst_ip("translated_dst_ip", entry.nat_dest_ip.to_string()); + FieldValueTuple reverse_translated_src_ip("translated_src_ip", entry.orig_dest_ip.to_string()); + FieldValueTuple reverse_translated_dst_ip("translated_dst_ip", entry.orig_src_ip.to_string()); + + fvVector.push_back(dynamic_entry); + fvVector.push_back(translated_src_ip); + fvVector.push_back(translated_dst_ip); + + reverseFvVector.push_back(dynamic_entry); + reverseFvVector.push_back(reverse_translated_src_ip); + reverseFvVector.push_back(reverse_translated_dst_ip); + } + + string tmpKey = key + entry.orig_src_ip.to_string() + ":" + entry.orig_dest_ip.to_string(); + string tmpReverseEntryKey = reverseEntryKey + entry.nat_dest_ip.to_string() + ":" + entry.nat_src_ip.to_string(); + + std::vector values; + if (m_twiceNatCheckTable.get(tmpKey, values)) + { + src_port_natted = dst_port_natted = false; + + for (auto iter : values) + { + /* If a matching Static Twice NAT entry exists in the APP_DB, + * it has higher priority than the dynamic twice nat entry. */ + if ((fvField(iter) == "entry_type") && (fvValue(iter) == "static")) + { + SWSS_LOG_INFO("Static Twice NAT %s: entry exists, not processing twice NAT entry notification", opStr.c_str()); + if (m_AppRestartAssist->isWarmStartInProgress()) + { + m_AppRestartAssist->insertToMap(APP_NAT_TWICE_TABLE_NAME, tmpKey, fvVector, (!addFlag)); + m_AppRestartAssist->insertToMap(APP_NAT_TWICE_TABLE_NAME, tmpReverseEntryKey, reverseFvVector, (!addFlag)); + } + return 1; + } + } + if (addFlag) + { + SWSS_LOG_INFO("Twice SNAT CREATE: ignoring the duplicated Twice SNAT notification"); + return 1; + } + } + + if (src_port_natted || dst_port_natted) + { + /* Case of Twice NAPT entry, where both the SIP, DIP and + * the L4 port(s) are NAT'ted. */ + SWSS_LOG_INFO("Twice NAPT %s conntrack notification", opStr.c_str()); + + key += protostr + entry.orig_src_ip.to_string(); + reverseEntryKey += protostr + entry.nat_dest_ip.to_string(); + + string src_l4_port = std::to_string(entry.orig_src_l4_port); + string dst_l4_port = std::to_string(entry.orig_dst_l4_port); + string nat_src_l4_port = std::to_string(entry.nat_src_l4_port); + string nat_dst_l4_port = std::to_string(entry.nat_dst_l4_port); + + key += ":" + src_l4_port + ":" + entry.orig_dest_ip.to_string() + + ":" + dst_l4_port; + reverseEntryKey += ":" + nat_dst_l4_port + ":" + entry.nat_src_ip.to_string() + + ":" + nat_src_l4_port; + + std::vector values; + /* If a matching Static Twice NAPT entry exists in the APP_DB, + * it has higher priority than the dynamic twice napt entry. */ + if (m_twiceNaptCheckTable.get(key, values)) + { + for (auto iter : values) + { + if ((fvField(iter) == "entry_type") && (fvValue(iter) == "static")) + { + SWSS_LOG_INFO("Static Twice NAPT %s: entry exists, not processing dynamic twice NAPT entry", opStr.c_str()); + if (m_AppRestartAssist->isWarmStartInProgress()) + { + m_AppRestartAssist->insertToMap(APP_NAPT_TWICE_TABLE_NAME, key, fvVector, (!addFlag)); + m_AppRestartAssist->insertToMap(APP_NAPT_TWICE_TABLE_NAME, reverseEntryKey, reverseFvVector, (!addFlag)); + } + return 1; + } + } + if (addFlag) + { + SWSS_LOG_INFO("Twice SNAPT CREATE: ignoring the duplicated Twice SNAPT notification"); + return 1; + } + } + if (addFlag) + { + FieldValueTuple translated_src_port("translated_src_l4_port", nat_src_l4_port); + FieldValueTuple translated_dst_port("translated_dst_l4_port", nat_dst_l4_port); + FieldValueTuple reverse_translated_src_port("translated_src_l4_port", dst_l4_port); + FieldValueTuple reverse_translated_dst_port("translated_dst_l4_port", src_l4_port); + + fvVector.push_back(translated_src_port); + fvVector.push_back(translated_dst_port); + reverseFvVector.push_back(reverse_translated_src_port); + reverseFvVector.push_back(reverse_translated_dst_port); + + + if (m_AppRestartAssist->isWarmStartInProgress()) + { + m_AppRestartAssist->insertToMap(APP_NAPT_TWICE_TABLE_NAME, key, fvVector, false); + m_AppRestartAssist->insertToMap(APP_NAPT_TWICE_TABLE_NAME, reverseEntryKey, reverseFvVector, false); + } + else + { + m_naptTwiceTable.set(key, fvVector); + SWSS_LOG_NOTICE("Twice NAPT entry with key %s added to APP_DB", key.c_str()); + m_naptTwiceTable.set(reverseEntryKey, reverseFvVector); + SWSS_LOG_NOTICE("Twice NAPT entry with reverse key %s added to APP_DB", reverseEntryKey.c_str()); + } + } + else + { + if (m_AppRestartAssist->isWarmStartInProgress()) + { + m_AppRestartAssist->insertToMap(APP_NAPT_TWICE_TABLE_NAME, key, fvVector, true); + m_AppRestartAssist->insertToMap(APP_NAPT_TWICE_TABLE_NAME, reverseEntryKey, reverseFvVector, true); + } + else + { + m_naptTwiceTable.del(key); + SWSS_LOG_NOTICE("Twice NAPT entry with key %s deleted from APP_DB", key.c_str()); + m_naptTwiceTable.del(reverseEntryKey); + SWSS_LOG_NOTICE("Twice NAPT entry with reverse key %s deleted from APP_DB", reverseEntryKey.c_str()); + } + } + } + else + { + /* Case of Twice NAT entry, where only the SIP, DIP are + * NAT'ted but the port translation is not done. */ + SWSS_LOG_INFO("Twice NAT %s conntrack notification", opStr.c_str()); + + key = tmpKey; + reverseEntryKey = tmpReverseEntryKey; + + if (addFlag) + { + if (m_AppRestartAssist->isWarmStartInProgress()) + { + m_AppRestartAssist->insertToMap(APP_NAT_TWICE_TABLE_NAME, key, fvVector, false); + m_AppRestartAssist->insertToMap(APP_NAT_TWICE_TABLE_NAME, reverseEntryKey, reverseFvVector, false); + } + else + { + m_natTwiceTable.set(key, fvVector); + SWSS_LOG_NOTICE("Twice NAT entry with key %s added to APP_DB", key.c_str()); + m_natTwiceTable.set(reverseEntryKey, reverseFvVector); + SWSS_LOG_NOTICE("Twice NAT entry with reverse key %s added to APP_DB", reverseEntryKey.c_str()); + } + } + else + { + if (m_AppRestartAssist->isWarmStartInProgress()) + { + m_AppRestartAssist->insertToMap(APP_NAT_TWICE_TABLE_NAME, key, fvVector, true); + m_AppRestartAssist->insertToMap(APP_NAT_TWICE_TABLE_NAME, reverseEntryKey, reverseFvVector, true); + } + else + { + m_natTwiceTable.del(key); + SWSS_LOG_NOTICE("Twice NAT entry with key %s deleted from APP_DB", key.c_str()); + m_natTwiceTable.del(reverseEntryKey); + SWSS_LOG_NOTICE("Twice NAT entry with reverse key %s deleted from APP_DB", reverseEntryKey.c_str()); + } + } + } + } + else if (src_ip_natted || dst_ip_natted) + { + if (src_ip_natted) + { + if (addFlag) + { + FieldValueTuple snat_translated_ip("translated_ip", entry.nat_src_ip.to_string()); + FieldValueTuple dnat_translated_ip("translated_ip", entry.orig_src_ip.to_string()); + + fvVector.push_back(snat_type); + fvVector.push_back(dynamic_entry); + fvVector.push_back(snat_translated_ip); + + reverseFvVector.push_back(dnat_type); + reverseFvVector.push_back(dynamic_entry); + reverseFvVector.push_back(dnat_translated_ip); + } + + if (src_port_natted) + { + key += protostr + entry.orig_src_ip.to_string(); + reverseEntryKey += protostr + entry.nat_src_ip.to_string(); + + /* Case of NAPT entry, where SIP is NAT'ted and the L4 port is NAT'ted. */ + SWSS_LOG_INFO("SNAPT %s conntrack notification", opStr.c_str()); + + string src_l4_port = std::to_string(entry.orig_src_l4_port); + string nat_src_l4_port = std::to_string(entry.nat_src_l4_port); + + key += ":" + src_l4_port; + reverseEntryKey += ":" + nat_src_l4_port; + + std::vector values; + /* We check for existence of reverse nat entry in the app-db because the same dnat static entry + * would be reported as snat entry from the kernel if a packet that is forwarded in the kernel + * is matched by the iptables rules corresponding to the dnat static entry */ + if (! m_AppRestartAssist->isWarmStartInProgress()) + { + if ((entryExists = m_naptCheckTable.get(key, values))) + { + for (auto iter : values) + { + if ((fvField(iter) == "entry_type") && (fvValue(iter) == "static")) + { + /* If a matching Static NAPT entry exists in the APP_DB, + * it has higher priority than the dynamic napt entry. */ + SWSS_LOG_INFO("SNAPT %s: static entry exists, not processing the NAPT notification", opStr.c_str()); + return 1; + } + } + if (addFlag) + { + SWSS_LOG_INFO("SNAPT CREATE: ignoring the duplicated SNAPT notification"); + return 1; + } + else + { + /* Skip entry, if entry contains loopback destination address */ + if ((IS_LOOPBACK_ADDR(ntohl(entry.orig_dest_ip.getV4Addr()))) || + (IS_LOOPBACK_ADDR(ntohl(entry.nat_dest_ip.getV4Addr())))) + { + SWSS_LOG_INFO("SNAPT %s: static entry contains loopback address, ignoring the notification", opStr.c_str()); + return 1; + } + else + { + m_naptTable.del(key); + SWSS_LOG_NOTICE("SNAPT entry with key %s deleted from APP_DB", key.c_str()); + } + } + } + if ((reverseEntryExists = m_naptCheckTable.get(reverseEntryKey, values))) + { + for (auto iter : values) + { + if ((fvField(iter) == "entry_type") && (fvValue(iter) == "static")) + { + /* If a matching Static NAPT entry exists in the APP_DB, + * it has higher priority than the dynamic napt entry. */ + SWSS_LOG_INFO("SNAPT %s: static reverse entry exists, not processing dynamic NAPT entry", opStr.c_str()); + return 1; + } + } + if (addFlag) + { + SWSS_LOG_INFO("SNAPT CREATE: ignoring the duplicated SNAPT notification"); + return 1; + } + else + { + /* Skip entry, if entry contains loopback destination address */ + if ((IS_LOOPBACK_ADDR(ntohl(entry.orig_dest_ip.getV4Addr()))) || + (IS_LOOPBACK_ADDR(ntohl(entry.nat_dest_ip.getV4Addr())))) + { + SWSS_LOG_INFO("SNAPT %s: static entry contains loopback address, ignoring the notification", opStr.c_str()); + return 1; + } + else + { + m_naptTable.del(reverseEntryKey); + SWSS_LOG_NOTICE("Implicit DNAPT entry with key %s deleted from APP_DB", reverseEntryKey.c_str()); + } + } + } + } + if (addFlag) + { + FieldValueTuple snat_translated_port("translated_l4_port", nat_src_l4_port); + FieldValueTuple dnat_translated_port("translated_l4_port", src_l4_port); + + fvVector.push_back(snat_translated_port); + reverseFvVector.push_back(dnat_translated_port); + + if (m_AppRestartAssist->isWarmStartInProgress()) + { + m_AppRestartAssist->insertToMap(APP_NAPT_TABLE_NAME, key, fvVector, false); + m_AppRestartAssist->insertToMap(APP_NAPT_TABLE_NAME, reverseEntryKey, reverseFvVector, false); + } + else + { + /* Skip entry, if entry contains loopback destination address */ + if ((IS_LOOPBACK_ADDR(ntohl(entry.orig_dest_ip.getV4Addr()))) || + (IS_LOOPBACK_ADDR(ntohl(entry.nat_dest_ip.getV4Addr())))) + { + SWSS_LOG_INFO("SNAPT %s: static entry contains loopback address, ignoring the notification", opStr.c_str()); + return 1; + } + else + { + m_naptTable.set(key, fvVector); + SWSS_LOG_NOTICE("SNAPT entry with key %s added to APP_DB", key.c_str()); + m_naptTable.set(reverseEntryKey, reverseFvVector); + SWSS_LOG_NOTICE("Implicit DNAPT entry with key %s added to APP_DB", reverseEntryKey.c_str()); + } + } + } + } + else + { + /* Case of Basic SNAT entry, where SIP is NAT'ted but the port translation is not done. */ + SWSS_LOG_INFO("SNAT %s conntrack notification", opStr.c_str()); + + key += entry.orig_src_ip.to_string(); + reverseEntryKey += entry.nat_src_ip.to_string(); + + std::vector values; + if (! m_AppRestartAssist->isWarmStartInProgress()) + { + if ((entryExists = m_natCheckTable.get(key, values))) + { + for (auto iter : values) + { + if ((fvField(iter) == "entry_type") && (fvValue(iter) == "static")) + { + /* If a matching Static NAT entry exists in the APP_DB, + * it has higher priority than the dynamic napt entry. */ + SWSS_LOG_INFO("SNAT %s: static entry exists, not processing the NAT notification", opStr.c_str()); + return 1; + } + } + if (addFlag) + { + SWSS_LOG_INFO("SNAT CREATE: ignoring the duplicated notification"); + return 1; + } + else + { + /* Skip entry, if entry contains loopback destination address */ + if ((IS_LOOPBACK_ADDR(ntohl(entry.orig_dest_ip.getV4Addr()))) || + (IS_LOOPBACK_ADDR(ntohl(entry.nat_dest_ip.getV4Addr())))) + { + SWSS_LOG_INFO("SNAT %s: static entry contains loopback address, ignoring the notification", opStr.c_str()); + return 1; + } + else + { + m_natTable.del(key); + SWSS_LOG_NOTICE("SNAT entry with key %s deleted from APP_DB", key.c_str()); + } + } + } + if ((reverseEntryExists = m_natCheckTable.get(reverseEntryKey, values))) + { + for (auto iter : values) + { + if ((fvField(iter) == "entry_type") && (fvValue(iter) == "static")) + { + /* If a matching Static NAT entry exists in the APP_DB, + * it has higher priority than the dynamic napt entry. */ + SWSS_LOG_INFO("SNAT %s: static reverse entry exists, not adding dynamic NAT entry", opStr.c_str()); + return 1; + } + } + if (addFlag) + { + SWSS_LOG_INFO("SNAT CREATE: ignoring the duplicated notification"); + return 1; + } + else + { + /* Skip entry, if entry contains loopback destination address */ + if ((IS_LOOPBACK_ADDR(ntohl(entry.orig_dest_ip.getV4Addr()))) || + (IS_LOOPBACK_ADDR(ntohl(entry.nat_dest_ip.getV4Addr())))) + { + SWSS_LOG_INFO("SNAT %s: static entry contains loopback address, ignoring the notification", opStr.c_str()); + return 1; + } + else + { + m_natTable.del(reverseEntryKey); + SWSS_LOG_NOTICE("Implicit DNAT entry with key %s deleted from APP_DB", reverseEntryKey.c_str()); + } + } + } + } + if (addFlag) + { + if (m_AppRestartAssist->isWarmStartInProgress()) + { + m_AppRestartAssist->insertToMap(APP_NAT_TABLE_NAME, key, fvVector, false); + m_AppRestartAssist->insertToMap(APP_NAT_TABLE_NAME, reverseEntryKey, reverseFvVector, false); + } + else + { + /* Skip entry, if entry is src natted and dest is loopback destination address */ + if ((IS_LOOPBACK_ADDR(ntohl(entry.orig_dest_ip.getV4Addr()))) || + (IS_LOOPBACK_ADDR(ntohl(entry.nat_dest_ip.getV4Addr())))) + { + SWSS_LOG_INFO("SNAT %s: static entry contains loopback address, ignoring the notification", opStr.c_str()); + return 1; + } + else + { + m_natTable.set(key, fvVector); + SWSS_LOG_NOTICE("SNAT entry with key %s added to APP_DB", key.c_str()); + m_natTable.set(reverseEntryKey, reverseFvVector); + SWSS_LOG_NOTICE("Implicit DNAT entry with key %s added to APP_DB", reverseEntryKey.c_str()); + } + } + } + } + } + else + { + if (addFlag) + { + FieldValueTuple dnat_translated_ip("translated_ip", entry.nat_dest_ip.to_string()); + FieldValueTuple snat_translated_ip("translated_ip", entry.orig_dest_ip.to_string()); + + fvVector.push_back(dnat_type); + fvVector.push_back(dynamic_entry); + fvVector.push_back(dnat_translated_ip); + + reverseFvVector.push_back(snat_type); + reverseFvVector.push_back(dynamic_entry); + reverseFvVector.push_back(snat_translated_ip); + } + + if (dst_port_natted) + { + key += protostr + entry.orig_dest_ip.to_string(); + reverseEntryKey += protostr + entry.nat_dest_ip.to_string(); + + /* Case of DNAPT entry, where DIP is NAT'ted and the L4 port is NAT'ted. */ + SWSS_LOG_INFO("DNAPT %s conntrack notification", opStr.c_str()); + + string dst_l4_port = std::to_string(entry.orig_dst_l4_port); + string nat_dst_l4_port = std::to_string(entry.nat_dst_l4_port); + + key += ":" + dst_l4_port; + reverseEntryKey += ":" + nat_dst_l4_port; + + std::vector values; + if (! m_AppRestartAssist->isWarmStartInProgress()) + { + if ((entryExists = m_naptCheckTable.get(key, values))) + { + for (auto iter : values) + { + if ((fvField(iter) == "entry_type") && (fvValue(iter) == "static")) + { + /* If a matching Static NAPT entry exists in the APP_DB, + * it has higher priority than the dynamic napt entry. */ + SWSS_LOG_INFO("DNAPT %s: static entry exists, not processing the NAPT notification", opStr.c_str()); + return 1; + } + } + if (addFlag) + { + SWSS_LOG_INFO("DNAPT CREATE: ignoring the duplicated notification"); + return 1; + } + else + { + m_naptTable.del(key); + SWSS_LOG_NOTICE("DNAPT entry with key %s deleted from APP_DB", key.c_str()); + } + } + if ((reverseEntryExists = m_naptCheckTable.get(reverseEntryKey, values))) + { + for (auto iter : values) + { + if ((fvField(iter) == "entry_type") && (fvValue(iter) == "static")) + { + /* If a matching Static NAPT entry exists in the APP_DB, + * it has higher priority than the dynamic napt entry. */ + SWSS_LOG_INFO("DNAPT %s: static reverse entry exists, not adding dynamic NAPT entry", opStr.c_str()); + return 1; + } + } + if (addFlag) + { + SWSS_LOG_INFO("DNAPT CREATE: ignoring the duplicated notification"); + return 1; + } + else + { + m_naptTable.del(reverseEntryKey); + SWSS_LOG_NOTICE("Implicit SNAPT entry with key %s deleted from APP_DB", reverseEntryKey.c_str()); + } + } + } + if (addFlag) + { + FieldValueTuple dnat_translated_port("translated_l4_port", nat_dst_l4_port); + FieldValueTuple snat_translated_port("translated_l4_port", dst_l4_port); + + fvVector.push_back(dnat_translated_port); + reverseFvVector.push_back(snat_translated_port); + + if (m_AppRestartAssist->isWarmStartInProgress()) + { + m_AppRestartAssist->insertToMap(APP_NAPT_TABLE_NAME, key, fvVector, false); + m_AppRestartAssist->insertToMap(APP_NAPT_TABLE_NAME, reverseEntryKey, reverseFvVector, false); + } + else + { + m_naptTable.set(key, fvVector); + SWSS_LOG_NOTICE("DNAPT entry with key %s added to APP_DB", key.c_str()); + m_naptTable.set(reverseEntryKey, reverseFvVector); + SWSS_LOG_NOTICE("Implicit SNAPT entry with key %s added to APP_DB", reverseEntryKey.c_str()); + } + } + } + else + { + /* Case of Basic DNAT entry, where DIP is NAT'ted but the port translation is not done. */ + SWSS_LOG_INFO("DNAT %s conntrack notification", opStr.c_str()); + key += entry.orig_dest_ip.to_string(); + reverseEntryKey += entry.nat_dest_ip.to_string(); + + std::vector values; + if (! m_AppRestartAssist->isWarmStartInProgress()) + { + if ((entryExists = m_natCheckTable.get(key, values))) + { + for (auto iter : values) + { + if ((fvField(iter) == "entry_type") && (fvValue(iter) == "static")) + { + /* If a matching Static NAT entry exists in the APP_DB, + * it has higher priority than the dynamic napt entry. */ + SWSS_LOG_INFO("DNAT %s: static entry exists, not processing the NAT notification", opStr.c_str()); + return 1; + } + } + if (addFlag) + { + SWSS_LOG_INFO("DNAT CREATE: ignoring the duplicated notification"); + return 1; + } + else + { + m_natTable.del(key); + SWSS_LOG_NOTICE("DNAT entry with key %s deleted from APP_DB", key.c_str()); + } + } + if ((reverseEntryExists = m_natCheckTable.get(reverseEntryKey, values))) + { + for (auto iter : values) + { + if ((fvField(iter) == "entry_type") && (fvValue(iter) == "static")) + { + /* If a matching Static NAT entry exists in the APP_DB, + * it has higher priority than the dynamic napt entry. */ + SWSS_LOG_INFO("DNAT %s: static reverse entry exists, not adding dynamic NAT entry", opStr.c_str()); + return 1; + } + } + if (addFlag) + { + SWSS_LOG_INFO("DNAT CREATE: ignoring the duplicated notification"); + return 1; + } + else + { + m_natTable.del(reverseEntryKey); + SWSS_LOG_NOTICE("Implicit SNAT entry with key %s deleted from APP_DB", reverseEntryKey.c_str()); + } + } + } + if (addFlag) + { + if (m_AppRestartAssist->isWarmStartInProgress()) + { + m_AppRestartAssist->insertToMap(APP_NAT_TABLE_NAME, key, fvVector, false); + m_AppRestartAssist->insertToMap(APP_NAT_TABLE_NAME, reverseEntryKey, reverseFvVector, false); + } + else + { + m_natTable.set(key, fvVector); + SWSS_LOG_NOTICE("DNAT entry with key %s added to APP_DB", key.c_str()); + m_natTable.set(reverseEntryKey, reverseFvVector); + SWSS_LOG_NOTICE("Implicit SNAT entry with key %s added to APP_DB", reverseEntryKey.c_str()); + } + } + } + } + } + return 0; +} + +/* This function is called only for updating the UDP connection entries + * so as not to timeout early in the kernel. */ +void NatSync::updateConnTrackEntry(struct nfnl_ct *ct) +{ + SWSS_LOG_ENTER(); + + SWSS_LOG_INFO("Updating conntrack entry in the kernel"); + + if (nfsock) + { + nfsock->updateConnTrackEntry(ct); + } +} + +/* This function is called to delete conflicting NAT entries + * to ensure full cone NAT functionality. + * Also this function is invoked to remove low priority + * dynamic NAT entry when there is a matching static NAT entry */ +void NatSync::deleteConnTrackEntry(struct nfnl_ct *ct) +{ + SWSS_LOG_ENTER(); + + SWSS_LOG_INFO("Deleting conntrack entry in the kernel"); + + if (nfsock) + { + nfsock->deleteConnTrackEntry(ct); + } +} diff --git a/natsyncd/natsync.h b/natsyncd/natsync.h new file mode 100644 index 0000000000..e4ea8c6f18 --- /dev/null +++ b/natsyncd/natsync.h @@ -0,0 +1,112 @@ +/* + * Copyright 2019 Broadcom Inc. + * + * 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. + */ + +#ifndef __NATSYNC_H__ +#define __NATSYNC_H__ + +#include "dbconnector.h" +#include "producerstatetable.h" +#include "netmsg.h" +#include "warmRestartAssist.h" +#include "ipaddress.h" +#include "nfnetlink.h" +#include +#include +#include + +// The timeout value (in seconds) for natsyncd reconcilation logic +#define DEFAULT_NATSYNC_WARMSTART_TIMER 30 + +/* This is the timer value (in seconds) that the natsyncd waits for + * restore_nat_entries service to finish. */ + +#define RESTORE_NAT_WAIT_TIME_OUT 120 + +namespace swss { + +struct naptEntry; + +class NatSync : public NetMsg +{ +public: + NatSync(RedisPipeline *pipelineAppDB, DBConnector *appDb, DBConnector *stateDb, NfNetlink *nfnl); + + virtual void onMsg(int nlmsg_type, struct nl_object *obj); + + bool isNatRestoreDone(); + bool isPortInitDone(DBConnector *app_db); + + AppRestartAssist *getRestartAssist() + { + return m_AppRestartAssist; + } + +private: + static int parseConnTrackMsg(const struct nfnl_ct *ct, struct naptEntry &entry); + void updateConnTrackEntry(struct nfnl_ct *ct); + void deleteConnTrackEntry(struct nfnl_ct *ct); + + bool matchingSnaptPoolExists(const IpAddress &natIp); + bool matchingSnaptEntryExists(const naptEntry &entry); + bool matchingDnaptEntryExists(const naptEntry &entry); + int addNatEntry(struct nfnl_ct *ct, struct naptEntry &entry, bool addFlag); + + ProducerStateTable m_natTable; + ProducerStateTable m_naptTable; + ProducerStateTable m_natTwiceTable; + ProducerStateTable m_naptTwiceTable; + + Table m_natCheckTable; + Table m_naptCheckTable; + Table m_naptPoolCheckTable; + Table m_twiceNatCheckTable; + Table m_twiceNaptCheckTable; + + Table m_stateNatRestoreTable; + AppRestartAssist *m_AppRestartAssist; + + NfNetlink *nfsock; +}; + +struct naptEntry +{ + uint32_t conntrack_id; + uint8_t protocol; + IpAddress orig_src_ip; + uint16_t orig_src_l4_port; + IpAddress orig_dest_ip; + uint16_t orig_dst_l4_port; + IpAddress nat_src_ip; + uint16_t nat_src_l4_port; + IpAddress nat_dest_ip; + uint16_t nat_dst_l4_port; + uint32_t ct_status; +}; + +/* Copy of nl_addr from netlink-private/types.h */ +struct nl_ip_addr +{ + int a_family; + unsigned int a_maxsize; + unsigned int a_len; + int a_prefixlen; + int a_refcnt; + int a_addr; +}; + +} + +#endif /* __NATSYNC_H__ */ diff --git a/natsyncd/natsyncd.cpp b/natsyncd/natsyncd.cpp new file mode 100644 index 0000000000..bda2540620 --- /dev/null +++ b/natsyncd/natsyncd.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include "logger.h" +#include "select.h" +#include "netdispatcher.h" +#include "natsync.h" +#include + +using namespace std; +using namespace swss; + +int main(int argc, char **argv) +{ + Logger::linkToDbNative("natsyncd"); + + DBConnector appDb(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + RedisPipeline pipelineAppDB(&appDb); + DBConnector stateDb(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + NfNetlink nfnl; + + nfnl.registerRecvCallbacks(); + NatSync sync(&pipelineAppDB, &appDb, &stateDb, &nfnl); + + sync.isPortInitDone(&appDb); + + NetDispatcher::getInstance().registerMessageHandler(NFNLMSG_TYPE(NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_NEW), &sync); + NetDispatcher::getInstance().registerMessageHandler(NFNLMSG_TYPE(NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_DELETE), &sync); + + while (1) + { + try + { + Select s; + + using namespace std::chrono; + /* + * If warmstart, read the NAT tables to cache map. + * Wait for the kernel NAT conntrack table restore to finish in case of warmreboot. + * Start reconcile timer once restore flag is set. + */ + if (sync.getRestartAssist()->isWarmStartInProgress()) + { + sync.getRestartAssist()->readTablesToMap(); + + steady_clock::time_point starttime = steady_clock::now(); + while (!sync.isNatRestoreDone()) + { + duration time_span = + duration_cast>(steady_clock::now() - starttime); + int pasttime = int(time_span.count()); + SWSS_LOG_INFO("Waited for NAT conntrack table to be restored to kernel" + " for %d seconds", pasttime); + if (pasttime > RESTORE_NAT_WAIT_TIME_OUT) + { + SWSS_LOG_ERROR("Nat conntrack table restore is not finished" + " after timed-out, exit!!!"); + exit(EXIT_FAILURE); + } + sleep(1); + } + sync.getRestartAssist()->startReconcileTimer(s); + } + + nfnl.registerGroup(NFNLGRP_CONNTRACK_NEW); + nfnl.registerGroup(NFNLGRP_CONNTRACK_UPDATE); + nfnl.registerGroup(NFNLGRP_CONNTRACK_DESTROY); + + SWSS_LOG_INFO("Listens to conntrack messages..."); + nfnl.dumpRequest(IPCTNL_MSG_CT_GET); + + s.addSelectable(&nfnl); + while (true) + { + Selectable *temps; + s.select(&temps); + /* + * If warmstart is in progress, we check the reconcile timer, + * if timer expired, we stop the timer and start the reconcile process + */ + if (sync.getRestartAssist()->isWarmStartInProgress()) + { + if (sync.getRestartAssist()->checkReconcileTimer(temps)) + { + sync.getRestartAssist()->stopReconcileTimer(s); + sync.getRestartAssist()->reconcile(); + } + } + } + } + catch (const std::exception& e) + { + SWSS_LOG_ERROR("Runtime error: %s", e.what()); + return 0; + } + } + + return 1; +} diff --git a/neighsyncd/neighsync.cpp b/neighsyncd/neighsync.cpp index 69ca563f2e..1af94450ad 100644 --- a/neighsyncd/neighsync.cpp +++ b/neighsyncd/neighsync.cpp @@ -19,9 +19,21 @@ using namespace swss; NeighSync::NeighSync(RedisPipeline *pipelineAppDB, DBConnector *stateDb) : m_neighTable(pipelineAppDB, APP_NEIGH_TABLE_NAME), - m_stateNeighRestoreTable(stateDb, STATE_NEIGH_RESTORE_TABLE_NAME), - m_AppRestartAssist(pipelineAppDB, "neighsyncd", "swss", &m_neighTable, DEFAULT_NEIGHSYNC_WARMSTART_TIMER) + m_stateNeighRestoreTable(stateDb, STATE_NEIGH_RESTORE_TABLE_NAME) { + m_AppRestartAssist = new AppRestartAssist(pipelineAppDB, "neighsyncd", "swss", DEFAULT_NEIGHSYNC_WARMSTART_TIMER); + if (m_AppRestartAssist) + { + m_AppRestartAssist->registerAppTable(APP_NEIGH_TABLE_NAME, &m_neighTable); + } +} + +NeighSync::~NeighSync() +{ + if (m_AppRestartAssist) + { + delete m_AppRestartAssist; + } } // Check if neighbor table is restored in kernel @@ -98,9 +110,9 @@ void NeighSync::onMsg(int nlmsg_type, struct nl_object *obj) fvVector.push_back(f); // If warmstart is in progress, we take all netlink changes into the cache map - if (m_AppRestartAssist.isWarmStartInProgress()) + if (m_AppRestartAssist->isWarmStartInProgress()) { - m_AppRestartAssist.insertToMap(key, fvVector, delete_key); + m_AppRestartAssist->insertToMap(APP_NEIGH_TABLE_NAME, key, fvVector, delete_key); } else { diff --git a/neighsyncd/neighsync.h b/neighsyncd/neighsync.h index 66fd1c2645..387c849f30 100644 --- a/neighsyncd/neighsync.h +++ b/neighsyncd/neighsync.h @@ -24,6 +24,7 @@ class NeighSync : public NetMsg enum { MAX_ADDR_SIZE = 64 }; NeighSync(RedisPipeline *pipelineAppDB, DBConnector *stateDb); + ~NeighSync(); virtual void onMsg(int nlmsg_type, struct nl_object *obj); @@ -31,13 +32,13 @@ class NeighSync : public NetMsg AppRestartAssist *getRestartAssist() { - return &m_AppRestartAssist; + return m_AppRestartAssist; } private: Table m_stateNeighRestoreTable; ProducerStateTable m_neighTable; - AppRestartAssist m_AppRestartAssist; + AppRestartAssist *m_AppRestartAssist; }; } diff --git a/neighsyncd/neighsyncd.cpp b/neighsyncd/neighsyncd.cpp index c14380fb3a..f0dab62590 100644 --- a/neighsyncd/neighsyncd.cpp +++ b/neighsyncd/neighsyncd.cpp @@ -40,7 +40,7 @@ int main(int argc, char **argv) */ if (sync.getRestartAssist()->isWarmStartInProgress()) { - sync.getRestartAssist()->readTableToMap(); + sync.getRestartAssist()->readTablesToMap(); steady_clock::time_point starttime = steady_clock::now(); while (!sync.isNeighRestoreDone()) diff --git a/warmrestart/warmRestartAssist.cpp b/warmrestart/warmRestartAssist.cpp index 03baa0df63..2a02e5ec95 100644 --- a/warmrestart/warmRestartAssist.cpp +++ b/warmrestart/warmRestartAssist.cpp @@ -17,20 +17,16 @@ const AppRestartAssist::cache_state_map AppRestartAssist::cacheStateMap = {DELETE, "DELETE"} }; -AppRestartAssist::AppRestartAssist(RedisPipeline *pipeline, - const std::string &appName, const std::string &dockerName, - ProducerStateTable *psTable, const uint32_t defaultWarmStartTimerValue): - m_appTable(pipeline, APP_NEIGH_TABLE_NAME, false), +AppRestartAssist::AppRestartAssist(RedisPipeline *pipelineAppDB, const std::string &appName, + const std::string &dockerName, const uint32_t defaultWarmStartTimerValue): + m_pipeLine(pipelineAppDB), m_appName(appName), m_dockerName(dockerName), - m_psTable(psTable), m_warmStartTimer(timespec{0, 0}) { WarmStart::initialize(m_appName, m_dockerName); WarmStart::checkWarmStart(m_appName, m_dockerName); - m_appTableName = m_appTable.getTableName(); - /* * set the default timer value. * If the application instance privides timer value, use it if valid. @@ -66,15 +62,25 @@ AppRestartAssist::AppRestartAssist(RedisPipeline *pipeline, m_warmStartTimer.setInterval(timespec{m_reconcileTimer, 0}); - // Clear the producerstate table to make sure no pending data for the AppTable - m_psTable->clear(); - WarmStart::setWarmStartState(m_appName, WarmStart::INITIALIZED); } } AppRestartAssist::~AppRestartAssist() { + for (auto it = m_appTables.begin(); it != m_appTables.end(); it++) + { + delete (it->second); + } +} + +void AppRestartAssist::registerAppTable(const std::string &tableName, ProducerStateTable *psTable) +{ + m_psTables[tableName] = psTable; + + // Clear the producerstate table to make sure no pending data for the AppTable + psTable->clear(); + m_appTables[tableName] = new Table(m_pipeLine, tableName, false); } // join the field-value strings for straight printing. @@ -108,40 +114,43 @@ AppRestartAssist::cache_state_t AppRestartAssist::getCacheEntryState(const std:: throw std::logic_error("cache entry state is invalid"); } -// Read table from APPDB and append stale flag then insert to cachemap -void AppRestartAssist::readTableToMap() +// Read table(s) from APPDB and append stale flag then insert to cachemap +void AppRestartAssist::readTablesToMap() { vector keys; - m_appTable.getKeys(keys); - FieldValueTuple state(CACHE_STATE_FIELD, ""); - - for (const auto &key: keys) + for (auto it = m_appTables.begin(); it != m_appTables.end(); it++) { - vector fv; + (it->second)->getKeys(keys); + FieldValueTuple state(CACHE_STATE_FIELD, ""); - // if the fieldvalue is empty, skip - if (!m_appTable.get(key, fv)) + for (const auto &key: keys) { - continue; - } + vector fv; + + // if the fieldvalue is empty, skip + if (!(it->second)->get(key, fv)) + { + continue; + } - fv.push_back(state); - setCacheEntryState(fv, STALE); + fv.push_back(state); + setCacheEntryState(fv, STALE); - string s = joinVectorString(fv); + string s = joinVectorString(fv); - SWSS_LOG_INFO("write to cachemap: %s, key: %s, " - "%s", m_appTableName.c_str(), key.c_str(), s.c_str()); + SWSS_LOG_INFO("write to cachemap: %s, key: %s, " + "%s", (it->first).c_str(), key.c_str(), s.c_str()); - // insert to the cache map - appTableCacheMap[key] = fv; + // insert to the cache map + appTableCacheMap[it->first][key] = fv; + } + WarmStart::setWarmStartState(m_appName, WarmStart::RESTORED); + SWSS_LOG_NOTICE("Restored appDB table to %s internal cache map", (it->first).c_str()); } - WarmStart::setWarmStartState(m_appName, WarmStart::RESTORED); - SWSS_LOG_NOTICE("Restored appDB table to internal cache map"); return; } - + /* * Check and insert to CacheMap Logic: * if delete_key: @@ -154,40 +163,39 @@ void AppRestartAssist::readTableToMap() * insert with "NEW" flag. * } */ -void AppRestartAssist::insertToMap(string key, vector fvVector, bool delete_key) +void AppRestartAssist::insertToMap(string tableName, string key, vector fvVector, bool delete_key) { SWSS_LOG_INFO("Received message %s, key: %s, " - "%s, delete = %d", m_appTableName.c_str(), key.c_str(), joinVectorString(fvVector).c_str(), delete_key); - + "%s, delete = %d", tableName.c_str(), key.c_str(), joinVectorString(fvVector).c_str(), delete_key); - auto found = appTableCacheMap.find(key); + auto found = appTableCacheMap[tableName].find(key); if (delete_key) { - SWSS_LOG_NOTICE("%s, delete key: %s, ", m_appTableName.c_str(), key.c_str()); + SWSS_LOG_NOTICE("%s, delete key: %s, ", tableName.c_str(), key.c_str()); /* mark it as DELETE if exist, otherwise, no-op */ - if (found != appTableCacheMap.end()) + if (found != appTableCacheMap[tableName].end()) { setCacheEntryState(found->second, DELETE); } } - else if (found != appTableCacheMap.end()) + else if (found != appTableCacheMap[tableName].end()) { // check only the original vector range (exclude cache-state field/value) if(! contains(found->second, fvVector)) { - SWSS_LOG_NOTICE("%s, found key: %s, new value ", m_appTableName.c_str(), key.c_str()); + SWSS_LOG_NOTICE("%s, found key: %s, new value ", tableName.c_str(), key.c_str()); FieldValueTuple state(CACHE_STATE_FIELD, ""); fvVector.push_back(state); // mark as NEW flag setCacheEntryState(fvVector, NEW); - appTableCacheMap[key] = fvVector; + appTableCacheMap[tableName][key] = fvVector; } else { - SWSS_LOG_INFO("%s, found key: %s, same value", m_appTableName.c_str(), key.c_str()); + SWSS_LOG_INFO("%s, found key: %s, same value", tableName.c_str(), key.c_str()); // mark as SAME flag setCacheEntryState(found->second, SAME); @@ -196,13 +204,12 @@ void AppRestartAssist::insertToMap(string key, vector fvVector, else { // not found, mark the entry as NEW and insert to map - SWSS_LOG_NOTICE("%s, not found key: %s, new", m_appTableName.c_str(), key.c_str()); + SWSS_LOG_NOTICE("%s, not found key: %s, new", tableName.c_str(), key.c_str()); FieldValueTuple state(CACHE_STATE_FIELD, ""); fvVector.push_back(state); setCacheEntryState(fvVector, NEW); - appTableCacheMap[key] = fvVector; + appTableCacheMap[tableName][key] = fvVector; } - return; } @@ -216,42 +223,48 @@ void AppRestartAssist::insertToMap(string key, vector fvVector, */ void AppRestartAssist::reconcile() { + std::string tableName; SWSS_LOG_ENTER(); - for (auto iter = appTableCacheMap.begin(); iter != appTableCacheMap.end(); ++iter ) + for (auto tableIter = appTableCacheMap.begin(); tableIter != appTableCacheMap.end(); ++tableIter) { - string s = joinVectorString(iter->second); - auto state = getCacheEntryState(iter->second); - - if (state == SAME) - { - SWSS_LOG_INFO("%s SAME, key: %s, %s", - m_appTableName.c_str(), iter->first.c_str(), s.c_str()); - continue; - } - else if (state == STALE || state == DELETE) - { - SWSS_LOG_NOTICE("%s STALE/DELETE, key: %s, %s", - m_appTableName.c_str(), iter->first.c_str(), s.c_str()); - - //delete from appDB - m_psTable->del(iter->first); - } - else if (state == NEW) - { - SWSS_LOG_NOTICE("%s NEW, key: %s, %s", - m_appTableName.c_str(), iter->first.c_str(), s.c_str()); - - //add to appDB, exclude the state - iter->second.pop_back(); - m_psTable->set(iter->first, iter->second); - } - else + tableName = tableIter->first; + for (auto it = (tableIter->second).begin(); it != (tableIter->second).end(); ++it) { - throw std::logic_error("cache entry state is invalid"); + string s = joinVectorString(it->second); + auto state = getCacheEntryState(it->second); + + if (state == SAME) + { + SWSS_LOG_INFO("%s SAME, key: %s, %s", + tableName.c_str(), it->first.c_str(), s.c_str()); + continue; + } + else if (state == STALE || state == DELETE) + { + SWSS_LOG_NOTICE("%s STALE/DELETE, key: %s, %s", + tableName.c_str(), it->first.c_str(), s.c_str()); + + //delete from appDB + m_psTables[tableName]->del(it->first); + } + else if (state == NEW) + { + SWSS_LOG_NOTICE("%s NEW, key: %s, %s", + tableName.c_str(), it->first.c_str(), s.c_str()); + + //add to appDB, exclude the state + it->second.pop_back(); + m_psTables[tableName]->set(it->first, it->second); + } + else + { + throw std::logic_error("cache entry state is invalid"); + } } + // reconcile finished, clear the map, mark the warmstart state + appTableCacheMap[tableName].clear(); } - // reconcile finished, clear the map, mark the warmstart state appTableCacheMap.clear(); WarmStart::setWarmStartState(m_appName, WarmStart::RECONCILED); m_warmStartInProgress = false; diff --git a/warmrestart/warmRestartAssist.h b/warmrestart/warmRestartAssist.h index 227815c264..8587d84d4a 100644 --- a/warmrestart/warmRestartAssist.h +++ b/warmrestart/warmRestartAssist.h @@ -50,12 +50,14 @@ namespace swss { * } * } */ +typedef std::map Tables; +typedef std::map ProducerStateTables; + class AppRestartAssist { public: - AppRestartAssist(RedisPipeline *pipeline, - const std::string &appName, const std::string &dockerName, - ProducerStateTable *psTable, const uint32_t defaultWarmStartTimerValue = 0); + AppRestartAssist(RedisPipeline *pipeline, const std::string &appName, + const std::string &dockerName, const uint32_t defaultWarmStartTimerValue = 0); virtual ~AppRestartAssist(); /* @@ -76,13 +78,14 @@ class AppRestartAssist void startReconcileTimer(Select &s); void stopReconcileTimer(Select &s); bool checkReconcileTimer(Selectable *s); - void readTableToMap(void); - void insertToMap(std::string key, std::vector fvVector, bool delete_key); + void readTablesToMap(void); + void insertToMap(std::string tableName, std::string key, std::vector fvVector, bool delete_key); void reconcile(void); bool isWarmStartInProgress(void) { return m_warmStartInProgress; } + void registerAppTable(const std::string &tableName, ProducerStateTable *psTable); private: typedef std::map cache_state_map; @@ -96,16 +99,16 @@ class AppRestartAssist * Precedence ascent order: Default -> loading class with value -> configuration */ static const uint32_t DEFAULT_INTERNAL_TIMER_VALUE = 5; - typedef std::unordered_map> AppTableMap; + typedef std::map>> AppTableMap; // cache map to store temperary application table AppTableMap appTableCacheMap; - Table m_appTable; // table handler - std::string m_dockerName; // docker name of the application - std::string m_appName; // application name - ProducerStateTable *m_psTable; // produce state table handler - std::string m_appTableName; // application table name + RedisPipeline *m_pipeLine; + Tables m_appTables; // app tables + std::string m_dockerName; // docker name of the application + std::string m_appName; // application name + ProducerStateTables m_psTables; // producer state tables bool m_warmStartInProgress; // indicate if warm start is in progress time_t m_reconcileTimer; // reconcile timer value From 1cedf6b0381a23df3b37cb19e8d5daebe2eed3f1 Mon Sep 17 00:00:00 2001 From: Guohan Lu Date: Thu, 30 Jan 2020 22:53:08 +0000 Subject: [PATCH 39/63] Orchagent changes in sonic-swss submodule to support NAT feature. (#1125) Signed-off-by: Akhilesh Samineni akhilesh.samineni@broadcom.com --- orchagent/Makefile.am | 3 +- orchagent/aclorch.cpp | 7 + orchagent/aclorch.h | 8 +- orchagent/copporch.cpp | 11 +- orchagent/intfsorch.cpp | 79 +- orchagent/intfsorch.h | 2 + orchagent/main.cpp | 18 + orchagent/natorch.cpp | 4858 +++++++++++++++++++++++++ orchagent/natorch.h | 349 ++ orchagent/nexthopgroupkey.h | 12 + orchagent/nexthopkey.h | 5 + orchagent/orchdaemon.cpp | 16 + orchagent/orchdaemon.h | 1 + orchagent/port.h | 2 + orchagent/saihelper.cpp | 3 + swssconfig/sample/00-copp.config.json | 4 +- tests/conftest.py | 3 +- tests/mock_tests/Makefile.am | 3 +- tests/test_nat.py | 362 ++ 19 files changed, 5736 insertions(+), 10 deletions(-) create mode 100644 orchagent/natorch.cpp create mode 100644 orchagent/natorch.h create mode 100644 tests/test_nat.py diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index 79cfe99f6d..d431557b5c 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -55,7 +55,8 @@ orchagent_SOURCES = \ policerorch.cpp \ sfloworch.cpp \ chassisorch.cpp \ - debugcounterorch.cpp + debugcounterorch.cpp \ + natorch.cpp orchagent_SOURCES += flex_counter/flex_counter_manager.cpp flex_counter/flex_counter_stat_manager.cpp orchagent_SOURCES += debug_counter/debug_counter.cpp debug_counter/drop_counter.cpp diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index b25d18a44c..fe0163b58f 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -65,6 +65,7 @@ static acl_rule_attr_lookup_t aclL3ActionLookup = { { ACTION_PACKET_ACTION, SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION }, { ACTION_REDIRECT_ACTION, SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT }, + { ACTION_DO_NOT_NAT_ACTION, SAI_ACL_ENTRY_ATTR_ACTION_NO_NAT }, }; static acl_rule_attr_lookup_t aclMirrorStageLookup = @@ -797,6 +798,12 @@ bool AclRuleL3::validateAddAction(string attr_name, string _attr_value) action_str = ACTION_REDIRECT_ACTION; } + // handle PACKET_ACTION_DO_NOT_NAT in ACTION_PACKET_ACTION + else if (attr_value == PACKET_ACTION_DO_NOT_NAT) + { + value.aclaction.parameter.booldata = true; + action_str = ACTION_DO_NOT_NAT_ACTION; + } else { return false; diff --git a/orchagent/aclorch.h b/orchagent/aclorch.h index 5f09683f5a..1df91cea59 100644 --- a/orchagent/aclorch.h +++ b/orchagent/aclorch.h @@ -63,6 +63,7 @@ #define ACTION_PACKET_ACTION "PACKET_ACTION" #define ACTION_REDIRECT_ACTION "REDIRECT_ACTION" +#define ACTION_DO_NOT_NAT_ACTION "DO_NOT_NAT_ACTION" #define ACTION_MIRROR_ACTION "MIRROR_ACTION" #define ACTION_MIRROR_INGRESS_ACTION "MIRROR_INGRESS_ACTION" #define ACTION_MIRROR_EGRESS_ACTION "MIRROR_EGRESS_ACTION" @@ -73,9 +74,10 @@ #define ACTION_DTEL_FLOW_SAMPLE_PERCENT "FLOW_SAMPLE_PERCENT" #define ACTION_DTEL_REPORT_ALL_PACKETS "REPORT_ALL_PACKETS" -#define PACKET_ACTION_FORWARD "FORWARD" -#define PACKET_ACTION_DROP "DROP" -#define PACKET_ACTION_REDIRECT "REDIRECT" +#define PACKET_ACTION_FORWARD "FORWARD" +#define PACKET_ACTION_DROP "DROP" +#define PACKET_ACTION_REDIRECT "REDIRECT" +#define PACKET_ACTION_DO_NOT_NAT "DO_NOT_NAT" #define DTEL_FLOW_OP_NOP "NOP" #define DTEL_FLOW_OP_POSTCARD "POSTCARD" diff --git a/orchagent/copporch.cpp b/orchagent/copporch.cpp index fbe3f1080a..d6cbfaa736 100644 --- a/orchagent/copporch.cpp +++ b/orchagent/copporch.cpp @@ -18,6 +18,7 @@ extern sai_switch_api_t* sai_switch_api; extern sai_object_id_t gSwitchId; extern PortsOrch* gPortsOrch; +extern bool gIsNatSupported; static map policer_meter_map = { {"packets", SAI_METER_TYPE_PACKETS}, @@ -72,7 +73,9 @@ static map trap_id_map = { {"ttl_error", SAI_HOSTIF_TRAP_TYPE_TTL_ERROR}, {"udld", SAI_HOSTIF_TRAP_TYPE_UDLD}, {"bfd", SAI_HOSTIF_TRAP_TYPE_BFD}, - {"bfdv6", SAI_HOSTIF_TRAP_TYPE_BFDV6} + {"bfdv6", SAI_HOSTIF_TRAP_TYPE_BFDV6}, + {"src_nat_miss", SAI_HOSTIF_TRAP_TYPE_SNAT_MISS}, + {"dest_nat_miss", SAI_HOSTIF_TRAP_TYPE_DNAT_MISS} }; static map packet_action_map = { @@ -189,6 +192,12 @@ void CoppOrch::getTrapIdList(vector &trap_id_name_list, vector gDirectory; extern sai_router_interface_api_t* sai_router_intfs_api; extern sai_route_api_t* sai_route_api; extern sai_neighbor_api_t* sai_neighbor_api; +extern sai_switch_api_t* sai_switch_api; extern sai_object_id_t gSwitchId; extern PortsOrch *gPortsOrch; extern RouteOrch *gRouteOrch; extern CrmOrch *gCrmOrch; extern BufferOrch *gBufferOrch; +extern bool gIsNatSupported; const int intfsorch_pri = 35; @@ -165,6 +167,35 @@ bool IntfsOrch::setRouterIntfsMtu(const Port &port) return true; } +bool IntfsOrch::setRouterIntfsNatZoneId(Port &port) +{ + SWSS_LOG_ENTER(); + + /* Return true if the router interface is not exists */ + if (!port.m_rif_id) + { + SWSS_LOG_WARN("Router interface is not exists on %s", + port.m_alias.c_str()); + return true; + } + + sai_attribute_t attr; + attr.id = SAI_ROUTER_INTERFACE_ATTR_NAT_ZONE_ID; + attr.value.u32 = port.m_nat_zone_id; + + sai_status_t status = sai_router_intfs_api-> + set_router_interface_attribute(port.m_rif_id, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set router interface %s NAT Zone Id to %u, rv:%d", + port.m_alias.c_str(), port.m_nat_zone_id, status); + return false; + } + SWSS_LOG_NOTICE("Set router interface %s NAT Zone Id to %u", + port.m_alias.c_str(), port.m_nat_zone_id); + return true; +} + bool IntfsOrch::setRouterIntfsAdminStatus(const Port &port) { SWSS_LOG_ENTER(); @@ -404,9 +435,11 @@ void IntfsOrch::doTask(Consumer &consumer) } const vector& data = kfvFieldsValues(t); - string vrf_name = "", vnet_name = ""; + string vrf_name = "", vnet_name = "", nat_zone = ""; uint32_t mtu; bool adminUp; + uint32_t nat_zone_id = 0; + for (auto idx : data) { const auto &field = fvField(idx); @@ -419,6 +452,19 @@ void IntfsOrch::doTask(Consumer &consumer) { vnet_name = value; } + else if (field == "nat_zone") + { + try + { + nat_zone_id = (uint32_t)stoul(value); + } + catch (...) + { + SWSS_LOG_ERROR("Invalid argument %s for nat zone", value.c_str()); + continue; + } + nat_zone = value; + } else if (field == "mtu") { try @@ -452,6 +498,10 @@ void IntfsOrch::doTask(Consumer &consumer) } } } + else if (field == "nat_zone") + { + nat_zone = value; + } } if (alias == "eth0" || alias == "docker0") @@ -555,6 +605,23 @@ void IntfsOrch::doTask(Consumer &consumer) it++; continue; } + + /* Set nat zone id */ + if ((!nat_zone.empty()) and (port.m_nat_zone_id != nat_zone_id)) + { + port.m_nat_zone_id = nat_zone_id; + + if (gIsNatSupported) + { + setRouterIntfsNatZoneId(port); + } + else + { + SWSS_LOG_NOTICE("Not set router interface %s NAT Zone Id to %u, as NAT is not supported", + port.m_alias.c_str(), port.m_nat_zone_id); + } + gPortsOrch->setPort(alias, port); + } } it = consumer.m_toSync.erase(it); @@ -739,6 +806,15 @@ bool IntfsOrch::addRouterIntfs(sai_object_id_t vrf_id, Port &port) attr.value.u32 = port.m_mtu; attrs.push_back(attr); + if (gIsNatSupported) + { + attr.id = SAI_ROUTER_INTERFACE_ATTR_NAT_ZONE_ID; + attr.value.u32 = port.m_nat_zone_id; + + SWSS_LOG_INFO("Assinging NAT zone id %d to interface %s\n", attr.value.u32, port.m_alias.c_str()); + attrs.push_back(attr); + } + sai_status_t status = sai_router_intfs_api->create_router_interface(&port.m_rif_id, gSwitchId, (uint32_t)attrs.size(), attrs.data()); if (status != SAI_STATUS_SUCCESS) { @@ -779,6 +855,7 @@ bool IntfsOrch::removeRouterIntfs(Port &port) port.m_rif_id = 0; port.m_vr_id = 0; + port.m_nat_zone_id = 0; gPortsOrch->setPort(port.m_alias, port); SWSS_LOG_NOTICE("Remove router interface for port %s", port.m_alias.c_str()); diff --git a/orchagent/intfsorch.h b/orchagent/intfsorch.h index 22e41a4945..8209b5cfa1 100644 --- a/orchagent/intfsorch.h +++ b/orchagent/intfsorch.h @@ -40,7 +40,9 @@ class IntfsOrch : public Orch void decreaseRouterIntfsRefCount(const string&); bool setRouterIntfsMtu(const Port &port); + bool setRouterIntfsNatZoneId(Port &port); bool setRouterIntfsAdminStatus(const Port &port); + std::set getSubnetRoutes(); void generateInterfaceMap(); diff --git a/orchagent/main.cpp b/orchagent/main.cpp index 6d3d6d228e..e2cf42247d 100644 --- a/orchagent/main.cpp +++ b/orchagent/main.cpp @@ -50,6 +50,8 @@ bool gLogRotate = false; bool gSaiRedisLogRotate = false; bool gSyncMode = false; +extern bool gIsNatSupported; + ofstream gRecordOfs; string gRecordFile; @@ -262,6 +264,22 @@ int main(int argc, char **argv) gVirtualRouterId = attr.value.oid; SWSS_LOG_NOTICE("Get switch virtual router ID %" PRIx64, gVirtualRouterId); + /* Get the NAT supported info */ + attr.id = SAI_SWITCH_ATTR_AVAILABLE_SNAT_ENTRY; + + status = sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_NOTICE("Failed to get the SNAT available entry count, rv:%d", status); + } + else + { + if (attr.value.u32 != 0) + { + gIsNatSupported = true; + } + } + /* Create a loopback underlay router interface */ vector underlay_intf_attrs; diff --git a/orchagent/natorch.cpp b/orchagent/natorch.cpp new file mode 100644 index 0000000000..ccd0e12f44 --- /dev/null +++ b/orchagent/natorch.cpp @@ -0,0 +1,4858 @@ +/* + * Copyright 2019 Broadcom Inc. + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include "exec.h" +#include "logger.h" +#include "tokenize.h" +#include "natorch.h" +#include "notifier.h" +#include "sai_serialize.h" + +extern PortsOrch *gPortsOrch; +extern sai_object_id_t gSwitchId; +extern sai_switch_api_t *sai_switch_api; +extern sai_object_id_t gVirtualRouterId; +extern sai_nat_api_t *sai_nat_api; +extern sai_hostif_api_t *sai_hostif_api; +extern bool gIsNatSupported; +#ifdef DEBUG_FRAMEWORK +extern DebugDumpOrch *gDebugDumpOrch; +#endif +uint32_t natTimerTickCntr = 0; +bool gNhTrackingSupported = false; + +NatOrch::NatOrch(DBConnector *appDb, DBConnector *stateDb, vector &tableNames, + RouteOrch *routeOrch, NeighOrch *neighOrch): + Orch(appDb, tableNames), + m_neighOrch(neighOrch), + m_routeOrch(routeOrch), + m_countersDb(COUNTERS_DB, DBConnector::DEFAULT_UNIXSOCKET, 0), + m_countersNatTable(&m_countersDb, COUNTERS_NAT_TABLE), + m_countersNaptTable(&m_countersDb, COUNTERS_NAPT_TABLE), + m_countersTwiceNatTable(&m_countersDb, COUNTERS_TWICE_NAT_TABLE), + m_countersTwiceNaptTable(&m_countersDb, COUNTERS_TWICE_NAPT_TABLE), + m_countersGlobalNatTable(&m_countersDb, COUNTERS_GLOBAL_NAT_TABLE), + m_stateWarmRestartEnableTable(stateDb, STATE_WARM_RESTART_ENABLE_TABLE_NAME), + m_stateWarmRestartTable(stateDb, STATE_WARM_RESTART_TABLE_NAME), + m_natQueryTable(appDb, APP_NAT_TABLE_NAME), + m_naptQueryTable(appDb, APP_NAPT_TABLE_NAME), + m_twiceNatQueryTable(appDb, APP_NAT_TWICE_TABLE_NAME), + m_twiceNaptQueryTable(appDb, APP_NAPT_TWICE_TABLE_NAME), + nullIpv4Addr(0) +{ + /* Set NAT admin mode to disabled */ + admin_mode = "disabled"; + + /* Set NAT default timeout as 600 seconds */ + timeout = 600; + + /* Set NAT default tcp timeout as 86400 seconds (1 Day) */ + tcp_timeout = 86400; + + /* Set NAT default udp timeout as 300 seconds */ + udp_timeout = 300; + + /* Set entries count to 0 */ + totalEntries = totalSnatEntries = totalDnatEntries = 0; + totalStaticNatEntries = totalDynamicNatEntries = 0; + totalStaticNaptEntries = totalDynamicNaptEntries = 0; + totalStaticTwiceNatEntries = totalDynamicTwiceNatEntries = 0; + totalStaticTwiceNaptEntries = totalDynamicTwiceNaptEntries = 0; + + /* Add NAT notifications support from APPL_DB */ + SWSS_LOG_INFO("Add NAT notifications support from APPL_DB "); + m_flushNotificationsConsumer = new NotificationConsumer(appDb, "FLUSHNATREQUEST"); + auto flushNotifier = new Notifier(m_flushNotificationsConsumer, this, "FLUSHNATREQUEST"); + Orch::addExecutor(flushNotifier); + + SWSS_LOG_INFO("Add REDIS DB cleanup notification support"); + m_cleanupNotificationConsumer = new NotificationConsumer(appDb, "NAT_DB_CLEANUP_NOTIFICATION"); + auto cleanupNotifier = new Notifier(m_cleanupNotificationConsumer, this, "NAT_DB_CLEANUP_NOTIFICATION"); + Orch::addExecutor(cleanupNotifier); + + /* Start the timer to query NAT entry statistics every 5 secs and hitbits every 30 secs */ + SWSS_LOG_INFO("Start the HITBIT Timer "); + auto interval = timespec { .tv_sec = NAT_HITBIT_N_CNTRS_QUERY_PERIOD, .tv_nsec = 0 }; + m_natQueryTimer = new SelectableTimer(interval); + auto executor = new ExecutableTimer(m_natQueryTimer, this, "NAT_HITBIT_N_CNTRS_QUERY_TIMER"); + Orch::addExecutor(executor); + + /* Get the Maximum supported SNAT entries */ + SWSS_LOG_INFO("Get the Maximum supported SNAT entries"); + sai_status_t status; + sai_attribute_t attr; + memset(&attr, 0, sizeof(attr)); + attr.id = SAI_SWITCH_ATTR_AVAILABLE_SNAT_ENTRY; + maxAllowedSNatEntries = 0; + + status = sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_NOTICE("Failed to get the SNAT available entry count, rv:%d", status); + } + else + { + maxAllowedSNatEntries = attr.value.u32; + } + + /* Set default values and Max entries to counter DB */ + std::vector values; + std::string key = "Values"; + swss::FieldValueTuple p("MAX_NAT_ENTRIES", to_string(maxAllowedSNatEntries)); + swss::FieldValueTuple q("TIMEOUT", to_string(timeout)); + swss::FieldValueTuple r("UDP_TIMEOUT", to_string(udp_timeout)); + swss::FieldValueTuple s("TCP_TIMEOUT", to_string(tcp_timeout)); + values.push_back(p); + values.push_back(q); + values.push_back(r); + values.push_back(s); + m_countersGlobalNatTable.set(key, values); + +#ifdef DEBUG_FRAMEWORK + /*Register with debug framework*/ + this->m_dbgCompName = "natorch"; + gDebugDumpOrch->addDbgCompMap(m_dbgCompName, this); +#endif + + char *platform = getenv("platform"); + if (platform && strstr(platform, BRCM_PLATFORM_SUBSTRING)) + { + gNhTrackingSupported = true; + } + SWSS_LOG_NOTICE("DNAT nexthop tracking is %s", ((gNhTrackingSupported == true) ? "enabled" : "disabled")); +} + +/* Process notifications for changes in Neighbor entries and route entries + * that resolve the DNAT entries's next-hop for the translated ip address. + */ +void NatOrch::update(SubjectType type, void *cntx) +{ + SWSS_LOG_ENTER(); + + assert(cntx); + + switch(type) + { + case SUBJECT_TYPE_NEXTHOP_CHANGE: + { + NextHopUpdate *update = static_cast(cntx); + updateNextHop(*update); + break; + } + case SUBJECT_TYPE_NEIGH_CHANGE: + { + NeighborUpdate *update = static_cast(cntx); + updateNeighbor(*update); + break; + } + default: + /* Received update in which we are not interested + * Ignore it + */ + return; + } +} + +bool NatOrch::isNextHopResolved(const NextHopUpdate &update) +{ + // Ignore default route and subnet based routes + if ((update.prefix.isDefaultRoute()) || + ((update.nexthopGroup.getSize() == 1) && (update.nexthopGroup.hasIntfNextHop()))) + { + SWSS_LOG_INFO("Ignore default or subnet nexthop update event for ip %s", update.destination.to_string().c_str()); + return false; + } + if (update.nexthopGroup == NextHopGroupKey()) + { + return false; + } + return true; +} + +// Route nexthop change notification to be processed for the DNAT entries +void NatOrch::updateNextHop(const NextHopUpdate& update) +{ + SWSS_LOG_ENTER(); + + auto it = m_nhResolvCache.find(update.destination); + + if (it == m_nhResolvCache.end()) + { + // No dnat entries to be resolved on this nexthop + return; + } + auto &nhCache = it->second; + + // If the ECMP nexthop group did not change + if (update.nexthopGroup == nhCache.nextHopGroup) + { + return; + } + + SWSS_LOG_INFO("Nexthop update event for dnat entries with translated ip %s", + update.destination.to_string().c_str()); + + if ((nhCache.nextHopGroup == NextHopGroupKey()) && (isNextHopResolved(update))) + { + nhCache.nextHopGroup = update.nexthopGroup; + if (! nhCache.neighResolved) + { + // Add all DNAT entries whose translated destination has nexthop resolved. + SWSS_LOG_INFO("Nexthop resolved for dnat entries with translated ip %s, adding the entries", + update.destination.to_string().c_str()); + addNhCacheDnatEntries(update.destination, 1); + } + } + else if ((nhCache.nextHopGroup != NextHopGroupKey()) && (! isNextHopResolved(update))) + { + nhCache.nextHopGroup = NextHopGroupKey(); + if (! nhCache.neighResolved) + { + // Delete all DNAT entries whose translated destination has nexthop unresolved. + SWSS_LOG_INFO("Nexthop unresolved for dnat entries with translated ip %s, deleting the entries", + update.destination.to_string().c_str()); + addNhCacheDnatEntries(update.destination, 0); + } + } + else if ((nhCache.nextHopGroup != update.nexthopGroup) && (isNextHopResolved(update))) + { + nhCache.nextHopGroup = update.nexthopGroup; + if (! nhCache.neighResolved) + { + // Add and delete all DNAT entries whose translated destination has ECMP group/NH modified. + SWSS_LOG_INFO("Nexthop/ECMP modified for dnat entries with translated ip %s, deleting and re-adding the entries", + update.destination.to_string().c_str()); + addNhCacheDnatEntries(update.destination, 0); + addNhCacheDnatEntries(update.destination, 1); + } + } +} + +// Neighbor change notification to be processed for the DNAT entries +void NatOrch::updateNeighbor(const NeighborUpdate& update) +{ + SWSS_LOG_ENTER(); + + auto it = m_nhResolvCache.find(update.entry.ip_address); + + // Check if the neighbor update IP matches the translated DNAT IP we are interested in + if (it == m_nhResolvCache.end()) + { + return; + } + auto &nhCache = it->second; + + SWSS_LOG_INFO("Neighbor update event for dnat entries with translated ip %s", + update.entry.ip_address.to_string().c_str()); + + if ((nhCache.neighResolved) && (! update.add)) + { + // Delete all DNAT entries whose translated destination is unresolved. + SWSS_LOG_INFO("Neighbor unresolved for dnat entries with translated ip %s, deleting the entries", + update.entry.ip_address.to_string().c_str()); + + addNhCacheDnatEntries(update.entry.ip_address, 0); + if (nhCache.nextHopGroup != NextHopGroupKey()) + { + SWSS_LOG_INFO("Nexthop exists for dnat entries with translated ip %s, adding the entries", + update.entry.ip_address.to_string().c_str()); + addNhCacheDnatEntries(update.entry.ip_address, 1); + } + } + else if ((! nhCache.neighResolved) && (update.add)) + { + if (nhCache.nextHopGroup != NextHopGroupKey()) + { + SWSS_LOG_INFO("Neighbor resolved for dnat entries with translated ip %s, deleting the entries added with route nexthop", + update.entry.ip_address.to_string().c_str()); + addNhCacheDnatEntries(update.entry.ip_address, 0); + } + // Add all DNAT entries whose translated destination is resolved. + SWSS_LOG_INFO("Neighbor resolved for dnat entries with translated ip %s, adding the entries", + update.entry.ip_address.to_string().c_str()); + addNhCacheDnatEntries(update.entry.ip_address, 1); + } + nhCache.neighResolved = update.add; +} + +/* Process all the dependent DNAT entries to handle the changes + * in neighbor or nexthop notifications + */ +void NatOrch::addNhCacheDnatEntries(const IpAddress &nhIp, bool add) +{ + SWSS_LOG_ENTER(); + auto it = m_nhResolvCache.find(nhIp); + + if (it == m_nhResolvCache.end()) + { + return; + } + auto &nhCache = it->second; + + if (nhCache.dnatIp != nullIpv4Addr) + { + auto natIter = m_natEntries.find(nhCache.dnatIp); + if (natIter != m_natEntries.end()) + { + if (add) + { + addHwDnatEntry(natIter->first); + } + else + { + removeHwDnatEntry(natIter->first); + } + } + } + auto cIter = nhCache.dnapt.begin(); + while (cIter != nhCache.dnapt.end()) + { + auto naptIter = m_naptEntries.find(*cIter); + if (naptIter != m_naptEntries.end()) + { + if (add) + { + addHwDnaptEntry(naptIter->first); + } + else + { + removeHwDnaptEntry(naptIter->first); + } + } + cIter++; + } + auto tIter = nhCache.twiceNat.begin(); + while (tIter != nhCache.twiceNat.end()) + { + auto tnatIter = m_twiceNatEntries.find(*tIter); + if (tnatIter != m_twiceNatEntries.end()) + { + if (add) + { + addHwTwiceNatEntry(tnatIter->first); + } + else + { + removeHwTwiceNatEntry(tnatIter->first); + } + } + tIter++; + } + auto tpIter = nhCache.twiceNapt.begin(); + while (tpIter != nhCache.twiceNapt.end()) + { + auto tnaptIter = m_twiceNaptEntries.find(*tpIter); + if (tnaptIter != m_twiceNaptEntries.end()) + { + if (add) + { + addHwTwiceNaptEntry(tnaptIter->first); + } + else + { + removeHwTwiceNaptEntry(tnaptIter->first); + } + } + tpIter++; + } +} + +/* Cache the DNAT entry in the NH resolution cache. + * Only if the nexthop is resolved is the DNAT entry added to hardware. + */ +void NatOrch::addDnatToNhCache(const IpAddress &translatedIp, const IpAddress &dstIp) +{ + NeighborEntry neighEntry; + MacAddress macAddr; + DnatEntries dnatEntries; + + SWSS_LOG_ENTER(); + auto cIter = m_nhResolvCache.find(translatedIp); + + SWSS_LOG_INFO("Adding to NH cache indexed by translated ip %s, the DNAT entry with ip %s", + translatedIp.to_string().c_str(), dstIp.to_string().c_str()); + if (cIter == m_nhResolvCache.end()) + { + dnatEntries.dnatIp = dstIp; + dnatEntries.nextHopGroup = NextHopGroupKey(); + dnatEntries.neighResolved = false; + + if (m_neighOrch->getNeighborEntry(translatedIp, neighEntry, macAddr)) + { + dnatEntries.neighResolved = true; + SWSS_LOG_INFO("Resolved by a neighbor entry, adding to hardware"); + addHwDnatEntry(dstIp); + } + m_nhResolvCache[translatedIp] = dnatEntries; + m_routeOrch->attach(this, translatedIp); + return; + } + else + { + if ((cIter->second).dnatIp != dstIp) + { + (cIter->second).dnatIp = dstIp; + if ((cIter->second).neighResolved || ((cIter->second).nextHopGroup != NextHopGroupKey())) + { + SWSS_LOG_INFO("Resolved by a neighbor or route entry, adding to hardware"); + addHwDnatEntry(dstIp); + } + } + } +} + +/* Cache the Twice NAT entry in the NH resolution cache. + * Only if the translated dst nexthop is resolved is the Twice NAT entry added to hardware. + */ +void NatOrch::addTwiceNatToNhCache(const IpAddress &translatedIp, const TwiceNatEntryKey &key) +{ + NeighborEntry neighEntry; + MacAddress macAddr; + DnatEntries dnatEntries; + + SWSS_LOG_ENTER(); + auto cIter = m_nhResolvCache.find(translatedIp); + + SWSS_LOG_INFO("Adding to NH cache indexed by translated ip %s, the Twice NAT entry with src ip %s, dst ip %s", + translatedIp.to_string().c_str(), key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str()); + if (cIter == m_nhResolvCache.end()) + { + dnatEntries.dnatIp = nullIpv4Addr; + dnatEntries.nextHopGroup = NextHopGroupKey(); + dnatEntries.twiceNat.insert(key); + dnatEntries.neighResolved = false; + if (m_neighOrch->getNeighborEntry(translatedIp, neighEntry, macAddr)) + { + dnatEntries.neighResolved = true; + SWSS_LOG_INFO("Resolved by a neighbor entry, adding to hardware"); + addHwTwiceNatEntry(key); + } + m_nhResolvCache[translatedIp] = dnatEntries; + m_routeOrch->attach(this, translatedIp); + return; + } + else + { + auto naptIter = ((cIter->second).twiceNat).find(key); + if (naptIter == ((cIter->second).twiceNat).end()) + { + ((cIter->second).twiceNat).insert(key); + if ((cIter->second).neighResolved || ((cIter->second).nextHopGroup != NextHopGroupKey())) + { + SWSS_LOG_INFO("Twice NAT resolved by a neighbor or route entry, adding to hardware"); + addHwTwiceNatEntry(key); + } + } + } +} + +/* Cache the Twice NAPT entry in the NH resolution cache. + * Only if the translated dst nexthop is resolved is the Twice NAPT entry added to hardware. + */ +void NatOrch::addTwiceNaptToNhCache(const IpAddress &translatedIp, const TwiceNaptEntryKey &key) +{ + NeighborEntry neighEntry; + MacAddress macAddr; + DnatEntries dnatEntries; + + SWSS_LOG_ENTER(); + auto cIter = m_nhResolvCache.find(translatedIp); + + SWSS_LOG_INFO("Adding to NH cache indexed by translated ip %s, the Twice NAPT entry with src ip %s, src port %d, dst ip %s, dst port %d", + translatedIp.to_string().c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), + key.dst_l4_port); + if (cIter == m_nhResolvCache.end()) + { + dnatEntries.dnatIp = nullIpv4Addr; + dnatEntries.nextHopGroup = NextHopGroupKey(); + dnatEntries.twiceNapt.insert(key); + dnatEntries.neighResolved = false; + if (m_neighOrch->getNeighborEntry(translatedIp, neighEntry, macAddr)) + { + dnatEntries.neighResolved = true; + SWSS_LOG_INFO("Resolved by a neighbor entry, adding to hardware"); + addHwTwiceNaptEntry(key); + } + m_nhResolvCache[translatedIp] = dnatEntries; + m_routeOrch->attach(this, translatedIp); + return; + } + else + { + auto naptIter = ((cIter->second).twiceNapt).find(key); + if (naptIter == ((cIter->second).twiceNapt).end()) + { + ((cIter->second).twiceNapt).insert(key); + if ((cIter->second).neighResolved || ((cIter->second).nextHopGroup != NextHopGroupKey())) + { + SWSS_LOG_INFO("Twice NAPT resolved by a neighbor or route entry, adding to hardware"); + addHwTwiceNaptEntry(key); + } + } + } +} + +// Remove the DNAT entry from the NH resolution cache. +void NatOrch::removeDnatFromNhCache(const IpAddress &translatedIp, const IpAddress &dstIp) +{ + SWSS_LOG_ENTER(); + + auto cIter = m_nhResolvCache.find(translatedIp); + + SWSS_LOG_INFO("Removing from NH cache the DNAT entry with ip %s, indexed by translated ip %s", + dstIp.to_string().c_str(), translatedIp.to_string().c_str()); + if (cIter == m_nhResolvCache.end()) + { + SWSS_LOG_INFO("Translated IP %s doesn't exist in NH resolve cache, cannot delete DNAT entry %s", + translatedIp.to_string().c_str(), dstIp.to_string().c_str()); + return; + } + + DnatEntries &dnatEntries = cIter->second; + + if (dnatEntries.dnatIp != dstIp) + { + SWSS_LOG_INFO("DNAT entry %s doesn't exist in NH resolve cache", dstIp.to_string().c_str()); + return; + } + dnatEntries.dnatIp = nullIpv4Addr; + + if ((dnatEntries.neighResolved) || (dnatEntries.nextHopGroup != NextHopGroupKey())) + { + removeHwDnatEntry(dstIp); + } + + m_natEntries.erase(dstIp); + + if (dnatEntries.dnapt.empty() && (dnatEntries.dnatIp == nullIpv4Addr) && + dnatEntries.twiceNat.empty() && dnatEntries.twiceNapt.empty()) + { + SWSS_LOG_INFO("No NAT/NAPT entries waiting for NH resolution of translated-ip %s", translatedIp.to_string().c_str()); + m_routeOrch->detach(this, translatedIp); + m_nhResolvCache.erase(translatedIp); + } +} + +/* Cache the DNAPT entry in the NH resolution cache. + * Only if the nexthop is resolved is the DNAT entry added to hardware. + */ +void NatOrch::addDnaptToNhCache(const IpAddress &translatedIp, const NaptEntryKey &key) +{ + NeighborEntry neighEntry; + MacAddress macAddr; + DnatEntries dnatEntries; + + SWSS_LOG_ENTER(); + auto cIter = m_nhResolvCache.find(translatedIp); + + SWSS_LOG_INFO("Adding to NH cache indexed by translated ip %s, the DNAPT NAT entry with proto %s, ip %s, port %d", + translatedIp.to_string().c_str(), key.prototype.c_str(), key.ip_address.to_string().c_str(), key.l4_port); + + if (cIter == m_nhResolvCache.end()) + { + dnatEntries.dnatIp = nullIpv4Addr; + dnatEntries.nextHopGroup = NextHopGroupKey(); + dnatEntries.dnapt.insert(key); + dnatEntries.neighResolved = false; + if (m_neighOrch->getNeighborEntry(translatedIp, neighEntry, macAddr)) + { + dnatEntries.neighResolved = true; + SWSS_LOG_INFO("Resolved by a neighbor entry, adding to hardware"); + addHwDnaptEntry(key); + } + m_nhResolvCache[translatedIp] = dnatEntries; + m_routeOrch->attach(this, translatedIp); + return; + } + else + { + auto naptIter = ((cIter->second).dnapt).find(key); + if (naptIter == ((cIter->second).dnapt).end()) + { + ((cIter->second).dnapt).insert(key); + if ((cIter->second).neighResolved || ((cIter->second).nextHopGroup != NextHopGroupKey())) + { + SWSS_LOG_INFO("Resolved by a neighbor or route entry, adding to hardware"); + addHwDnaptEntry(key); + } + } + } +} + +// Remove the DNAPT entry from the NH resolution cache. +void NatOrch::removeDnaptFromNhCache(const IpAddress &translatedIp, const NaptEntryKey &key) +{ + SWSS_LOG_ENTER(); + + auto cIter = m_nhResolvCache.find(translatedIp); + + SWSS_LOG_INFO("Removing from NH cache the DNAPT NAT entry with proto %s, ip %s, port %d, indexed by translated ip %s", + key.prototype.c_str(), key.ip_address.to_string().c_str(), key.l4_port, translatedIp.to_string().c_str()); + + if (cIter == m_nhResolvCache.end()) + { + SWSS_LOG_INFO("Translated IP %s doesn't exist in NH resolve cache, cannot delete DNAPT entry with ip %s, l4port %d", + translatedIp.to_string().c_str(), key.ip_address.to_string().c_str(), key.l4_port); + return; + } + DnatEntries &dnatEntries = cIter->second; + + if (dnatEntries.dnapt.find(key) == dnatEntries.dnapt.end()) + { + SWSS_LOG_INFO("DNAPT entry ip %s, l4port %d doesn't exist in NH resolve cache", + key.ip_address.to_string().c_str(), key.l4_port); + return; + } + if ((dnatEntries.neighResolved) || (dnatEntries.nextHopGroup != NextHopGroupKey())) + { + removeHwDnaptEntry(key); + } + dnatEntries.dnapt.erase(key); + + m_naptEntries.erase(key); + + if (dnatEntries.dnapt.empty() && (dnatEntries.dnatIp == nullIpv4Addr) && + dnatEntries.twiceNat.empty() && dnatEntries.twiceNapt.empty()) + { + SWSS_LOG_INFO("No NAT/NAPT entries waiting for NH resolution of translated-ip %s", + translatedIp.to_string().c_str()); + m_routeOrch->detach(this, translatedIp); + m_nhResolvCache.erase(translatedIp); + } +} + +// Remove the Twice NAT entry from the NH resolution cache. +void NatOrch::removeTwiceNatFromNhCache(const IpAddress &translatedIp, const TwiceNatEntryKey &key) +{ + SWSS_LOG_ENTER(); + + auto cIter = m_nhResolvCache.find(translatedIp); + + SWSS_LOG_INFO("Removing from NH cache the Twice NAT entry with src ip %s, dst ip %s, indexed by translated ip %s", + key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str(), translatedIp.to_string().c_str()); + + if (cIter == m_nhResolvCache.end()) + { + SWSS_LOG_INFO("Translated IP %s doesn't exist in NH resolve cache, cannot delete Twice NAT entry with src ip %s, dst ip %s", + translatedIp.to_string().c_str(), key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str()); + return; + } + DnatEntries &dnatEntries = cIter->second; + + if (dnatEntries.twiceNat.find(key) == dnatEntries.twiceNat.end()) + { + SWSS_LOG_NOTICE("Twice NAT entry with src ip %s, dst ip %s doesn't exist in NH resolve cache", + key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str()); + return; + } + if ((dnatEntries.neighResolved) || (dnatEntries.nextHopGroup != NextHopGroupKey())) + { + removeHwTwiceNatEntry(key); + } + dnatEntries.twiceNat.erase(key); + + m_twiceNatEntries.erase(key); + + if (dnatEntries.dnapt.empty() && (dnatEntries.dnatIp == nullIpv4Addr) && + dnatEntries.twiceNat.empty() && dnatEntries.twiceNapt.empty()) + { + SWSS_LOG_INFO("No NAT/NAPT/Twice NAT entries waiting for NH resolution of translated-ip %s", + translatedIp.to_string().c_str()); + m_routeOrch->detach(this, translatedIp); + m_nhResolvCache.erase(translatedIp); + } +} + +// Remove the Twice NAPT entry from the NH resolution cache. +void NatOrch::removeTwiceNaptFromNhCache(const IpAddress &translatedIp, const TwiceNaptEntryKey &key) +{ + SWSS_LOG_ENTER(); + + auto cIter = m_nhResolvCache.find(translatedIp); + + SWSS_LOG_INFO("Removing from NH cache the Twice NAPT entry with proto %s, src ip %s, src port %d, dst ip %s, dst port %d, indexed by translated ip %s", + key.prototype.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), key.dst_l4_port, + translatedIp.to_string().c_str()); + + if (cIter == m_nhResolvCache.end()) + { + SWSS_LOG_INFO("Translated IP %s doesn't exist in NH resolve cache, cannot delete Twice NAPT entry with proto %s, \ + src ip %s, src port %d, dst ip %s, dst port %d", translatedIp.to_string().c_str(), + key.prototype.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), key.dst_l4_port); + return; + } + DnatEntries &dnatEntries = cIter->second; + + if (dnatEntries.twiceNapt.find(key) == dnatEntries.twiceNapt.end()) + { + SWSS_LOG_NOTICE("Twice NAPT entry with proto %s, src ip %s, src port %d, dst ip %s, dst port %d doesn't exist in NH resolve cache", + key.prototype.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), key.dst_l4_port); + return; + } + if ((dnatEntries.neighResolved) || (dnatEntries.nextHopGroup != NextHopGroupKey())) + { + removeHwTwiceNaptEntry(key); + } + dnatEntries.twiceNapt.erase(key); + + m_twiceNaptEntries.erase(key); + + if (dnatEntries.dnapt.empty() && (dnatEntries.dnatIp == nullIpv4Addr) && + dnatEntries.twiceNat.empty() && dnatEntries.twiceNapt.empty()) + { + SWSS_LOG_INFO("No NAT/NAPT/Twice NAT/Twice NAPT entries waiting for NH resolution of translated-ip %s", + translatedIp.to_string().c_str()); + m_routeOrch->detach(this, translatedIp); + m_nhResolvCache.erase(translatedIp); + } +} + +// Add the DNAT entry after nexthop resolution, to the hardware +bool NatOrch::addHwDnatEntry(const IpAddress &ip_address) +{ + uint32_t attr_count; + sai_nat_entry_t dnat_entry; + sai_attribute_t nat_entry_attr[5]; + sai_status_t status; + + SWSS_LOG_ENTER(); + SWSS_LOG_INFO("Create DNAT entry for ip %s, as nexthop is resolved", ip_address.to_string().c_str()); + + if (m_natEntries.find(ip_address) == m_natEntries.end()) + { + SWSS_LOG_INFO("NAT entry isn't found for ip %s", ip_address.to_string().c_str()); + return false; + } + + NatEntryValue entry = m_natEntries[ip_address]; + + memset(nat_entry_attr, 0, sizeof(nat_entry_attr)); + + nat_entry_attr[0].id = SAI_NAT_ENTRY_ATTR_NAT_TYPE; + nat_entry_attr[0].value.u32 = SAI_NAT_TYPE_DESTINATION_NAT; + nat_entry_attr[1].id = SAI_NAT_ENTRY_ATTR_DST_IP; + nat_entry_attr[1].value.u32 = entry.translated_ip.getV4Addr(); + nat_entry_attr[2].id = SAI_NAT_ENTRY_ATTR_DST_IP_MASK; + nat_entry_attr[2].value.u32 = 0xffffffff; + nat_entry_attr[3].id = SAI_NAT_ENTRY_ATTR_ENABLE_PACKET_COUNT; + nat_entry_attr[3].value.booldata = true; + nat_entry_attr[4].id = SAI_NAT_ENTRY_ATTR_ENABLE_BYTE_COUNT; + nat_entry_attr[4].value.booldata = true; + + attr_count = 5; + + memset(&dnat_entry, 0, sizeof(dnat_entry)); + + dnat_entry.vr_id = gVirtualRouterId; + dnat_entry.switch_id = gSwitchId; + dnat_entry.data.key.dst_ip = ip_address.getV4Addr(); + dnat_entry.data.mask.dst_ip = 0xffffffff; + + status = sai_nat_api->create_nat_entry(&dnat_entry, attr_count, nat_entry_attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create %s DNAT NAT entry with ip %s and it's translated ip %s", + entry.entry_type.c_str(), ip_address.to_string().c_str(), entry.translated_ip.to_string().c_str()); + + return false; + } + + SWSS_LOG_NOTICE("Created %s DNAT NAT entry with ip %s and it's translated ip %s", + entry.entry_type.c_str(), ip_address.to_string().c_str(), entry.translated_ip.to_string().c_str()); + + updateNatCounters(ip_address, 0, 0); + m_natEntries[ip_address].addedToHw = true; + + if (entry.entry_type == "static") + { + totalStaticNatEntries++; + updateStaticNatCounters(totalStaticNatEntries); + } + else + { + totalDynamicNatEntries++; + updateDynamicNatCounters(totalDynamicNatEntries); + } + totalDnatEntries++; + updateDnatCounters(totalDnatEntries); + totalEntries++; + + return true; +} + +// Add the DNAPT entry after nexthop resolution, to the hardware +bool NatOrch::addHwDnaptEntry(const NaptEntryKey &key) +{ + uint32_t attr_count; + sai_nat_entry_t dnat_entry; + sai_attribute_t nat_entry_attr[6]; + uint8_t ip_protocol = ((key.prototype == "TCP") ? IPPROTO_TCP : IPPROTO_UDP); + sai_status_t status; + + SWSS_LOG_ENTER(); + SWSS_LOG_INFO("Create DNAPT entry for proto %s, dest-ip %s, l4-port %d, as nexthop is resolved", + key.prototype.c_str(), key.ip_address.to_string().c_str(), key.l4_port); + + if (m_naptEntries.find(key) == m_naptEntries.end()) + { + SWSS_LOG_INFO("NAPT entry isn't found for Prototype - %s, ip - %s and port - %d", + key.prototype.c_str(), key.ip_address.to_string().c_str(), key.l4_port); + return false; + } + + NaptEntryValue entry = m_naptEntries[key]; + + memset(nat_entry_attr, 0, sizeof(nat_entry_attr)); + + nat_entry_attr[0].id = SAI_NAT_ENTRY_ATTR_NAT_TYPE; + nat_entry_attr[0].value.u32 = SAI_NAT_TYPE_DESTINATION_NAT; + nat_entry_attr[1].id = SAI_NAT_ENTRY_ATTR_DST_IP; + nat_entry_attr[2].id = SAI_NAT_ENTRY_ATTR_DST_IP_MASK; + nat_entry_attr[3].id = SAI_NAT_ENTRY_ATTR_L4_DST_PORT; + nat_entry_attr[1].value.u32 = entry.translated_ip.getV4Addr(); + nat_entry_attr[2].value.u32 = 0xffffffff; + nat_entry_attr[3].value.u16 = (uint16_t)(entry.translated_l4_port); + nat_entry_attr[4].id = SAI_NAT_ENTRY_ATTR_ENABLE_PACKET_COUNT; + nat_entry_attr[4].value.booldata = true; + nat_entry_attr[5].id = SAI_NAT_ENTRY_ATTR_ENABLE_BYTE_COUNT; + nat_entry_attr[5].value.booldata = true; + + attr_count = 6; + + memset(&dnat_entry, 0, sizeof(dnat_entry)); + + dnat_entry.vr_id = gVirtualRouterId; + dnat_entry.switch_id = gSwitchId; + dnat_entry.data.key.dst_ip = key.ip_address.getV4Addr(); + dnat_entry.data.key.l4_dst_port = (uint16_t)(key.l4_port); + dnat_entry.data.mask.dst_ip = 0xffffffff; + dnat_entry.data.mask.l4_dst_port = 0xffff; + dnat_entry.data.key.proto = ip_protocol; + dnat_entry.data.mask.proto = 0xff; + + status = sai_nat_api->create_nat_entry(&dnat_entry, attr_count, nat_entry_attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create %s DNAT NAPT entry with ip %s, port %d, prototype %s and it's translated ip %s, translated port %d", + entry.entry_type.c_str(), key.ip_address.to_string().c_str(), key.l4_port, key.prototype.c_str(), + entry.translated_ip.to_string().c_str(), entry.translated_l4_port); + return false; + } + + SWSS_LOG_NOTICE("Created %s DNAT NAPT entry with ip %s, port %d, prototype %s and it's translated ip %s, translated port %d", + entry.entry_type.c_str(), key.ip_address.to_string().c_str(), key.l4_port, key.prototype.c_str(), + entry.translated_ip.to_string().c_str(), entry.translated_l4_port); + + m_naptEntries[key].addedToHw = true; + updateNaptCounters(key.prototype.c_str(), key.ip_address, key.l4_port, 0, 0); + + if (entry.entry_type == "static") + { + totalStaticNaptEntries++; + updateStaticNaptCounters(totalStaticNaptEntries); + } + else + { + totalDynamicNaptEntries++; + updateDynamicNaptCounters(totalDynamicNaptEntries); + } + totalDnatEntries++; + updateDnatCounters(totalDnatEntries); + totalEntries++; + + return true; +} + +// Remove the DNAT entry from the hardware +bool NatOrch::removeHwDnatEntry(const IpAddress &dstIp) +{ + sai_nat_entry_t dnat_entry; + sai_status_t status; + + SWSS_LOG_ENTER(); + SWSS_LOG_INFO("Deleting DNAT entry ip %s from hardware", dstIp.to_string().c_str()); + + /* Check the entry is present in cache */ + if (m_natEntries.find(dstIp) == m_natEntries.end()) + { + SWSS_LOG_ERROR("DNAT entry isn't found for ip %s", dstIp.to_string().c_str()); + + return false; + } + + if (m_natEntries[dstIp].addedToHw == false) + { + SWSS_LOG_INFO("DNAT entry isn't added to h/w, for ip %s", dstIp.to_string().c_str()); + + return false; + } + + NatEntryValue entry = m_natEntries[dstIp]; + + m_natEntries[dstIp].addedToHw = false; + + memset(&dnat_entry, 0, sizeof(dnat_entry)); + + dnat_entry.vr_id = gVirtualRouterId; + dnat_entry.switch_id = gSwitchId; + dnat_entry.data.key.dst_ip = dstIp.getV4Addr(); + dnat_entry.data.mask.dst_ip = 0xffffffff; + + status = sai_nat_api->remove_nat_entry(&dnat_entry); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_INFO("Failed to remove %s DNAT NAT entry with ip %s and it's translated ip %s", + entry.entry_type.c_str(), dstIp.to_string().c_str(), entry.translated_ip.to_string().c_str()); + + return false; + } + + SWSS_LOG_NOTICE("Removed %s DNAT NAT entry with ip %s and it's translated ip %s", + entry.entry_type.c_str(), dstIp.to_string().c_str(), entry.translated_ip.to_string().c_str()); + + deleteNatCounters(dstIp); + + if (entry.entry_type == "static") + { + if (totalStaticNatEntries) + { + totalStaticNatEntries--; + updateStaticNatCounters(totalStaticNatEntries); + } + } + else + { + if (totalDynamicNatEntries) + { + totalDynamicNatEntries--; + updateDynamicNatCounters(totalDynamicNatEntries); + } + } + + if (totalDnatEntries) + { + totalDnatEntries--; + updateDnatCounters(totalDnatEntries); + } + + if (totalEntries) + { + totalEntries--; + } + + return true; +} + +// Remove the Twice NAT entry from the hardware +bool NatOrch::removeHwTwiceNatEntry(const TwiceNatEntryKey &key) +{ + sai_nat_entry_t dbl_nat_entry; + sai_status_t status; + + SWSS_LOG_ENTER(); + SWSS_LOG_INFO("Deleting Twice NAT entry src ip %s, dst ip %s from the hardware", + key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str()); + + /* Check the entry is present in cache */ + if (m_twiceNatEntries.find(key) == m_twiceNatEntries.end()) + { + SWSS_LOG_ERROR("Twice NAT entry isn't found for src ip %s, dst ip %s", + key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str()); + + return false; + } + + if (m_twiceNatEntries[key].addedToHw == false) + { + SWSS_LOG_INFO("Twice NAT entry isn't added to hardware, for src ip %s, dst ip %s", + key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str()); + return false; + } + + TwiceNatEntryValue value = m_twiceNatEntries[key]; + + m_twiceNatEntries[key].addedToHw = false; + + memset(&dbl_nat_entry, 0, sizeof(dbl_nat_entry)); + + dbl_nat_entry.vr_id = gVirtualRouterId; + dbl_nat_entry.switch_id = gSwitchId; + dbl_nat_entry.data.key.src_ip = key.src_ip.getV4Addr(); + dbl_nat_entry.data.mask.src_ip = 0xffffffff; + dbl_nat_entry.data.key.dst_ip = key.dst_ip.getV4Addr(); + dbl_nat_entry.data.mask.dst_ip = 0xffffffff; + + + status = sai_nat_api->remove_nat_entry(&dbl_nat_entry); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_INFO("Failed to remove Twice NAT entry with src-ip %s, dst-ip %s", + key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str()); + + return false; + } + SWSS_LOG_NOTICE("Removed Twice NAT entry with src-ip %s, dst-ip %s", + key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str()); + + deleteTwiceNatCounters(key); + m_twiceNatEntries.erase(key); + + if (value.entry_type == "static") + { + if (totalStaticTwiceNatEntries) + { + totalStaticTwiceNatEntries--; + updateStaticTwiceNatCounters(totalStaticTwiceNatEntries); + } + } + else + { + if (totalDynamicTwiceNatEntries) + { + totalDynamicTwiceNatEntries--; + updateDynamicTwiceNatCounters(totalDynamicTwiceNatEntries); + } + } + + if (totalSnatEntries) + { + totalSnatEntries--; + updateSnatCounters(totalSnatEntries); + } + + if (totalDnatEntries) + { + totalDnatEntries--; + updateDnatCounters(totalDnatEntries); + } + + if (totalEntries >= 2) + { + // Each Twice NAT entry is equivalent to 1 SNAT and 1 DNAT entry together + totalEntries -= 2; + } + + return true; +} + +// Remove the DNAPT entry from the hardware +bool NatOrch::removeHwDnaptEntry(const NaptEntryKey &key) +{ + sai_nat_entry_t dnat_entry; + sai_status_t status; + uint8_t ip_protocol = ((key.prototype == "TCP") ? IPPROTO_TCP : IPPROTO_UDP); + + SWSS_LOG_ENTER(); + SWSS_LOG_INFO("Delete DNAPT entry for proto %s, dest-ip %s, l4-port %d", + key.prototype.c_str(), key.ip_address.to_string().c_str(), key.l4_port); + + /* Check the entry is present in cache */ + if (m_naptEntries.find(key) == m_naptEntries.end()) + { + SWSS_LOG_ERROR("DNAPT entry isn't found for ip %s, l4-port %d", key.ip_address.to_string().c_str(), key.l4_port); + + return false; + } + + if (m_naptEntries[key].addedToHw == false) + { + SWSS_LOG_ERROR("DNAPT entry isn't added to hardware, for ip %s, l4-port %d", key.ip_address.to_string().c_str(), key.l4_port); + + return false; + } + + NaptEntryValue entry = m_naptEntries[key]; + + m_naptEntries[key].addedToHw = false; + + memset(&dnat_entry, 0, sizeof(dnat_entry)); + + dnat_entry.vr_id = gVirtualRouterId; + dnat_entry.switch_id = gSwitchId; + dnat_entry.data.key.dst_ip = key.ip_address.getV4Addr(); + dnat_entry.data.key.l4_dst_port = (uint16_t)(key.l4_port); + dnat_entry.data.mask.dst_ip = 0xffffffff; + dnat_entry.data.mask.l4_dst_port = 0xffff; + dnat_entry.data.key.proto = ip_protocol; + dnat_entry.data.mask.proto = 0xff; + + status = sai_nat_api->remove_nat_entry(&dnat_entry); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_INFO("Failed to remove %s DNAT NAPT entry with ip %s, port %d, prototype %s and it's translated ip %s, translated port %d", + entry.entry_type.c_str(), key.ip_address.to_string().c_str(), key.l4_port, key.prototype.c_str(), + entry.translated_ip.to_string().c_str(), entry.translated_l4_port); + + + return false; + } + + SWSS_LOG_NOTICE("Removed %s DNAT NAPT entry with ip %s, port %d, prototype %s and it's translated ip %s, translated port %d", + entry.entry_type.c_str(), key.ip_address.to_string().c_str(), key.l4_port, key.prototype.c_str(), + entry.translated_ip.to_string().c_str(), entry.translated_l4_port); + + deleteNaptCounters(key.prototype.c_str(), key.ip_address, key.l4_port); + + if (entry.entry_type == "static") + { + if (totalStaticNaptEntries) + { + totalStaticNaptEntries--; + updateStaticNaptCounters(totalStaticNaptEntries); + } + } + else + { + if (totalDynamicNaptEntries) + { + totalDynamicNaptEntries--; + updateDynamicNaptCounters(totalDynamicNaptEntries); + } + } + + if (totalDnatEntries) + { + totalDnatEntries--; + updateDnatCounters(totalDnatEntries); + } + + if (totalEntries) + { + totalEntries--; + } + + return true; +} + +// Remove the Twice NAPT entry from the hardware +bool NatOrch::removeHwTwiceNaptEntry(const TwiceNaptEntryKey &key) +{ + sai_nat_entry_t dbl_nat_entry; + sai_status_t status; + uint8_t protoType = ((key.prototype == "TCP") ? IPPROTO_TCP : IPPROTO_UDP); + + SWSS_LOG_ENTER(); + SWSS_LOG_INFO("Delete Twice NAPT entry for proto %s, src-ip %s, src port %d, dst-ip %s, dst port %d", + key.prototype.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, + key.dst_ip.to_string().c_str(), key.dst_l4_port); + + /* Check the entry is present in cache */ + if (m_twiceNaptEntries.find(key) == m_twiceNaptEntries.end()) + { + SWSS_LOG_ERROR("Twice DNAPT entry isn't found for proto %s, src-ip %s, src port %d, dst-ip %s, dst port %d", + key.prototype.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), + key.dst_l4_port); + return false; + } + + if (m_twiceNaptEntries[key].addedToHw == false) + { + SWSS_LOG_INFO("Twice DNAPT entry isn't added to hardware, for proto %s, src-ip %s, src port %d, dst-ip %s, dst port %d", + key.prototype.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), + key.dst_l4_port); + return false; + } + + TwiceNaptEntryValue value = m_twiceNaptEntries[key]; + + m_twiceNaptEntries[key].addedToHw = false; + + memset(&dbl_nat_entry, 0, sizeof(dbl_nat_entry)); + + dbl_nat_entry.vr_id = gVirtualRouterId; + dbl_nat_entry.switch_id = gSwitchId; + dbl_nat_entry.data.key.src_ip = key.src_ip.getV4Addr(); + dbl_nat_entry.data.mask.src_ip = 0xffffffff; + dbl_nat_entry.data.key.l4_src_port = (uint16_t)(key.src_l4_port); + dbl_nat_entry.data.mask.l4_src_port = 0xffff; + dbl_nat_entry.data.key.dst_ip = key.dst_ip.getV4Addr(); + dbl_nat_entry.data.mask.dst_ip = 0xffffffff; + dbl_nat_entry.data.key.l4_dst_port = (uint16_t)(key.dst_l4_port); + dbl_nat_entry.data.mask.l4_dst_port = 0xffff; + dbl_nat_entry.data.key.proto = protoType; + dbl_nat_entry.data.mask.proto = 0xff; + + status = sai_nat_api->remove_nat_entry(&dbl_nat_entry); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_INFO("Failed to remove Twice NAPT entry with prototype %s, src-ip %s, src port %d, dst-ip %s, dst port %d", + key.prototype.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, + key.dst_ip.to_string().c_str(), key.dst_l4_port); + return false; + } + + SWSS_LOG_NOTICE("Removed Twice NAPT entry with prototype %s, src-ip %s, src port %d, dst-ip %s, dst port %d", + key.prototype.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, + key.dst_ip.to_string().c_str(), key.dst_l4_port); + + deleteTwiceNaptCounters(key); + m_twiceNaptEntries.erase(key); + + if (value.entry_type == "static") + { + if (totalStaticTwiceNaptEntries) + { + totalStaticTwiceNaptEntries--; + updateStaticTwiceNaptCounters(totalStaticTwiceNaptEntries); + } + } + else + { + if (totalDynamicTwiceNaptEntries) + { + totalDynamicTwiceNaptEntries--; + updateDynamicTwiceNaptCounters(totalDynamicTwiceNaptEntries); + } + } + + if (totalSnatEntries) + { + totalSnatEntries--; + updateSnatCounters(totalSnatEntries); + } + + if (totalDnatEntries) + { + totalDnatEntries--; + updateDnatCounters(totalDnatEntries); + } + + if (totalEntries >= 2) + { + // Each Twice NAT entry is equivalent to 1 SNAT and 1 DNAT entry together + totalEntries -= 2; + } + + return true; +} + +// Add the SNAT entry to the hardware +bool NatOrch::addHwSnatEntry(const IpAddress &ip_address) +{ + uint32_t attr_count; + sai_nat_entry_t snat_entry; + sai_attribute_t nat_entry_attr[5]; + sai_status_t status; + + SWSS_LOG_ENTER(); + SWSS_LOG_INFO("Create SNAT entry for ip %s", ip_address.to_string().c_str()); + + NatEntryValue entry = m_natEntries[ip_address]; + + memset(nat_entry_attr, 0, sizeof(nat_entry_attr)); + + nat_entry_attr[0].id = SAI_NAT_ENTRY_ATTR_NAT_TYPE; + nat_entry_attr[0].value.u32 = SAI_NAT_TYPE_SOURCE_NAT; + nat_entry_attr[1].id = SAI_NAT_ENTRY_ATTR_SRC_IP; + nat_entry_attr[1].value.u32 = entry.translated_ip.getV4Addr(); + nat_entry_attr[2].id = SAI_NAT_ENTRY_ATTR_SRC_IP_MASK; + nat_entry_attr[2].value.u32 = 0xffffffff; + nat_entry_attr[3].id = SAI_NAT_ENTRY_ATTR_ENABLE_PACKET_COUNT; + nat_entry_attr[3].value.booldata = true; + nat_entry_attr[4].id = SAI_NAT_ENTRY_ATTR_ENABLE_BYTE_COUNT; + nat_entry_attr[4].value.booldata = true; + + attr_count = 5; + + memset(&snat_entry, 0, sizeof(snat_entry)); + + snat_entry.vr_id = gVirtualRouterId; + snat_entry.switch_id = gSwitchId; + snat_entry.data.key.src_ip = ip_address.getV4Addr(); + snat_entry.data.mask.src_ip = 0xffffffff; + + status = sai_nat_api->create_nat_entry(&snat_entry, attr_count, nat_entry_attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create %s SNAT NAT entry with ip %s and it's translated ip %s", + entry.entry_type.c_str(), ip_address.to_string().c_str(), entry.translated_ip.to_string().c_str()); + + return true; + } + + SWSS_LOG_NOTICE("Created %s SNAT NAT entry with ip %s and it's translated ip %s", + entry.entry_type.c_str(), ip_address.to_string().c_str(), entry.translated_ip.to_string().c_str()); + + updateNatCounters(ip_address, 0, 0); + m_natEntries[ip_address].addedToHw = true; + + if (entry.entry_type == "static") + { + totalStaticNatEntries++; + updateStaticNatCounters(totalStaticNatEntries); + } + else + { + totalDynamicNatEntries++; + updateDynamicNatCounters(totalDynamicNatEntries); + } + totalEntries++; + + return true; +} + +// Add the Twice NAT entry to the hardware +bool NatOrch::addHwTwiceNatEntry(const TwiceNatEntryKey &key) +{ + uint32_t attr_count; + sai_nat_entry_t dbl_nat_entry; + sai_attribute_t nat_entry_attr[8]; + + sai_status_t status; + + SWSS_LOG_ENTER(); + SWSS_LOG_INFO("Create Twice NAT entry for src ip %s, dst ip %s", key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str()); + + TwiceNatEntryValue value = m_twiceNatEntries[key]; + + memset(nat_entry_attr, 0, sizeof(nat_entry_attr)); + + nat_entry_attr[0].id = SAI_NAT_ENTRY_ATTR_NAT_TYPE; + nat_entry_attr[0].value.u32 = SAI_NAT_TYPE_DOUBLE_NAT; + nat_entry_attr[1].id = SAI_NAT_ENTRY_ATTR_SRC_IP; + nat_entry_attr[1].value.u32 = value.translated_src_ip.getV4Addr(); + nat_entry_attr[2].id = SAI_NAT_ENTRY_ATTR_SRC_IP_MASK; + nat_entry_attr[2].value.u32 = 0xffffffff; + nat_entry_attr[3].id = SAI_NAT_ENTRY_ATTR_DST_IP; + nat_entry_attr[3].value.u32 = value.translated_dst_ip.getV4Addr(); + nat_entry_attr[4].id = SAI_NAT_ENTRY_ATTR_DST_IP_MASK; + nat_entry_attr[4].value.u32 = 0xffffffff; + nat_entry_attr[5].id = SAI_NAT_ENTRY_ATTR_ENABLE_PACKET_COUNT; + nat_entry_attr[5].value.booldata = true; + nat_entry_attr[6].id = SAI_NAT_ENTRY_ATTR_ENABLE_BYTE_COUNT; + nat_entry_attr[6].value.booldata = true; + + attr_count = 7; + + memset(&dbl_nat_entry, 0, sizeof(dbl_nat_entry)); + + dbl_nat_entry.vr_id = gVirtualRouterId; + dbl_nat_entry.switch_id = gSwitchId; + dbl_nat_entry.data.key.src_ip = key.src_ip.getV4Addr(); + dbl_nat_entry.data.mask.src_ip = 0xffffffff; + dbl_nat_entry.data.key.dst_ip = key.dst_ip.getV4Addr(); + dbl_nat_entry.data.mask.dst_ip = 0xffffffff; + + status = sai_nat_api->create_nat_entry(&dbl_nat_entry, attr_count, nat_entry_attr); + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create %s Twice NAT entry with src ip %s, dst ip %s, translated src ip %s, translated dst ip %s", + value.entry_type.c_str(), key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str(), + value.translated_src_ip.to_string().c_str(), value.translated_dst_ip.to_string().c_str()); + + return true; + } + + SWSS_LOG_NOTICE("Created %s Twice NAT entry with src ip %s, dst ip %s, translated src ip %s, translated dst ip %s", + value.entry_type.c_str(), key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str(), + value.translated_src_ip.to_string().c_str(), value.translated_dst_ip.to_string().c_str()); + + updateTwiceNatCounters(key, 0, 0); + m_twiceNatEntries[key].addedToHw = true; + + totalDnatEntries++; + updateDnatCounters(totalDnatEntries); + totalEntries++; + + totalSnatEntries++; + updateSnatCounters(totalSnatEntries); + totalEntries++; + + if (value.entry_type == "static") + { + totalStaticTwiceNatEntries++; + updateStaticTwiceNatCounters(totalStaticTwiceNatEntries); + } + else + { + totalDynamicTwiceNatEntries++; + updateDynamicTwiceNatCounters(totalDynamicTwiceNatEntries); + } + + return true; +} + +// Add the SNAPT entry to the hardware +bool NatOrch::addHwSnaptEntry(const NaptEntryKey &keyEntry) +{ + uint32_t attr_count; + sai_nat_entry_t snat_entry; + sai_attribute_t nat_entry_attr[6]; + uint8_t ip_protocol = ((keyEntry.prototype == "TCP") ? IPPROTO_TCP : IPPROTO_UDP); + sai_status_t status; + + SWSS_LOG_ENTER(); + SWSS_LOG_INFO("Create SNAPT entry for proto %s, src-ip %s, l4-port %d", + keyEntry.prototype.c_str(), keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port); + + NaptEntryValue entry = m_naptEntries[keyEntry]; + + memset(nat_entry_attr, 0, sizeof(nat_entry_attr)); + + nat_entry_attr[0].id = SAI_NAT_ENTRY_ATTR_NAT_TYPE; + nat_entry_attr[0].value.u32 = SAI_NAT_TYPE_SOURCE_NAT; + nat_entry_attr[1].id = SAI_NAT_ENTRY_ATTR_SRC_IP; + nat_entry_attr[1].value.u32 = entry.translated_ip.getV4Addr(); + nat_entry_attr[2].id = SAI_NAT_ENTRY_ATTR_SRC_IP_MASK; + nat_entry_attr[2].value.u32 = 0xffffffff; + nat_entry_attr[3].id = SAI_NAT_ENTRY_ATTR_L4_SRC_PORT; + nat_entry_attr[3].value.u16 = (uint16_t)(entry.translated_l4_port); + nat_entry_attr[4].id = SAI_NAT_ENTRY_ATTR_ENABLE_PACKET_COUNT; + nat_entry_attr[4].value.booldata = true; + nat_entry_attr[5].id = SAI_NAT_ENTRY_ATTR_ENABLE_BYTE_COUNT; + nat_entry_attr[5].value.booldata = true; + + attr_count = 6; + + memset(&snat_entry, 0, sizeof(snat_entry)); + + snat_entry.vr_id = gVirtualRouterId; + snat_entry.switch_id = gSwitchId; + snat_entry.data.key.src_ip = keyEntry.ip_address.getV4Addr(); + snat_entry.data.key.l4_src_port = (uint16_t)(keyEntry.l4_port); + snat_entry.data.mask.src_ip = 0xffffffff; + snat_entry.data.mask.l4_src_port = 0xffff; + snat_entry.data.key.proto = ip_protocol; + snat_entry.data.mask.proto = 0xff; + + status = sai_nat_api->create_nat_entry(&snat_entry, attr_count, nat_entry_attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create %s SNAT NAPT entry with ip %s, port %d, prototype %s and it's translated ip %s, translated port %d", + entry.entry_type.c_str(), keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port, keyEntry.prototype.c_str(), + entry.translated_ip.to_string().c_str(), entry.translated_l4_port); + + return true; + } + + SWSS_LOG_NOTICE("Created %s SNAT NAPT entry with ip %s, port %d, prototype %s and it's translated ip %s, translated port %d", + entry.entry_type.c_str(), keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port, keyEntry.prototype.c_str(), + entry.translated_ip.to_string().c_str(), entry.translated_l4_port); + + m_naptEntries[keyEntry].addedToHw = true; + updateNaptCounters(keyEntry.prototype.c_str(), keyEntry.ip_address, keyEntry.l4_port, 0, 0); + + if (entry.entry_type == "static") + { + totalStaticNaptEntries++; + updateStaticNaptCounters(totalStaticNaptEntries); + } + else + { + totalDynamicNaptEntries++; + updateDynamicNaptCounters(totalDynamicNaptEntries); + } + totalEntries++; + + return true; +} + +// Add the Twice NAPT entry to the hardware +bool NatOrch::addHwTwiceNaptEntry(const TwiceNaptEntryKey &key) +{ + uint32_t attr_count; + sai_nat_entry_t dbl_nat_entry; + sai_attribute_t nat_entry_attr[10]; + uint8_t protoType = ((key.prototype == "TCP") ? IPPROTO_TCP : IPPROTO_UDP); + sai_status_t status; + + SWSS_LOG_ENTER(); + SWSS_LOG_INFO("Create Twice SNAPT entry for proto %s, src-ip %s, src port %d, dst-ip %s, dst port %d", + key.prototype.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, + key.dst_ip.to_string().c_str(), key.dst_l4_port); + + TwiceNaptEntryValue value = m_twiceNaptEntries[key]; + + memset(nat_entry_attr, 0, sizeof(nat_entry_attr)); + + nat_entry_attr[0].id = SAI_NAT_ENTRY_ATTR_NAT_TYPE; + nat_entry_attr[0].value.u32 = SAI_NAT_TYPE_DOUBLE_NAT; + nat_entry_attr[1].id = SAI_NAT_ENTRY_ATTR_SRC_IP; + nat_entry_attr[1].value.u32 = value.translated_src_ip.getV4Addr(); + nat_entry_attr[2].id = SAI_NAT_ENTRY_ATTR_SRC_IP_MASK; + nat_entry_attr[2].value.u32 = 0xffffffff; + nat_entry_attr[3].id = SAI_NAT_ENTRY_ATTR_L4_SRC_PORT; + nat_entry_attr[3].value.u16 = (uint16_t)(value.translated_src_l4_port); + nat_entry_attr[4].id = SAI_NAT_ENTRY_ATTR_DST_IP; + nat_entry_attr[4].value.u32 = value.translated_dst_ip.getV4Addr(); + nat_entry_attr[5].id = SAI_NAT_ENTRY_ATTR_DST_IP_MASK; + nat_entry_attr[5].value.u32 = 0xffffffff; + nat_entry_attr[6].id = SAI_NAT_ENTRY_ATTR_L4_DST_PORT; + nat_entry_attr[6].value.u16 = (uint16_t)(value.translated_dst_l4_port); + nat_entry_attr[7].id = SAI_NAT_ENTRY_ATTR_ENABLE_PACKET_COUNT; + nat_entry_attr[7].value.booldata = true; + nat_entry_attr[8].id = SAI_NAT_ENTRY_ATTR_ENABLE_BYTE_COUNT; + nat_entry_attr[8].value.booldata = true; + + attr_count = 9; + + memset(&dbl_nat_entry, 0, sizeof(dbl_nat_entry)); + + dbl_nat_entry.vr_id = gVirtualRouterId; + dbl_nat_entry.switch_id = gSwitchId; + dbl_nat_entry.data.key.src_ip = key.src_ip.getV4Addr(); + dbl_nat_entry.data.mask.src_ip = 0xffffffff; + dbl_nat_entry.data.key.l4_src_port = (uint16_t)(key.src_l4_port); + dbl_nat_entry.data.mask.l4_src_port = 0xffff; + dbl_nat_entry.data.key.dst_ip = key.dst_ip.getV4Addr(); + dbl_nat_entry.data.mask.dst_ip = 0xffffffff; + dbl_nat_entry.data.key.l4_dst_port = (uint16_t)(key.dst_l4_port); + dbl_nat_entry.data.mask.l4_dst_port = 0xffff; + dbl_nat_entry.data.key.proto = protoType; + dbl_nat_entry.data.mask.proto = 0xff; + + status = sai_nat_api->create_nat_entry(&dbl_nat_entry, attr_count, nat_entry_attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create %s Twice NAPT entry with src ip %s, src port %d, dst ip %s dst port %d, prototype %s and \ + it's translated src ip %s, translated src port %d, translated dst ip %s, translated dst port %d ", + value.entry_type.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), + key.dst_l4_port, key.prototype.c_str(), value.translated_src_ip.to_string().c_str(), value.translated_src_l4_port, + value.translated_dst_ip.to_string().c_str(), value.translated_dst_l4_port); + + return true; + } + + + SWSS_LOG_NOTICE("Created %s Twice NAPT entry with src ip %s, src port %d, dst ip %s dst port %d, prototype %s and \ + it's translated src ip %s, translated src port %d, translated dst ip %s, translated dst port %d ", + value.entry_type.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), + key.dst_l4_port, key.prototype.c_str(), value.translated_src_ip.to_string().c_str(), value.translated_src_l4_port, + value.translated_dst_ip.to_string().c_str(), value.translated_dst_l4_port); + + updateTwiceNaptCounters(key, 0, 0); + m_twiceNaptEntries[key].addedToHw = true; + + totalDnatEntries++; + updateDnatCounters(totalDnatEntries); + totalEntries++; + + totalSnatEntries++; + updateSnatCounters(totalSnatEntries); + totalEntries++; + + if (value.entry_type == "static") + { + totalStaticTwiceNaptEntries++; + updateStaticTwiceNaptCounters(totalStaticTwiceNaptEntries); + } + else + { + totalDynamicTwiceNaptEntries++; + updateDynamicTwiceNaptCounters(totalDynamicTwiceNaptEntries); + } + + return true; +} + +// Remove the SNAT entry from the hardware +bool NatOrch::removeHwSnatEntry(const IpAddress &ip_address) +{ + sai_nat_entry_t snat_entry; + sai_status_t status; + + SWSS_LOG_ENTER(); + SWSS_LOG_INFO("Deleting SNAT entry ip %s from hardware", ip_address.to_string().c_str()); + + NatEntryValue entry = m_natEntries[ip_address]; + + memset(&snat_entry, 0, sizeof(snat_entry)); + + snat_entry.vr_id = gVirtualRouterId; + snat_entry.switch_id = gSwitchId; + snat_entry.data.key.src_ip = ip_address.getV4Addr(); + snat_entry.data.mask.src_ip = 0xffffffff; + + status = sai_nat_api->remove_nat_entry(&snat_entry); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_INFO("Failed to removed %s SNAT NAT entry with ip %s and it's translated ip %s", + entry.entry_type.c_str(), ip_address.to_string().c_str(), entry.translated_ip.to_string().c_str()); + } + else + { + SWSS_LOG_NOTICE("Removed %s SNAT NAT entry with ip %s and it's translated ip %s", + entry.entry_type.c_str(), ip_address.to_string().c_str(), entry.translated_ip.to_string().c_str()); + } + deleteNatCounters(ip_address); + m_natEntries.erase(ip_address); + + if (entry.entry_type == "static") + { + if (totalStaticNatEntries) + { + totalStaticNatEntries--; + updateStaticNatCounters(totalStaticNatEntries); + } + } + else + { + if (totalDynamicNatEntries) + { + totalDynamicNatEntries--; + updateDynamicNatCounters(totalDynamicNatEntries); + } + else + { + SWSS_LOG_ERROR("Found the total number dynamic nat entries to be corrupt, when removing SNAT entry with ip %s, translated ip %s!!", + ip_address.to_string().c_str(), entry.translated_ip.to_string().c_str()); + } + } + + if (totalSnatEntries) + { + totalSnatEntries--; + updateSnatCounters(totalSnatEntries); + } + else + { + SWSS_LOG_ERROR("Found the total number dynamic snat entries to be corrupt, when removing SNAT entry with ip %s, translated ip %s!!", + ip_address.to_string().c_str(), entry.translated_ip.to_string().c_str()); + } + + if (totalEntries) + { + totalEntries--; + } + + return true; +} + +// Remove the SNAPT entry from the hardware +bool NatOrch::removeHwSnaptEntry(const NaptEntryKey &keyEntry) +{ + sai_nat_entry_t snat_entry; + sai_status_t status; + uint8_t ip_protocol = ((keyEntry.prototype == "TCP") ? IPPROTO_TCP : IPPROTO_UDP); + + SWSS_LOG_ENTER(); + SWSS_LOG_INFO("Delete SNAPT entry for proto %s, src-ip %s, l4-port %d", + keyEntry.prototype.c_str(), keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port); + + /* Check the entry is present in cache */ + if (m_naptEntries.find(keyEntry) == m_naptEntries.end()) + { + SWSS_LOG_ERROR("SNAPT entry isn't found for ip %s, l4-port %d", keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port); + + return false; + } + + NaptEntryValue entry = m_naptEntries[keyEntry]; + + memset(&snat_entry, 0, sizeof(snat_entry)); + + snat_entry.vr_id = gVirtualRouterId; + snat_entry.switch_id = gSwitchId; + snat_entry.data.key.src_ip = keyEntry.ip_address.getV4Addr(); + snat_entry.data.key.l4_src_port = (uint16_t)(keyEntry.l4_port); + snat_entry.data.mask.src_ip = 0xffffffff; + snat_entry.data.mask.l4_src_port = 0xffff; + snat_entry.data.key.proto = ip_protocol; + snat_entry.data.mask.proto = 0xff; + + status = sai_nat_api->remove_nat_entry(&snat_entry); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_INFO("Failed to removed %s SNAT NAPT entry with ip %s, port %d, prototype %s and it's translated ip %s, translated port %d", + entry.entry_type.c_str(), keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port, keyEntry.prototype.c_str(), + entry.translated_ip.to_string().c_str(), entry.translated_l4_port); + } + else + { + SWSS_LOG_NOTICE("Removed %s SNAT NAPT entry with ip %s, port %d, prototype %s and it's translated ip %s, translated port %d", + entry.entry_type.c_str(), keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port, keyEntry.prototype.c_str(), + entry.translated_ip.to_string().c_str(), entry.translated_l4_port); + } + deleteNaptCounters(keyEntry.prototype.c_str(), keyEntry.ip_address, keyEntry.l4_port); + m_naptEntries.erase(keyEntry); + + if (entry.entry_type == "static") + { + if (totalStaticNaptEntries) + { + totalStaticNaptEntries--; + updateStaticNaptCounters(totalStaticNaptEntries); + } + } + else + { + if (totalDynamicNaptEntries) + { + totalDynamicNaptEntries--; + updateDynamicNaptCounters(totalDynamicNaptEntries); + } + else + { + SWSS_LOG_ERROR("Found the total number dynamic napt entries to be corrupt, when removing SNAPT entry with proto %s, ip %s, port %d, translated ip %s, translated port %d!!", + keyEntry.prototype.c_str(), keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port, + entry.translated_ip.to_string().c_str(), entry.translated_l4_port); + } + } + + if (totalSnatEntries) + { + totalSnatEntries--; + updateSnatCounters(totalSnatEntries); + } + else + { + SWSS_LOG_ERROR("Found the total number dynamic snat entries to be corrupt, when removing SNAPT entry with proto %s, ip %s, port %d, translated ip %s, translated port %d!!", + keyEntry.prototype.c_str(), keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port, + entry.translated_ip.to_string().c_str(), entry.translated_l4_port); + } + + if (totalEntries) + totalEntries--; + + return true; +} + +bool NatOrch::addNatEntry(const IpAddress &ip_address, const NatEntryValue &entry) +{ + SWSS_LOG_ENTER(); + + /* Check the entry is present in cache */ + if (m_natEntries.find(ip_address) != m_natEntries.end()) + { + SWSS_LOG_INFO("Duplicate %s %s NAT entry with ip %s and it's translated ip %s, do nothing", + entry.entry_type.c_str(), entry.nat_type.c_str(), ip_address.to_string().c_str(), + entry.translated_ip.to_string().c_str()); + return true; + } + + if ((entry.nat_type == "snat") and + (entry.entry_type == "dynamic")) + { + if (totalSnatEntries == maxAllowedSNatEntries) + { + SWSS_LOG_INFO("Reached the max allowed NAT entries in the hardware, dropping new SNAT translation with ip %s and translated ip %s", + ip_address.to_string().c_str(), entry.translated_ip.to_string().c_str()); + deleteConnTrackEntry(ip_address); + return true; + } + + m_natEntries[ip_address] = entry; + m_natEntries[ip_address].addedToHw = false; + + updateConnTrackTimeout(ip_address); + } + else + { + m_natEntries[ip_address] = entry; + m_natEntries[ip_address].addedToHw = false; + } + + if (entry.nat_type == "snat") + { + totalSnatEntries++; + updateSnatCounters(totalSnatEntries); + } + + if (!isNatEnabled()) + { + SWSS_LOG_WARN("NAT Feature is not yet enabled, skipped adding %s %s NAT entry with ip %s and it's translated ip %s", + entry.entry_type.c_str(), entry.nat_type.c_str(), ip_address.to_string().c_str(), + entry.translated_ip.to_string().c_str()); + + return true; + } + + if (entry.nat_type == "snat") + { + /* Add SNAT entry to the hardware */ + addHwSnatEntry(ip_address); + } + else if (entry.nat_type == "dnat") + { + if (gNhTrackingSupported == true) + { + /* Cache the DNAT entry in the nexthop resolution cache */ + addDnatToNhCache(entry.translated_ip, ip_address); + } + else + { + /* Add DNAT entry to the hardware */ + addHwDnatEntry(ip_address); + } + } + + return true; +} + +bool NatOrch::removeNatEntry(const IpAddress &ip_address) +{ + SWSS_LOG_ENTER(); + + /* Check the entry is present in cache */ + if (m_natEntries.find(ip_address) == m_natEntries.end()) + { + SWSS_LOG_INFO("NAT entry isn't found for ip %s", ip_address.to_string().c_str()); + + return true; + } + + NatEntryValue entry = m_natEntries[ip_address]; + + if (entry.nat_type == "snat") + { + /* Remove SNAT entry from the hardware */ + removeHwSnatEntry(ip_address); + } + else if (entry.nat_type == "dnat") + { + if (gNhTrackingSupported == true) + { + /* Cache the DNAT entry in the nexthop resolution cache */ + removeDnatFromNhCache(entry.translated_ip, ip_address); + } + else + { + removeHwDnatEntry(ip_address); + m_natEntries.erase(ip_address); + } + } + else + { + SWSS_LOG_ERROR("Invalid NAT %s type for removing the %s NAT entry with ip %s and it's translated ip %s", + entry.nat_type.c_str(), entry.entry_type.c_str(), ip_address.to_string().c_str(), entry.translated_ip.to_string().c_str()); + + return false; + } + + return true; +} + +bool NatOrch::addTwiceNatEntry(const TwiceNatEntryKey &key, const TwiceNatEntryValue &value) +{ + SWSS_LOG_ENTER(); + + /* Check the entry is present in cache */ + if (m_twiceNatEntries.find(key) != m_twiceNatEntries.end()) + { + SWSS_LOG_INFO("Duplicate %s Twice NAT entry with src ip %s, dst ip %s and it's translated src ip %s, dst ip %s, do nothing", + value.entry_type.c_str(), key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str(), + value.translated_src_ip.to_string().c_str(), value.translated_dst_ip.to_string().c_str()); + return true; + } + + if (value.entry_type == "dynamic") + { + if (totalSnatEntries == maxAllowedSNatEntries) + { + SWSS_LOG_INFO("Reached the max allowed NAT entries in the hardware, dropping new Twice NAT translation with src ip %s, dst ip %s and translated src ip %s, dst ip %s", + key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str(), value.translated_src_ip.to_string().c_str(), value.translated_dst_ip.to_string().c_str()); + deleteConnTrackEntry(key); + return true; + } + } + m_twiceNatEntries[key] = value; + m_twiceNatEntries[key].addedToHw = false; + + updateConnTrackTimeout(key); + + if (!isNatEnabled()) + { + SWSS_LOG_WARN("NAT Feature is not yet enabled, skipped adding %s Twice NAT entry with src ip %s, dst ip %s and it's translated src ip %s, dst ip %s", + value.entry_type.c_str(), key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str(), + value.translated_src_ip.to_string().c_str(), value.translated_dst_ip.to_string().c_str()); + return true; + } + + if (gNhTrackingSupported == true) + { + /* Cache the Twice NAT entry in the nexthop resolution cache */ + addTwiceNatToNhCache(value.translated_dst_ip, key); + } + else + { + /* Add Twice NAT entry to the hardware */ + addHwTwiceNatEntry(key); + } + + return true; +} + +bool NatOrch::removeTwiceNatEntry(const TwiceNatEntryKey &key) +{ + SWSS_LOG_ENTER(); + + /* Check the entry is present in cache */ + if (m_twiceNatEntries.find(key) == m_twiceNatEntries.end()) + { + SWSS_LOG_INFO("Twice NAT entry isn't found for src ip %s, dst ip %s", key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str()); + + return true; + } + + TwiceNatEntryValue value = m_twiceNatEntries[key]; + + if (gNhTrackingSupported == true) + { + removeTwiceNatFromNhCache(value.translated_dst_ip, key); + } + else + { + removeHwTwiceNatEntry(key); + m_twiceNatEntries.erase(key); + } + + return true; +} + +bool NatOrch::addNaptEntry(const NaptEntryKey &keyEntry, const NaptEntryValue &entry) +{ + SWSS_LOG_ENTER(); + + /* Check the entry is present in cache */ + if (m_naptEntries.find(keyEntry) != m_naptEntries.end()) + { + NaptEntryValue oldEntry = m_naptEntries[keyEntry]; + + /* Entry is exist*/ + if ((entry.translated_l4_port != oldEntry.translated_l4_port) or + (entry.translated_ip.to_string() != oldEntry.translated_ip.to_string()) or + (entry.nat_type != oldEntry.nat_type)) + { + SWSS_LOG_INFO("%s %s NAPT entry already exists with ip %s, port %d, prototype %s and it's translated ip %s, translated port %d", + oldEntry.entry_type.c_str(), oldEntry.nat_type.c_str(), keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port, + keyEntry.prototype.c_str(), oldEntry.translated_ip.to_string().c_str(), oldEntry.translated_l4_port); + + /* Removed the NAPT entry */ + SWSS_LOG_INFO("Removing %s %s NAPT entry with ip %s, port %d, prototype %s and it's translated ip %s, translated port %d", + oldEntry.entry_type.c_str(), oldEntry.nat_type.c_str(), keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port, + keyEntry.prototype.c_str(), oldEntry.translated_ip.to_string().c_str(), oldEntry.translated_l4_port); + + removeNaptEntry(keyEntry); + } + else if (entry.entry_type != oldEntry.entry_type) + { + SWSS_LOG_INFO("%s %s NAPT entry already exists with ip %s, port %d, prototype %s and it's translated ip %s, translated port %d, is moved to %s NAPT", + oldEntry.entry_type.c_str(), entry.nat_type.c_str(), keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port, + keyEntry.prototype.c_str(), entry.translated_ip.to_string().c_str(), entry.translated_l4_port, entry.entry_type.c_str()); + + m_naptEntries[keyEntry].entry_type = entry.entry_type; + if (entry.entry_type == "static") + { + totalStaticNaptEntries++; + totalDynamicNaptEntries--; + updateStaticNaptCounters(totalStaticNaptEntries); + updateDynamicNaptCounters(totalDynamicNaptEntries); + } + return true; + } + else + { + SWSS_LOG_INFO("Duplicate %s %s NAPT entry already exists with ip %s, port %d, prototype %s and it's translated ip %s, translated port %d, do nothing", + entry.entry_type.c_str(), entry.nat_type.c_str(), keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port, + keyEntry.prototype.c_str(), entry.translated_ip.to_string().c_str(), entry.translated_l4_port); + return true; + } + } + + if ((entry.nat_type == "snat") and + (entry.entry_type == "dynamic")) + { + if (totalSnatEntries == maxAllowedSNatEntries) + { + SWSS_LOG_INFO("Reached the max allowed NAT entries in the hardware, dropping new SNAPT translation with ip %s, port %d, prototype %s, translated ip %s, translated port %d", + keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port, + keyEntry.prototype.c_str(), entry.translated_ip.to_string().c_str(), entry.translated_l4_port); + deleteConnTrackEntry(keyEntry); + return true; + } + + m_naptEntries[keyEntry] = entry; + m_naptEntries[keyEntry].addedToHw = false; + + updateConnTrackTimeout(keyEntry); + } + else + { + m_naptEntries[keyEntry] = entry; + m_naptEntries[keyEntry].addedToHw = false; + } + + if (entry.nat_type == "snat") + { + totalSnatEntries++; + updateSnatCounters(totalSnatEntries); + } + + if (!isNatEnabled()) + { + SWSS_LOG_WARN("NAT feature is not yet enabled, skipped adding %s %s NAPT entry with ip %s, port %d, prototype %s and it's translated ip %s, translated port %d", + entry.entry_type.c_str(), entry.nat_type.c_str(), keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port, keyEntry.prototype.c_str(), + entry.translated_ip.to_string().c_str(), entry.translated_l4_port); + + return true; + } + + if (entry.nat_type == "snat") + { + /* Add SNAPT entry to the hardware */ + addHwSnaptEntry(keyEntry); + } + else if (entry.nat_type == "dnat") + { + if (gNhTrackingSupported == true) + { + /* Cache the DNAPT entry in the nexthop resolution cache */ + addDnaptToNhCache(entry.translated_ip, keyEntry); + } + else + { + /* Add DNAPT entry in the hardware */ + addHwDnaptEntry(keyEntry); + } + } + else + { + SWSS_LOG_ERROR("Invalid NAT %s type for adding the %s NAPT entry with ip %s, port %d, prototype %s and it's translated ip %s, translated port %d", + entry.nat_type.c_str(), entry.entry_type.c_str(), keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port, keyEntry.prototype.c_str(), + entry.translated_ip.to_string().c_str(), entry.translated_l4_port); + + return false; + } + + return true; +} + +bool NatOrch::removeNaptEntry(const NaptEntryKey &keyEntry) +{ + SWSS_LOG_ENTER(); + + if (m_naptEntries.find(keyEntry) == m_naptEntries.end()) + { + SWSS_LOG_INFO("NAPT entry isn't found for prototype - %s, ip - %s and port - %d", + keyEntry.prototype.c_str(), keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port); + + return true; + } + + NaptEntryValue entry = m_naptEntries[keyEntry]; + + if (entry.nat_type == "snat") + { + /* Remove SNAPT entry from the hardware */ + removeHwSnaptEntry(keyEntry); + } + else if (entry.nat_type == "dnat") + { + if (gNhTrackingSupported == true) + { + removeDnaptFromNhCache(entry.translated_ip, keyEntry); + } + else + { + removeHwDnaptEntry(keyEntry); + m_naptEntries.erase(keyEntry); + } + } + else + { + SWSS_LOG_ERROR("Invalid NAT %s type for removing the %s NAPT entry with ip %s, port %d, prototype %s and it's translated ip %s, translated port %d", + entry.nat_type.c_str(), entry.entry_type.c_str(), keyEntry.ip_address.to_string().c_str(), keyEntry.l4_port, keyEntry.prototype.c_str(), + entry.translated_ip.to_string().c_str(), entry.translated_l4_port); + + return false; + } + + return true; +} + +bool NatOrch::addTwiceNaptEntry(const TwiceNaptEntryKey &key, const TwiceNaptEntryValue &value) +{ + SWSS_LOG_ENTER(); + + /* Check the entry is present in the cache */ + if (m_twiceNaptEntries.find(key) != m_twiceNaptEntries.end()) + { + TwiceNaptEntryValue oldEntry = m_twiceNaptEntries[key]; + + /* Entry exists */ + if ((value.translated_src_l4_port != oldEntry.translated_src_l4_port) or + (value.translated_dst_l4_port != oldEntry.translated_dst_l4_port) or + (value.translated_src_ip.to_string() != oldEntry.translated_src_ip.to_string()) or + (value.translated_dst_ip.to_string() != oldEntry.translated_dst_ip.to_string())) + { + SWSS_LOG_INFO("Twice NAPT entry already exists with src ip %s, src port %d, dst ip %s, dst port %d, prototype %s and it's translated src ip %s, \ + translated src port %d, translated dst ip %s, translated dst port %d", + key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), key.dst_l4_port, + key.prototype.c_str(), oldEntry.translated_src_ip.to_string().c_str(), oldEntry.translated_src_l4_port, + oldEntry.translated_dst_ip.to_string().c_str(), oldEntry.translated_dst_l4_port); + + /* Removed the NAPT entry */ + SWSS_LOG_INFO("Removing %s Twice NAPT entry with src ip %s, src port %d, dst ip %s, dst port %d, prototype %s", + oldEntry.entry_type.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, + key.dst_ip.to_string().c_str(), key.dst_l4_port, key.prototype.c_str()); + + removeTwiceNaptEntry(key); + } + else if (value.entry_type != oldEntry.entry_type) + { + SWSS_LOG_INFO("Entry type change, %s Twice NAPT entry already exists with src ip %s, src port %d, dst ip %s, dst port %d, prototype %s and it's translated src ip %s, \ + translated src port %d, translated dst ip %s, translated dst port %d", + oldEntry.entry_type.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), key.dst_l4_port, + key.prototype.c_str(), oldEntry.translated_src_ip.to_string().c_str(), oldEntry.translated_src_l4_port, + oldEntry.translated_dst_ip.to_string().c_str(), oldEntry.translated_dst_l4_port); + + m_twiceNaptEntries[key].entry_type = value.entry_type; + + if (value.entry_type == "static") + { + totalStaticTwiceNaptEntries++; + totalDynamicTwiceNaptEntries--; + updateStaticTwiceNaptCounters(totalStaticTwiceNaptEntries); + updateDynamicTwiceNaptCounters(totalDynamicTwiceNaptEntries); + } + return true; + } + else + { + SWSS_LOG_INFO("Duplicate %s Twice NAPT entry already exists with src ip %s, src port %d, dst ip %s, dst port %d, prototype %s and \ + it's translated src ip %s, translated src port %d, translated dst ip %s, translated dst port %d, do nothing", + value.entry_type.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), + key.dst_l4_port, key.prototype.c_str(), value.translated_src_ip.to_string().c_str(), + value.translated_src_l4_port, value.translated_dst_ip.to_string().c_str(), value.translated_dst_l4_port); + return true; + } + } + + if (value.entry_type == "dynamic") + { + if (totalSnatEntries == maxAllowedSNatEntries) + { + SWSS_LOG_INFO("Reached the max allowed NAT entries in the hardware, dropping new Twice SNAPT translation with src ip %s, src port %d, prototype %s, \ + dst ip %s, dst port %d", + key.src_ip.to_string().c_str(), key.src_l4_port, key.prototype.c_str(), key.dst_ip.to_string().c_str(), key.dst_l4_port); + deleteConnTrackEntry(key); + return true; + } + } + m_twiceNaptEntries[key] = value; + m_twiceNaptEntries[key].addedToHw = false; + + updateConnTrackTimeout(key); + + if (!isNatEnabled()) + { + SWSS_LOG_WARN("NAT feature is not yet enabled, skipped adding %s Twice NAPT entry with src ip %s, src port %d, prototype %s, dst ip %s, dst port %d", + value.entry_type.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, key.prototype.c_str(), + key.dst_ip.to_string().c_str(), key.dst_l4_port); + + return true; + } + + if (gNhTrackingSupported == true) + { + /* Add Twice NAPT entry to the NH resolv cache */ + addTwiceNaptToNhCache(value.translated_dst_ip, key); + } + else + { + /* Add Twice NAPT entry to the hardware */ + addHwTwiceNaptEntry(key); + } + + return true; +} + +bool NatOrch::removeTwiceNaptEntry(const TwiceNaptEntryKey &key) +{ + SWSS_LOG_ENTER(); + + if (m_twiceNaptEntries.find(key) == m_twiceNaptEntries.end()) + { + SWSS_LOG_INFO("Twice NAPT entry isn't found for prototype - %s, src ip %s src port %d dst ip %s dst port %d", + key.prototype.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, + key.dst_ip.to_string().c_str(), key.dst_l4_port); + return true; + } + + TwiceNaptEntryValue value = m_twiceNaptEntries[key]; + + if (gNhTrackingSupported == true) + { + /* Remove Twice NAPT entry from the NH resolv cache */ + removeTwiceNaptFromNhCache(value.translated_dst_ip, key); + } + else + { + removeHwTwiceNaptEntry(key); + m_twiceNaptEntries.erase(key); + } + + return true; +} + +bool NatOrch::isNatEnabled(void) +{ + if (admin_mode == "enabled") + { + return true; + } + + return false; +} + +void NatOrch::flushAllNatEntries(void) +{ + std::string res; + const std::string cmds = std::string("") + CONNTRACK + FLUSH; + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Cleared the All NAT Entries"); + } +} + +void NatOrch::clearAllDnatEntries(void) +{ + IpAddress dstIp; + NaptEntryKey keyEntry; + NatEntryValue natEntry; + NaptEntryValue naptEntry; + TwiceNatEntryKey twiceNatKey; + TwiceNatEntryValue twiceNatValue; + TwiceNaptEntryKey twiceNaptKey; + TwiceNaptEntryValue twiceNaptValue; + + NatEntry::iterator natIter = m_natEntries.begin(); + while (natIter != m_natEntries.end()) + { + natEntry = (*natIter).second; + dstIp = (*natIter).first; + natIter++; + + if (natEntry.addedToHw == true) + { + if (natEntry.nat_type == "dnat") + { + if (gNhTrackingSupported == true) + { + removeDnatFromNhCache(natEntry.translated_ip, dstIp); + } + else + { + removeHwDnatEntry(dstIp); + m_natEntries.erase(dstIp); + } + } + } + } + + NaptEntry::iterator naptIter = m_naptEntries.begin(); + while (naptIter != m_naptEntries.end()) + { + naptEntry = (*naptIter).second; + keyEntry = (*naptIter).first; + naptIter++; + + if (naptEntry.addedToHw == true) + { + if (naptEntry.nat_type == "dnat") + { + if (gNhTrackingSupported == true) + { + removeDnaptFromNhCache(naptEntry.translated_ip, keyEntry); + } + else + { + removeHwDnaptEntry(keyEntry); + m_naptEntries.erase(keyEntry); + } + } + } + } + + TwiceNatEntry::iterator twiceNatIter = m_twiceNatEntries.begin(); + while (twiceNatIter != m_twiceNatEntries.end()) + { + twiceNatValue = (*twiceNatIter).second; + twiceNatKey = (*twiceNatIter).first; + twiceNatIter++; + + if (twiceNatValue.addedToHw == true) + { + if (gNhTrackingSupported == true) + { + removeTwiceNatFromNhCache(twiceNatValue.translated_dst_ip, twiceNatKey); + } + else + { + removeHwTwiceNatEntry(twiceNatKey); + m_twiceNatEntries.erase(twiceNatKey); + } + } + } + + TwiceNaptEntry::iterator twiceNaptIter = m_twiceNaptEntries.begin(); + while (twiceNaptIter != m_twiceNaptEntries.end()) + { + twiceNaptValue = (*twiceNaptIter).second; + twiceNaptKey = (*twiceNaptIter).first; + twiceNaptIter++; + + if (twiceNaptValue.addedToHw == true) + { + if (gNhTrackingSupported == true) + { + removeTwiceNaptFromNhCache(twiceNaptValue.translated_dst_ip, twiceNaptKey); + } + else + { + removeHwTwiceNaptEntry(twiceNaptKey); + m_twiceNaptEntries.erase(twiceNaptKey); + } + } + } +} + +void NatOrch::cleanupAppDbEntries(void) +{ + /* Iterate over all the entries clean them up from the APP_DB + * and from the hardware */ + string appDbKey; + IpAddress ip; + NaptEntryKey keyEntry; + TwiceNatEntryKey twiceNatKey; + TwiceNaptEntryKey twiceNaptKey; + + NatEntry::iterator natIter = m_natEntries.begin(); + while (natIter != m_natEntries.end()) + { + ip = (*natIter).first; + natIter++; + + /* Remove from APP_DB */ + appDbKey = ip.to_string(); + m_natQueryTable.del(appDbKey); + + /* Remove from ASIC */ + removeNatEntry(ip); + + SWSS_LOG_INFO("Removed NAT entry from APP_DB and ASIC - %s", appDbKey.c_str()); + } + + NaptEntry::iterator naptIter = m_naptEntries.begin(); + while (naptIter != m_naptEntries.end()) + { + keyEntry = (*naptIter).first; + naptIter++; + + /* Remove from APP_DB */ + appDbKey = keyEntry.prototype + ":" + keyEntry.ip_address.to_string() + ":" + std::to_string(keyEntry.l4_port); + m_naptQueryTable.del(appDbKey); + + /* Remove from ASIC */ + removeNaptEntry(keyEntry); + + SWSS_LOG_INFO("Removed NAPT entry from APP_DB and ASIC - %s", appDbKey.c_str()); + } + + TwiceNatEntry::iterator twiceNatIter = m_twiceNatEntries.begin(); + while (twiceNatIter != m_twiceNatEntries.end()) + { + twiceNatKey = (*twiceNatIter).first; + twiceNatIter++; + + /* Remove from APP_DB */ + appDbKey = twiceNatKey.src_ip.to_string() + ":" + twiceNatKey.dst_ip.to_string(); + m_twiceNatQueryTable.del(appDbKey); + + /* Remove from ASIC */ + removeTwiceNatEntry(twiceNatKey); + + SWSS_LOG_INFO("Removed Twice NAT entry from APP_DB and ASIC - %s", appDbKey.c_str()); + } + + TwiceNaptEntry::iterator twiceNaptIter = m_twiceNaptEntries.begin(); + while (twiceNaptIter != m_twiceNaptEntries.end()) + { + twiceNaptKey = (*twiceNaptIter).first; + twiceNaptIter++; + + /* Remove from APP_DB */ + appDbKey = twiceNaptKey.prototype + ":" + twiceNaptKey.src_ip.to_string() + ":" + + std::to_string(twiceNaptKey.src_l4_port) + ":" + twiceNaptKey.dst_ip.to_string() + + ":" + std::to_string(twiceNaptKey.dst_l4_port); + m_twiceNaptQueryTable.del(appDbKey); + + /* Remove from ASIC */ + removeTwiceNaptEntry(twiceNaptKey); + + SWSS_LOG_INFO("Removed Twice NAPT entry from APP_DB and ASIC - %s", appDbKey.c_str()); + } +} + +bool NatOrch::warmBootingInProgress(void) +{ + std::string value; + + m_stateWarmRestartEnableTable.hget("system", "enable", value); + if (value == "true") + { + SWSS_LOG_INFO("Warm reboot enabled"); + m_stateWarmRestartTable.hget("natsyncd", "state", value); + if (value != "reconciled") + { + SWSS_LOG_NOTICE("Nat conntrack state reconciliation not completed yet"); + return true; + } + return false; + } + else + { + SWSS_LOG_INFO("Warm reboot not enabled"); + } + return false; +} + +void NatOrch::enableNatFeature(void) +{ + sai_status_t status; + sai_attribute_t attr; + + SWSS_LOG_INFO("Verify NAT is supported or not"); + + if (gIsNatSupported == false) + { + SWSS_LOG_NOTICE("NAT Feature is not supported in this Platform"); + return; + } + else + { + admin_mode = "enabled"; + SWSS_LOG_INFO("NAT Feature is supported with available limit : %d", maxAllowedSNatEntries); + } + + SWSS_LOG_INFO("Enabling NAT "); + + memset(&attr, 0, sizeof(attr)); + attr.id = SAI_SWITCH_ATTR_NAT_ENABLE; + attr.value.booldata = true; + + status = sai_switch_api->set_switch_attribute(gSwitchId, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to enable NAT: %d", status); + } + + SWSS_LOG_INFO("NAT Query timer start "); + m_natQueryTimer->start(); + + if (gNhTrackingSupported == true) + { + SWSS_LOG_INFO("Attach to Neighbor Orch "); + m_neighOrch->attach(this); + } + if (! warmBootingInProgress()) + { + SWSS_LOG_NOTICE("Not warm rebooting, so clearing all conntrack Entries on nat feature enable"); + flushAllNatEntries(); + } + + SWSS_LOG_INFO("Adding NAT Entries "); + addAllNatEntries(); + + if (! warmBootingInProgress()) + { + SWSS_LOG_NOTICE("Not warm rebooting, so adding static conntrack Entries"); + addAllStaticConntrackEntries(); + } + +} + +void NatOrch::disableNatFeature(void) +{ + sai_status_t status; + sai_attribute_t attr; + + SWSS_LOG_INFO("Disabling NAT "); + + memset(&attr, 0, sizeof(attr)); + + admin_mode = "disabled"; + attr.id = SAI_SWITCH_ATTR_NAT_ENABLE; + attr.value.booldata = false; + + status = sai_switch_api->set_switch_attribute(gSwitchId, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to disable NAT: %d", status); + } + + SWSS_LOG_INFO("NAT Query timer stop "); + m_natQueryTimer->stop(); + + if (gNhTrackingSupported == true) + { + SWSS_LOG_INFO("Detach to Neighbor Orch "); + m_neighOrch->detach(this); + } + + SWSS_LOG_INFO("Clear all dynamic NAT Entries "); + flushAllNatEntries(); + + SWSS_LOG_INFO("Clear all DNAT Entries "); + clearAllDnatEntries(); +} + +void NatOrch::doNatTableTask(Consumer& consumer) +{ + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + string key = kfvKey(t); + string op = kfvOp(t); + vector keys = tokenize(key, ':'); + IpAddress global_address; + /* Example : APPL_DB + * NAT_TABLE:65.55.45.1 + * translated_ip: 10.0.0.1 + * nat_type: dnat + * entry_type: static + */ + + /* Ensure the key size is 1 otherwise ignore */ + if (keys.size() != 1) + { + SWSS_LOG_ERROR("Invalid key size, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + IpAddress ip_address = IpAddress(key); + + if (op == SET_COMMAND) + { + NatEntryValue entry; + string type; + + for (auto i : kfvFieldsValues(t)) + { + if (fvField(i) == "entry_type") + type = fvValue(i); + else if (fvField(i) == "translated_ip") + entry.translated_ip = IpAddress(fvValue(i)); + else if (fvField(i) == "nat_type") + entry.nat_type = fvValue(i); + } + + /* NAT type is either dynamic or static */ + assert(type == "dynamic" || type == "static"); + entry.entry_type = type; + entry.addedToHw = false; + + if (addNatEntry(ip_address, entry)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else if (op == DEL_COMMAND) + { + if (removeNatEntry(ip_address)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s\n", op.c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +void NatOrch::doNaptTableTask(Consumer& consumer) +{ + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + string key = kfvKey(t); + string op = kfvOp(t); + vector keys = tokenize(key, ':'); + + /* Example : APPL_DB + * NAPT_TABLE:TCP:65.55.42.1:1024 + * translated_ip: 10.0.0.1 + * translated_l4_port: 6000 + * nat_type: snat + * entry_type: static + */ + + /* Ensure the key size is 5 otherwise ignore */ + if (keys.size() != 3) + { + SWSS_LOG_ERROR("Invalid key size, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + NaptEntryKey keyEntry; + + keyEntry.ip_address = IpAddress(keys[1]); + keyEntry.l4_port = stoi(keys[2]); + keyEntry.prototype = keys[0]; + + if (op == SET_COMMAND) + { + NaptEntryValue entry; + string type; + + for (auto i : kfvFieldsValues(t)) + { + if (fvField(i) == "entry_type") + type = fvValue(i); + else if (fvField(i) == "translated_ip") + entry.translated_ip = IpAddress(fvValue(i)); + else if (fvField(i) == "translated_l4_port") + entry.translated_l4_port = stoi(fvValue(i)); + else if (fvField(i) == "nat_type") + entry.nat_type = fvValue(i); + } + + /* NAT type is either dynamic or static */ + assert(type == "dynamic" || type == "static"); + entry.entry_type = type; + entry.addedToHw = false; + + if (addNaptEntry(keyEntry, entry)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else if (op == DEL_COMMAND) + { + if (removeNaptEntry(keyEntry)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s\n", op.c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +void NatOrch::doTwiceNatTableTask(Consumer& consumer) +{ + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + string key = kfvKey(t); + string op = kfvOp(t); + vector keys = tokenize(key, ':'); + IpAddress global_address; + /* Example : APPL_DB + * NAT_TWICE_TABLE:91.91.91.91:65.55.45.1 + * translated_src_ip: 14.14.14.14 + * translated_dst_ip: 12.12.12.12 + * entry_type: static + */ + + /* Ensure the key size is 2 otherwise ignore */ + if (keys.size() != 2) + { + SWSS_LOG_ERROR("Invalid key size, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + TwiceNatEntryKey keyEntry; + keyEntry.src_ip = IpAddress(keys[0]); + keyEntry.dst_ip = IpAddress(keys[1]); + + if (op == SET_COMMAND) + { + TwiceNatEntryValue entry; + string type; + + for (auto i : kfvFieldsValues(t)) + { + if (fvField(i) == "entry_type") + type = fvValue(i); + else if (fvField(i) == "translated_src_ip") + entry.translated_src_ip = IpAddress(fvValue(i)); + else if (fvField(i) == "translated_dst_ip") + entry.translated_dst_ip = IpAddress(fvValue(i)); + } + + /* NAT type is either dynamic or static */ + assert(type == "dynamic" || type == "static"); + entry.entry_type = type; + entry.addedToHw = false; + + if (addTwiceNatEntry(keyEntry, entry)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else if (op == DEL_COMMAND) + { + if (removeTwiceNatEntry(keyEntry)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s\n", op.c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +void NatOrch::doTwiceNaptTableTask(Consumer& consumer) +{ + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + string key = kfvKey(t); + string op = kfvOp(t); + vector keys = tokenize(key, ':'); + + /* Example : APPL_DB + * NAPT_TWICE_TABLE:TCP:91.91.91.91:6363:165.55.42.1:1024 + * translated_src_ip: 14.14.14.14 + * translated_src_l4_port: 6000 + * translated_dst_ip: 12.12.12.12 + * translated_dst_l4_port: 8000 + * entry_type: static + */ + + /* Ensure the key size is 5 otherwise ignore */ + if (keys.size() != 5) + { + SWSS_LOG_ERROR("Invalid key size, skipping %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + TwiceNaptEntryKey keyEntry; + + keyEntry.src_ip = IpAddress(keys[1]); + keyEntry.src_l4_port = stoi(keys[2]); + keyEntry.dst_ip = IpAddress(keys[3]); + keyEntry.dst_l4_port = stoi(keys[4]); + keyEntry.prototype = keys[0]; + + if (op == SET_COMMAND) + { + TwiceNaptEntryValue entry; + string type; + + for (auto i : kfvFieldsValues(t)) + { + if (fvField(i) == "entry_type") + type = fvValue(i); + else if (fvField(i) == "translated_src_ip") + entry.translated_src_ip = IpAddress(fvValue(i)); + else if (fvField(i) == "translated_src_l4_port") + entry.translated_src_l4_port = stoi(fvValue(i)); + else if (fvField(i) == "translated_dst_ip") + entry.translated_dst_ip = IpAddress(fvValue(i)); + else if (fvField(i) == "translated_dst_l4_port") + entry.translated_dst_l4_port = stoi(fvValue(i)); + } + + /* NAT type is either dynamic or static */ + assert(type == "dynamic" || type == "static"); + entry.entry_type = type; + entry.addedToHw = false; + + if (addTwiceNaptEntry(keyEntry, entry)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else if (op == DEL_COMMAND) + { + if (removeTwiceNaptEntry(keyEntry)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s\n", op.c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +void NatOrch::doNatGlobalTableTask(Consumer& consumer) +{ + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + string key = kfvKey(t); + string op = kfvOp(t); + string mode; + vector keys = tokenize(key, ':'); + + /* Example : APPL_DB + * NAT_GLOBAL_TABLE:Values + * admin_mode: disabled + * nat_timeout : 600 + * nat_tcp_timeout : 100 + * nat_udp_timeout : 500 + */ + + /* Ensure the key is "Values" otherwise ignore */ + if (strcmp(key.c_str(), VALUES)) + { + SWSS_LOG_ERROR("Invalid key format. No Values: %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + for (auto i : kfvFieldsValues(t)) + { + if (fvField(i) == "admin_mode") + { + mode = fvValue(i); + + /* NAT mode is either enabled or disabled */ + assert(mode == "enabled" || mode == "disabled"); + + if (mode != admin_mode) + { + if (mode == "enabled") + enableNatFeature(); + else + disableNatFeature(); + } + } + else if (fvField(i) == "nat_tcp_timeout") + { + tcp_timeout = stoi(fvValue(i)); + updateConnTrackTimeout("tcp"); + } + else if (fvField(i) == "nat_udp_timeout") + { + udp_timeout = stoi(fvValue(i)); + updateConnTrackTimeout("udp"); + } + else if (fvField(i) == "nat_timeout") + { + timeout = stoi(fvValue(i)); + updateConnTrackTimeout("all"); + } + } + + SWSS_LOG_INFO("Global Values - Admin mode - %s, TCP - %d, UDP - %d and Both - %d", admin_mode.c_str(), tcp_timeout, udp_timeout, timeout); + + it = consumer.m_toSync.erase(it); + } +} + +void NatOrch::doTask(Consumer& consumer) +{ + SWSS_LOG_ENTER(); + + string table_name = consumer.getTableName(); + + unique_lock lock(m_natMutex); + + if (table_name == APP_NAT_TABLE_NAME) + { + SWSS_LOG_INFO("Received APP_NAT_TABLE_NAME update"); + doNatTableTask(consumer); + } + else if (table_name == APP_NAPT_TABLE_NAME) + { + SWSS_LOG_INFO("Received APP_NAPT_TABLE_NAME update"); + doNaptTableTask(consumer); + } + if (table_name == APP_NAT_TWICE_TABLE_NAME) + { + SWSS_LOG_INFO("Received APP_NAT_TWICE_TABLE_NAME update"); + doTwiceNatTableTask(consumer); + } + else if (table_name == APP_NAPT_TWICE_TABLE_NAME) + { + SWSS_LOG_INFO("Received APP_NAPT_TWICE_TABLE_NAME update"); + doTwiceNaptTableTask(consumer); + } + else if (table_name == APP_NAT_GLOBAL_TABLE_NAME) + { + SWSS_LOG_INFO("Received APP_NAT_GLOBAL_TABLE_NAME update"); + doNatGlobalTableTask(consumer); + } + else + { + SWSS_LOG_INFO("Received unknown NAT Table - %s notification", table_name.c_str()); + return; + } +} + +struct timespec getTimeDiff(const struct timespec &begin, const struct timespec &end) +{ + struct timespec diff = {0, 0}; + + if (end.tv_nsec-begin.tv_nsec < 0) + { + diff.tv_sec = end.tv_sec - begin.tv_sec - 1; + diff.tv_nsec = end.tv_nsec - begin.tv_nsec + 1000000000UL; + } + else + { + diff.tv_sec = end.tv_sec - begin.tv_sec; + diff.tv_nsec = end.tv_nsec - begin.tv_nsec; + } + return diff; +} + +void NatOrch::doTask(SelectableTimer &timer) +{ + SWSS_LOG_ENTER(); + + if (((natTimerTickCntr++) % NAT_HITBIT_QUERY_MULTIPLE) == 0) + { + queryHitBits(); + } + queryCounters(); +} + +void NatOrch::queryCounters(void) +{ + SWSS_LOG_ENTER(); + + uint32_t queried_entries = 0; + struct timespec time_now, time_end, time_spent; + + if (clock_gettime (CLOCK_MONOTONIC, &time_now) < 0) + { + return; + } + + NatEntry::iterator natIter = m_natEntries.begin(); + while (natIter != m_natEntries.end()) + { + getNatCounters(natIter); + + queried_entries++; + natIter++; + } + + NaptEntry::iterator naptIter = m_naptEntries.begin(); + while (naptIter != m_naptEntries.end()) + { + getNaptCounters(naptIter); + + queried_entries++; + naptIter++; + } + TwiceNatEntry::iterator tnatIter = m_twiceNatEntries.begin(); + while (tnatIter != m_twiceNatEntries.end()) + { + getTwiceNatCounters(tnatIter); + + queried_entries++; + tnatIter++; + } + + TwiceNaptEntry::iterator tnaptIter = m_twiceNaptEntries.begin(); + while (tnaptIter != m_twiceNaptEntries.end()) + { + getTwiceNaptCounters(tnaptIter); + + queried_entries++; + tnaptIter++; + } + + if (clock_gettime (CLOCK_MONOTONIC, &time_end) < 0) + { + return; + } + time_spent = getTimeDiff(time_now, time_end); + + if (queried_entries) + { + SWSS_LOG_DEBUG("Time spent in querying counters for %u NAT/NAPT entries = %lu secs, %lu msecs", + queried_entries, time_spent.tv_sec, (time_spent.tv_nsec / 1000000UL)); + } +} + +void NatOrch::addAllNatEntries(void) +{ + SWSS_LOG_ENTER(); + + NatEntry::iterator natIter = m_natEntries.begin(); + while (natIter != m_natEntries.end()) + { + if ((*natIter).second.addedToHw == false) + { + if ((*natIter).second.nat_type == "snat") + { + /* Add SNAT entry to the hardware */ + addHwSnatEntry((*natIter).first); + } + else if ((*natIter).second.nat_type == "dnat") + { + if (gNhTrackingSupported == true) + { + addDnatToNhCache((*natIter).second.translated_ip, (*natIter).first); + } + else + { + addHwDnatEntry((*natIter).first); + } + } + } + natIter++; + } + + NaptEntry::iterator naptIter = m_naptEntries.begin(); + while (naptIter != m_naptEntries.end()) + { + if ((*naptIter).second.addedToHw == false) + { + if ((*naptIter).second.nat_type == "snat") + { + /* Add SNAPT entry to the hardware */ + addHwSnaptEntry((*naptIter).first); + } + else if ((*naptIter).second.nat_type == "dnat") + { + if (gNhTrackingSupported == true) + { + addDnaptToNhCache((*naptIter).second.translated_ip, (*naptIter).first); + } + else + { + addHwDnaptEntry((*naptIter).first); + } + } + } + naptIter++; + } + + TwiceNatEntry::iterator twiceNatIter = m_twiceNatEntries.begin(); + while (twiceNatIter != m_twiceNatEntries.end()) + { + if ((*twiceNatIter).second.addedToHw == false) + { + if (gNhTrackingSupported == true) + { + /* Cache the Twice NAT entry in the nexthop resolution cache */ + addTwiceNatToNhCache((*twiceNatIter).second.translated_dst_ip, (*twiceNatIter).first); + } + else + { + /* Add Twice NAT entry to the hardware */ + addHwTwiceNatEntry((*twiceNatIter).first); + } + } + twiceNatIter++; + } + + TwiceNaptEntry::iterator twiceNaptIter = m_twiceNaptEntries.begin(); + while (twiceNaptIter != m_twiceNaptEntries.end()) + { + if ((*twiceNaptIter).second.addedToHw == false) + { + if (gNhTrackingSupported == true) + { + /* Cache the Twice NAPT entry in the nexthop resolution cache */ + addTwiceNaptToNhCache((*twiceNaptIter).second.translated_dst_ip, (*twiceNaptIter).first); + } + else + { + /* Add Twice NAPT entry to the hardware */ + addHwTwiceNaptEntry((*twiceNaptIter).first); + } + } + twiceNaptIter++; + } +} + +void NatOrch::clearCounters(void) +{ + SWSS_LOG_ENTER(); + + NatEntry::iterator natIter = m_natEntries.begin(); + while (natIter != m_natEntries.end()) + { + setNatCounters(natIter); + natIter++; + } + + NaptEntry::iterator naptIter = m_naptEntries.begin(); + while (naptIter != m_naptEntries.end()) + { + setNaptCounters(naptIter); + naptIter++; + } + + TwiceNatEntry::iterator twiceNatIter = m_twiceNatEntries.begin(); + while (twiceNatIter != m_twiceNatEntries.end()) + { + setTwiceNatCounters(twiceNatIter); + twiceNatIter++; + } + + TwiceNaptEntry::iterator twiceNaptIter = m_twiceNaptEntries.begin(); + while (twiceNaptIter != m_twiceNaptEntries.end()) + { + setTwiceNaptCounters(twiceNaptIter); + twiceNaptIter++; + } +} + +void NatOrch::addAllStaticConntrackEntries(void) +{ + SWSS_LOG_ENTER(); + + NatEntry::iterator natIter = m_natEntries.begin(); + while (natIter != m_natEntries.end()) + { + if (((*natIter).second.entry_type == "static") and + ((*natIter).second.addedToHw == true) and + ((*natIter).second.nat_type == "snat")) + { + addConnTrackEntry((*natIter).first); + } + natIter++; + } + NaptEntry::iterator naptIter = m_naptEntries.begin(); + while (naptIter != m_naptEntries.end()) + { + if (((*naptIter).second.entry_type == "static") and + ((*naptIter).second.addedToHw == true) and + ((*naptIter).second.nat_type == "snat")) + { + addConnTrackEntry((*naptIter).first); + } + naptIter++; + } + TwiceNatEntry::iterator twiceNatIter = m_twiceNatEntries.begin(); + while (twiceNatIter != m_twiceNatEntries.end()) + { + if (((*twiceNatIter).second.entry_type == "static") and + ((*twiceNatIter).second.addedToHw == true)) + { + addConnTrackEntry((*twiceNatIter).first); + } + twiceNatIter++; + } + TwiceNaptEntry::iterator twiceNaptIter = m_twiceNaptEntries.begin(); + while (twiceNaptIter != m_twiceNaptEntries.end()) + { + if (((*twiceNaptIter).second.entry_type == "static") and + ((*twiceNaptIter).second.addedToHw == true)) + { + addConnTrackEntry((*twiceNaptIter).first); + } + twiceNaptIter++; + } +} + +void NatOrch::queryHitBits(void) +{ + SWSS_LOG_ENTER(); + + uint32_t queried_entries = 0; + struct timespec time_now, time_end, time_spent; + + if (clock_gettime (CLOCK_MONOTONIC, &time_now) < 0) + { + return; + } + + /* Remove the NAT entries that are aged out. + * Query the NAT entries for their activity in the hardware + * and update the aging timeout. */ + NatEntry::iterator natIter = m_natEntries.begin(); + while (natIter != m_natEntries.end()) + { + if (checkIfNatEntryIsActive(natIter, time_now.tv_sec)) + { + /* Since the entry is active in the hardware, reset + * the expiry time for the conntrack entry in the kernel. */ + updateConnTrackTimeout(natIter->first); + } + queried_entries++; + natIter++; + } + + /* Remove the NAPT entries that are aged out. + * Query the NAPT entries for their activity in the hardware + * and update the aging timeout. */ + NaptEntry::iterator naptIter = m_naptEntries.begin(); + while (naptIter != m_naptEntries.end()) + { + if (checkIfNaptEntryIsActive(naptIter, time_now.tv_sec)) + { + /* Since the entry is active in the hardware, reset + * the expiry time for the conntrack entry in the kernel. */ + updateConnTrackTimeout(naptIter->first); + } + queried_entries++; + naptIter++; + } + + /* Remove the Twice NAT entries that are aged out. + * Query the Twice NAT entries for their activity in the hardware + * and update the aging timeout. */ + TwiceNatEntry::iterator twiceNatIter = m_twiceNatEntries.begin(); + while (twiceNatIter != m_twiceNatEntries.end()) + { + if (checkIfTwiceNatEntryIsActive(twiceNatIter, time_now.tv_sec)) + { + /* Since the entry is active in the hardware, reset + * the expiry time for the conntrack entry in the kernel. */ + updateConnTrackTimeout(twiceNatIter->first); + } + queried_entries++; + twiceNatIter++; + } + + /* Remove the Twice NAPT entries that are aged out. + * Query the Twice NAPT entries for their activity in the hardware + * and update the aging timeout. */ + TwiceNaptEntry::iterator twiceNaptIter = m_twiceNaptEntries.begin(); + while (twiceNaptIter != m_twiceNaptEntries.end()) + { + if (checkIfTwiceNaptEntryIsActive(twiceNaptIter, time_now.tv_sec)) + { + /* Since the entry is active in the hardware, reset + * the expiry time for the conntrack entry in the kernel. */ + updateConnTrackTimeout(twiceNaptIter->first); + } + queried_entries++; + twiceNaptIter++; + } + if (clock_gettime (CLOCK_MONOTONIC, &time_end) < 0) + { + return; + } + time_spent = getTimeDiff(time_now, time_end); + + if (queried_entries) + { + SWSS_LOG_DEBUG("Time spent in querying hardware hit-bits for %u NAT/NAPT entries = %lu secs, %lu msecs", + queried_entries, time_spent.tv_sec, (time_spent.tv_nsec / 1000000UL)); + } +} + +bool NatOrch::getNatCounters(const NatEntry::iterator &iter) +{ + const IpAddress &ipAddr = iter->first; + NatEntryValue &entry = iter->second; + uint32_t attr_count; + sai_attribute_t nat_entry_attr[4]; + sai_nat_entry_t nat_entry; + sai_status_t status; + uint64_t nat_translations_pkts = 0, nat_translations_bytes = 0; + + if (entry.addedToHw == false) + { + SWSS_LOG_DEBUG("Skip get Counters for %s NAT entry [ip %s], as not yet added to HW", entry.nat_type.c_str(), ipAddr.to_string().c_str()); + return 0; + } + + memset(nat_entry_attr, 0, sizeof(nat_entry_attr)); + nat_entry_attr[0].id = SAI_NAT_ENTRY_ATTR_BYTE_COUNT; + nat_entry_attr[1].id = SAI_NAT_ENTRY_ATTR_PACKET_COUNT; + + attr_count = 2; + + memset(&nat_entry, 0, sizeof(nat_entry)); + + nat_entry.vr_id = gVirtualRouterId; + nat_entry.switch_id = gSwitchId; + + if (entry.nat_type == "dnat") + { + nat_entry.data.key.dst_ip = ipAddr.getV4Addr(); + nat_entry.data.mask.dst_ip = 0xffffffff; + } + else + { + nat_entry.data.key.src_ip = ipAddr.getV4Addr(); + nat_entry.data.mask.src_ip = 0xffffffff; + } + + status = sai_nat_api->get_nat_entry_attribute(&nat_entry, attr_count, nat_entry_attr); + if (entry.nat_type == "snat") + { + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to get Counters for SNAT entry [src-ip %s], bytes = %lu, pkts = %lu", ipAddr.to_string().c_str(), + nat_entry_attr[0].value.u64, nat_entry_attr[1].value.u64); + } + else + { + nat_translations_bytes = nat_entry_attr[0].value.u64; + nat_translations_pkts = nat_entry_attr[1].value.u64; + } + } + else if (entry.nat_type == "dnat") + { + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to get Counters for DNAT entry [dst-ip %s], bytes = %lu, pkts = %lu", ipAddr.to_string().c_str(), + nat_entry_attr[0].value.u64, nat_entry_attr[1].value.u64); + } + else + { + nat_translations_bytes = nat_entry_attr[0].value.u64; + nat_translations_pkts = nat_entry_attr[1].value.u64; + } + } + + /* Update the Counter values in the database */ + updateNatCounters(ipAddr, nat_translations_pkts, nat_translations_bytes); + + return 0; +} + +bool NatOrch::getTwiceNatCounters(const TwiceNatEntry::iterator &iter) +{ + const TwiceNatEntryKey &key = iter->first; + TwiceNatEntryValue &entry = iter->second; + uint32_t attr_count; + sai_attribute_t nat_entry_attr[4]; + sai_nat_entry_t dbl_nat_entry; + sai_status_t status; + uint64_t nat_translations_pkts = 0, nat_translations_bytes = 0; + + if (entry.addedToHw == false) + { + SWSS_LOG_DEBUG("Skip get Counters for Twice NAT entry [src ip %s, dst ip %s], as not yet added to HW", + key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str()); + return 0; + } + + memset(nat_entry_attr, 0, sizeof(nat_entry_attr)); + nat_entry_attr[0].id = SAI_NAT_ENTRY_ATTR_BYTE_COUNT; + nat_entry_attr[1].id = SAI_NAT_ENTRY_ATTR_PACKET_COUNT; + + attr_count = 2; + + memset(&dbl_nat_entry, 0, sizeof(dbl_nat_entry)); + + dbl_nat_entry.vr_id = gVirtualRouterId; + dbl_nat_entry.switch_id = gSwitchId; + dbl_nat_entry.data.key.src_ip = key.src_ip.getV4Addr(); + dbl_nat_entry.data.mask.src_ip = 0xffffffff; + dbl_nat_entry.data.key.dst_ip = key.dst_ip.getV4Addr(); + dbl_nat_entry.data.mask.dst_ip = 0xffffffff; + + status = sai_nat_api->get_nat_entry_attribute(&dbl_nat_entry, attr_count, nat_entry_attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to get Counters for Twice NAT entry [src-ip %s, dst-ip %s], bytes = %lu, pkts = %lu", + key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str(), + nat_entry_attr[0].value.u64, nat_entry_attr[1].value.u64); + } + else + { + nat_translations_bytes = nat_entry_attr[0].value.u64; + nat_translations_pkts = nat_entry_attr[1].value.u64; + } + + /* Update the Counter values in the database */ + updateTwiceNatCounters(key, nat_translations_pkts, nat_translations_bytes); + + return 0; +} + +bool NatOrch::setNatCounters(const NatEntry::iterator &iter) +{ + const IpAddress &ipAddr = iter->first; + NatEntryValue &entry = iter->second; + sai_attribute_t nat_entry_attr_packet; + sai_attribute_t nat_entry_attr_byte; + sai_nat_entry_t nat_entry; + sai_status_t status; + uint64_t nat_translations_pkts = 0, nat_translations_bytes = 0; + + if (entry.addedToHw == false) + { + SWSS_LOG_DEBUG("Skip set Counters for %s NAT entry [ip %s], as not yet added to HW", entry.nat_type.c_str(), ipAddr.to_string().c_str()); + return 0; + } + + memset(&nat_entry_attr_packet, 0, sizeof(nat_entry_attr_packet)); + memset(&nat_entry_attr_byte, 0, sizeof(nat_entry_attr_byte)); + nat_entry_attr_byte.id = SAI_NAT_ENTRY_ATTR_BYTE_COUNT; + nat_entry_attr_packet.id = SAI_NAT_ENTRY_ATTR_PACKET_COUNT; + + memset(&nat_entry, 0, sizeof(nat_entry)); + + nat_entry.vr_id = gVirtualRouterId; + nat_entry.switch_id = gSwitchId; + + if (entry.nat_type == "dnat") + { + nat_entry.data.key.dst_ip = ipAddr.getV4Addr(); + nat_entry.data.mask.dst_ip = 0xffffffff; + } + else + { + nat_entry.data.key.src_ip = ipAddr.getV4Addr(); + nat_entry.data.mask.src_ip = 0xffffffff; + } + + status = sai_nat_api->set_nat_entry_attribute(&nat_entry, &nat_entry_attr_packet); + + if (entry.nat_type == "snat") + { + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to clear packet counter for SNAT entry [src-ip %s]", ipAddr.to_string().c_str()); + } + } + else if (entry.nat_type == "dnat") + { + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to clear packet counter for DNAT entry [dst-ip %s]", ipAddr.to_string().c_str()); + } + } + + status = sai_nat_api->set_nat_entry_attribute(&nat_entry, &nat_entry_attr_byte); + + if (entry.nat_type == "snat") + { + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to clear byte counter for SNAT entry [src-ip %s]", ipAddr.to_string().c_str()); + } + } + else if (entry.nat_type == "dnat") + { + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to clear byte counter for DNAT entry [dst-ip %s]", ipAddr.to_string().c_str()); + } + } + /* Update the Counter values in the database */ + updateNatCounters(ipAddr, nat_translations_pkts, nat_translations_bytes); + + return 0; +} + +bool NatOrch::getNaptCounters(const NaptEntry::iterator &iter) +{ + const NaptEntryKey &naptKey = iter->first; + NaptEntryValue &entry = iter->second; + uint8_t protoType = ((naptKey.prototype == "TCP") ? IPPROTO_TCP : IPPROTO_UDP); + uint32_t attr_count; + sai_attribute_t nat_entry_attr[4]; + sai_nat_entry_t nat_entry; + sai_status_t status; + uint64_t nat_translations_pkts = 0, nat_translations_bytes = 0; + + if (entry.addedToHw == false) + { + SWSS_LOG_DEBUG("Skip get Counters for %s NAPT entry for [proto %s, ip %s, port %d], as not yet added to HW", + entry.nat_type.c_str(), naptKey.prototype.c_str(), naptKey.ip_address.to_string().c_str(), naptKey.l4_port); + return 0; + } + + memset(nat_entry_attr, 0, sizeof(nat_entry_attr)); + nat_entry_attr[0].id = SAI_NAT_ENTRY_ATTR_BYTE_COUNT; + nat_entry_attr[1].id = SAI_NAT_ENTRY_ATTR_PACKET_COUNT; + + attr_count = 2; + + memset(&nat_entry, 0, sizeof(nat_entry)); + + nat_entry.vr_id = gVirtualRouterId; + nat_entry.switch_id = gSwitchId; + + if (entry.nat_type == "dnat") + { + nat_entry.data.key.dst_ip = naptKey.ip_address.getV4Addr(); + nat_entry.data.key.l4_dst_port = (uint16_t)(naptKey.l4_port); + nat_entry.data.mask.dst_ip = 0xffffffff; + nat_entry.data.mask.l4_dst_port = 0xffff; + } + else if (entry.nat_type == "snat") + { + nat_entry.data.key.src_ip = naptKey.ip_address.getV4Addr(); + nat_entry.data.key.l4_src_port = (uint16_t)(naptKey.l4_port); + nat_entry.data.mask.src_ip = 0xffffffff; + nat_entry.data.mask.l4_src_port = 0xffff; + } + + nat_entry.data.key.proto = protoType; + nat_entry.data.mask.proto = 0xff; + + status = sai_nat_api->get_nat_entry_attribute(&nat_entry, attr_count, nat_entry_attr); + + if (entry.nat_type == "snat") + { + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to get Counters for SNAPT entry for [proto %s, src-ip %s, src-port %d], bytes = %lu, pkts = %lu", + naptKey.prototype.c_str(), naptKey.ip_address.to_string().c_str(), naptKey.l4_port, + nat_entry_attr[0].value.u64, nat_entry_attr[1].value.u64); + } + else + { + nat_translations_bytes = nat_entry_attr[0].value.u64; + nat_translations_pkts = nat_entry_attr[1].value.u64; + } + } + else if (entry.nat_type == "dnat") + { + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to get Counters for DNAPT entry for [proto %s, dst-ip %s, dst-port %d], bytes = %lu, pkts = %lu", + naptKey.prototype.c_str(), naptKey.ip_address.to_string().c_str(), naptKey.l4_port, + nat_entry_attr[0].value.u64, nat_entry_attr[1].value.u64); + } + else + { + nat_translations_bytes = nat_entry_attr[0].value.u64; + nat_translations_pkts = nat_entry_attr[1].value.u64; + } + } + + /* Update the Counter values in the database */ + updateNaptCounters(naptKey.prototype, naptKey.ip_address, naptKey.l4_port, + nat_translations_pkts, nat_translations_bytes); + return 0; +} + +bool NatOrch::getTwiceNaptCounters(const TwiceNaptEntry::iterator &iter) +{ + const TwiceNaptEntryKey &key = iter->first; + TwiceNaptEntryValue &entry = iter->second; + uint8_t protoType = ((key.prototype == "TCP") ? IPPROTO_TCP : IPPROTO_UDP); + uint32_t attr_count; + sai_attribute_t nat_entry_attr[4]; + sai_nat_entry_t dbl_nat_entry; + sai_status_t status; + uint64_t nat_translations_pkts = 0, nat_translations_bytes = 0; + + if (entry.addedToHw == false) + { + SWSS_LOG_DEBUG("Skip get Counters for Twice NAPT entry for [proto %s, src ip %s, src port %d, dst ip %s, dst port %d], as not yet added to HW", + key.prototype.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), + key.dst_l4_port); + return 0; + } + + memset(nat_entry_attr, 0, sizeof(nat_entry_attr)); + nat_entry_attr[0].id = SAI_NAT_ENTRY_ATTR_BYTE_COUNT; + nat_entry_attr[1].id = SAI_NAT_ENTRY_ATTR_PACKET_COUNT; + + attr_count = 2; + + memset(&dbl_nat_entry, 0, sizeof(dbl_nat_entry)); + + dbl_nat_entry.vr_id = gVirtualRouterId; + dbl_nat_entry.switch_id = gSwitchId; + dbl_nat_entry.data.key.src_ip = key.src_ip.getV4Addr(); + dbl_nat_entry.data.mask.src_ip = 0xffffffff; + dbl_nat_entry.data.key.l4_src_port = (uint16_t)(key.src_l4_port); + dbl_nat_entry.data.mask.l4_src_port = 0xffff; + dbl_nat_entry.data.key.dst_ip = key.dst_ip.getV4Addr(); + dbl_nat_entry.data.mask.dst_ip = 0xffffffff; + dbl_nat_entry.data.key.l4_dst_port = (uint16_t)(key.dst_l4_port); + dbl_nat_entry.data.mask.l4_dst_port = 0xffff; + dbl_nat_entry.data.key.proto = protoType; + dbl_nat_entry.data.mask.proto = 0xff; + + status = sai_nat_api->get_nat_entry_attribute(&dbl_nat_entry, attr_count, nat_entry_attr); + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_DEBUG("Failed to get Counters for Twice NAPT entry for [proto %s, src ip %s, src port %d, dst ip %s, dst port %d], as not yet added to HW", + key.prototype.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), + key.dst_l4_port); + } + else + { + nat_translations_bytes = nat_entry_attr[0].value.u64; + nat_translations_pkts = nat_entry_attr[1].value.u64; + } + + /* Update the Counter values in the database */ + updateTwiceNaptCounters(key, nat_translations_pkts, nat_translations_bytes); + return 0; +} + +bool NatOrch::setNaptCounters(const NaptEntry::iterator &iter) +{ + const NaptEntryKey &naptKey = iter->first; + NaptEntryValue &entry = iter->second; + uint8_t protoType = ((naptKey.prototype == "TCP") ? IPPROTO_TCP : IPPROTO_UDP); + sai_attribute_t nat_entry_attr_packet; + sai_attribute_t nat_entry_attr_byte; + sai_nat_entry_t nat_entry; + sai_status_t status; + uint64_t nat_translations_pkts = 0, nat_translations_bytes = 0; + + if (entry.addedToHw == false) + { + SWSS_LOG_DEBUG("Skip set Counters for %s NAPT entry for [proto %s, ip %s, port %d], as not yet added to HW", + entry.nat_type.c_str(), naptKey.prototype.c_str(), naptKey.ip_address.to_string().c_str(), naptKey.l4_port); + return 0; + } + + memset(&nat_entry_attr_packet, 0, sizeof(nat_entry_attr_packet)); + memset(&nat_entry_attr_byte, 0, sizeof(nat_entry_attr_byte)); + nat_entry_attr_packet.id = SAI_NAT_ENTRY_ATTR_PACKET_COUNT; + nat_entry_attr_byte.id = SAI_NAT_ENTRY_ATTR_BYTE_COUNT; + + memset(&nat_entry, 0, sizeof(nat_entry)); + + nat_entry.vr_id = gVirtualRouterId; + nat_entry.switch_id = gSwitchId; + + if (entry.nat_type == "dnat") + { + nat_entry.data.key.dst_ip = naptKey.ip_address.getV4Addr(); + nat_entry.data.key.l4_dst_port = (uint16_t)(naptKey.l4_port); + nat_entry.data.mask.dst_ip = 0xffffffff; + nat_entry.data.mask.l4_dst_port = 0xffff; + } + else if (entry.nat_type == "snat") + { + nat_entry.data.key.src_ip = naptKey.ip_address.getV4Addr(); + nat_entry.data.key.l4_src_port = (uint16_t)(naptKey.l4_port); + nat_entry.data.mask.src_ip = 0xffffffff; + nat_entry.data.mask.l4_src_port = 0xffff; + } + + nat_entry.data.key.proto = protoType; + nat_entry.data.mask.proto = 0xff; + + status = sai_nat_api->set_nat_entry_attribute(&nat_entry, &nat_entry_attr_packet); + + if (entry.nat_type == "snat") + { + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to clear packet counter for SNAPT entry for [proto %s, src-ip %s, src-port %d", + naptKey.prototype.c_str(), naptKey.ip_address.to_string().c_str(), naptKey.l4_port); + } + } + else if (entry.nat_type == "dnat") + { + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to clear packet counter for DNAPT entry for [proto %s, dst-ip %s, dst-port %d]", + naptKey.prototype.c_str(), naptKey.ip_address.to_string().c_str(), naptKey.l4_port); + } + } + + status = sai_nat_api->set_nat_entry_attribute(&nat_entry, &nat_entry_attr_byte); + + if (entry.nat_type == "snat") + { + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to clear byte counter for SNAPT entry for [proto %s, src-ip %s, src-port %d", + naptKey.prototype.c_str(), naptKey.ip_address.to_string().c_str(), naptKey.l4_port); + } + } + else if (entry.nat_type == "dnat") + { + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to clear byte counter for DNAPT entry for [proto %s, dst-ip %s, dst-port %d]", + naptKey.prototype.c_str(), naptKey.ip_address.to_string().c_str(), naptKey.l4_port); + } + } + + /* Update the Counter values in the database */ + updateNaptCounters(naptKey.prototype, naptKey.ip_address, naptKey.l4_port, + nat_translations_pkts, nat_translations_bytes); + return 0; +} + +bool NatOrch::setTwiceNatCounters(const TwiceNatEntry::iterator &iter) +{ + const TwiceNatEntryKey &key = iter->first; + TwiceNatEntryValue &entry = iter->second; + sai_attribute_t nat_entry_attr_packet; + sai_attribute_t nat_entry_attr_byte; + sai_nat_entry_t dbl_nat_entry; + sai_status_t status; + uint64_t nat_translations_pkts = 0, nat_translations_bytes = 0; + + if (entry.addedToHw == false) + { + SWSS_LOG_DEBUG("Skip set Counters for Twice NAT entry [src ip %s, dst ip %s], as not yet added to HW", + key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str()); + return 0; + } + + memset(&nat_entry_attr_packet, 0, sizeof(nat_entry_attr_packet)); + memset(&nat_entry_attr_byte, 0, sizeof(nat_entry_attr_byte)); + nat_entry_attr_packet.id = SAI_NAT_ENTRY_ATTR_PACKET_COUNT; + nat_entry_attr_byte.id = SAI_NAT_ENTRY_ATTR_BYTE_COUNT; + + memset(&dbl_nat_entry, 0, sizeof(dbl_nat_entry)); + + dbl_nat_entry.vr_id = gVirtualRouterId; + dbl_nat_entry.switch_id = gSwitchId; + dbl_nat_entry.data.key.src_ip = key.src_ip.getV4Addr(); + dbl_nat_entry.data.mask.src_ip = 0xffffffff; + dbl_nat_entry.data.key.dst_ip = key.dst_ip.getV4Addr(); + dbl_nat_entry.data.mask.dst_ip = 0xffffffff; + + status = sai_nat_api->set_nat_entry_attribute(&dbl_nat_entry, &nat_entry_attr_packet); + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to clear packet counters for Twice NAT entry [src-ip %s, dst-ip %s]", + key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str()); + } + + status = sai_nat_api->set_nat_entry_attribute(&dbl_nat_entry, &nat_entry_attr_byte); + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to clear byte counters for Twice NAT entry [src-ip %s, dst-ip %s]", + key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str()); + } + + /* Update the Counter values in the database */ + updateTwiceNatCounters(key, nat_translations_pkts, nat_translations_bytes); + + return 0; +} + +bool NatOrch::setTwiceNaptCounters(const TwiceNaptEntry::iterator &iter) +{ + const TwiceNaptEntryKey &key = iter->first; + TwiceNaptEntryValue &entry = iter->second; + uint8_t protoType = ((key.prototype == "TCP") ? IPPROTO_TCP : IPPROTO_UDP); + sai_attribute_t nat_entry_attr_packet; + sai_attribute_t nat_entry_attr_byte; + sai_nat_entry_t dbl_nat_entry; + sai_status_t status; + uint64_t nat_translations_pkts = 0, nat_translations_bytes = 0; + + if (entry.addedToHw == false) + { + SWSS_LOG_DEBUG("Skip set Counters for Twice NAPT entry [src ip %s, src port %d, dst ip %s, dst port %d], as not yet added to HW", + key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), key.dst_l4_port); + return 0; + } + + memset(&nat_entry_attr_packet, 0, sizeof(nat_entry_attr_packet)); + memset(&nat_entry_attr_byte, 0, sizeof(nat_entry_attr_byte)); + nat_entry_attr_packet.id = SAI_NAT_ENTRY_ATTR_PACKET_COUNT; + nat_entry_attr_byte.id = SAI_NAT_ENTRY_ATTR_BYTE_COUNT; + + memset(&dbl_nat_entry, 0, sizeof(dbl_nat_entry)); + + dbl_nat_entry.vr_id = gVirtualRouterId; + dbl_nat_entry.switch_id = gSwitchId; + dbl_nat_entry.data.key.src_ip = key.src_ip.getV4Addr(); + dbl_nat_entry.data.mask.src_ip = 0xffffffff; + dbl_nat_entry.data.key.l4_src_port = (uint16_t)(key.src_l4_port); + dbl_nat_entry.data.mask.l4_src_port = 0xffff; + dbl_nat_entry.data.key.dst_ip = key.dst_ip.getV4Addr(); + dbl_nat_entry.data.mask.dst_ip = 0xffffffff; + dbl_nat_entry.data.key.l4_dst_port = (uint16_t)(key.dst_l4_port); + dbl_nat_entry.data.mask.l4_dst_port = 0xffff; + dbl_nat_entry.data.key.proto = protoType; + dbl_nat_entry.data.mask.proto = 0xff; + + status = sai_nat_api->set_nat_entry_attribute(&dbl_nat_entry, &nat_entry_attr_packet); + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to clear packet counters for Twice NAPT entry [src-ip %s, src port %d, dst-ip %s, dst port %d]", + key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), key.dst_l4_port); + } + + status = sai_nat_api->set_nat_entry_attribute(&dbl_nat_entry, &nat_entry_attr_byte); + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to clear byte counters for Twice NAPT entry [src-ip %s, src port %d, dst-ip %s, dst port %d]", + key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), key.dst_l4_port); + } + + /* Update the Counter values in the database */ + updateTwiceNaptCounters(key, nat_translations_pkts, nat_translations_bytes); + + return 0; +} + +void NatOrch::deleteConnTrackEntry(const IpAddress &ipAddr) +{ + std::string res; + std::string cmds = std::string("") + CONNTRACK + DELETE; + + cmds += (" -s " + ipAddr.to_string()); + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Deleted the NAT conntrack entry"); + } +} + +void NatOrch::deleteConnTrackEntry(const NaptEntryKey &key) +{ + std::string res; + std::string prototype = ((key.prototype == string("TCP")) ? "tcp" : "udp"); + std::string cmds = std::string("") + CONNTRACK + DELETE; + + cmds += (" -s " + key.ip_address.to_string() + " -p " + prototype + " --orig-port-src " + to_string(key.l4_port)); + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Deleted the NAPT conntrack entry"); + } +} + +void NatOrch::deleteConnTrackEntry(const TwiceNatEntryKey &key) +{ + std::string res; + std::string cmds = std::string("") + CONNTRACK + DELETE; + + cmds += (" -s " + key.src_ip.to_string()); + cmds += (" -d " + key.dst_ip.to_string()); + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Deleted the Twice NAT conntrack entry"); + } +} + +void NatOrch::deleteConnTrackEntry(const TwiceNaptEntryKey &key) +{ + std::string res; + std::string prototype = ((key.prototype == string("TCP")) ? "tcp" : "udp"); + std::string cmds = std::string("") + CONNTRACK + DELETE; + + cmds += (" -s " + key.src_ip.to_string() + " -p " + prototype + " --orig-port-src " + to_string(key.src_l4_port)); + cmds += (" -d " + key.dst_ip.to_string() + " --orig-port-dst " + to_string(key.dst_l4_port)); + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Deleted the Twice NAPT conntrack entry"); + } +} + +void NatOrch::updateNatCounters(const IpAddress &ipAddr, + uint64_t nat_translations_pkts, uint64_t nat_translations_bytes) +{ + vector values; + string key = ipAddr.to_string().c_str(); + + swss::FieldValueTuple p("NAT_TRANSLATIONS_PKTS", std::to_string(nat_translations_pkts)); + values.push_back(p); + swss::FieldValueTuple q("NAT_TRANSLATIONS_BYTES", std::to_string(nat_translations_bytes)); + values.push_back(q); + + m_countersNatTable.set(key, values); +} + +void NatOrch::deleteNatCounters(const IpAddress &ipAddr) +{ + string key = ipAddr.to_string(); + + m_countersNatTable.del(key); +} + +void NatOrch::deleteTwiceNatCounters(const TwiceNatEntryKey &key) +{ + string natKey = key.src_ip.to_string() + ":" + key.dst_ip.to_string(); + + m_countersTwiceNatTable.del(natKey); +} + +void NatOrch::updateNaptCounters(const string &protocol, const IpAddress &ipAddr, int l4_port, + uint64_t nat_translations_pkts, uint64_t nat_translations_bytes) +{ + vector values; + string protoStr = protocol.c_str(), ipStr = ipAddr.to_string().c_str(), portStr = std::to_string(l4_port); + string key = (protoStr + ":" + ipStr + ":" + portStr); + + swss::FieldValueTuple p("NAT_TRANSLATIONS_PKTS", to_string(nat_translations_pkts)); + values.push_back(p); + swss::FieldValueTuple q("NAT_TRANSLATIONS_BYTES", to_string(nat_translations_bytes)); + values.push_back(q); + + m_countersNaptTable.set(key, values); +} + +void NatOrch::deleteNaptCounters(const string &protocol, const IpAddress &ipAddr, int l4_port) +{ + string protoStr = protocol.c_str(), ipStr = ipAddr.to_string().c_str(), portStr = std::to_string(l4_port); + string key = (protoStr + ":" + ipStr + ":" + portStr); + + m_countersNaptTable.del(key); +} + +void NatOrch::deleteTwiceNaptCounters(const TwiceNaptEntryKey &key) +{ + string naptKey = (key.prototype + ":" + key.src_ip.to_string() + ":" + std::to_string(key.src_l4_port) + + ":" + key.dst_ip.to_string() + ":" + std::to_string(key.dst_l4_port)); + + m_countersTwiceNaptTable.del(naptKey); +} + +void NatOrch::updateTwiceNatCounters(const TwiceNatEntryKey &key, + uint64_t nat_translations_pkts, uint64_t nat_translations_bytes) +{ + vector values; + string natKey = key.src_ip.to_string() + ":" + key.dst_ip.to_string(); + + swss::FieldValueTuple p("NAT_TRANSLATIONS_PKTS", to_string(nat_translations_pkts)); + values.push_back(p); + swss::FieldValueTuple q("NAT_TRANSLATIONS_BYTES", to_string(nat_translations_bytes)); + values.push_back(q); + + m_countersTwiceNatTable.set(natKey, values); +} + +void NatOrch::updateTwiceNaptCounters(const TwiceNaptEntryKey &key, + uint64_t nat_translations_pkts, uint64_t nat_translations_bytes) +{ + vector values; + string naptKey = (key.prototype + ":" + key.src_ip.to_string() + ":" + std::to_string(key.src_l4_port) + + ":" + key.dst_ip.to_string() + ":" + std::to_string(key.dst_l4_port)); + + swss::FieldValueTuple p("NAT_TRANSLATIONS_PKTS", to_string(nat_translations_pkts)); + values.push_back(p); + swss::FieldValueTuple q("NAT_TRANSLATIONS_BYTES", to_string(nat_translations_bytes)); + values.push_back(q); + + m_countersTwiceNaptTable.set(naptKey, values); +} + +bool NatOrch::checkIfNatEntryIsActive(const NatEntry::iterator &iter, time_t now) +{ + const IpAddress &ipAddr = iter->first; + NatEntryValue &entry = iter->second; + uint32_t attr_count; + IpAddress srcIp; + sai_attribute_t nat_entry_attr[4]; + sai_nat_entry_t snat_entry, dnat_entry; + sai_status_t status; + + if (entry.nat_type == "dnat") + { + /* Hitbits are queried for both directions when SNAT entry is checked */ + return 0; + } + + if (entry.addedToHw == false) + { + SWSS_LOG_DEBUG("Skip get hitbits for %s NAT entry [ip %s], as not yet added to HW", entry.nat_type.c_str(), ipAddr.to_string().c_str()); + return 0; + } + + if (entry.entry_type == "static") + { + /* Static NAT entries are always treated active */ + return 1; + } + + memset(nat_entry_attr, 0, sizeof(nat_entry_attr)); + nat_entry_attr[0].id = SAI_NAT_ENTRY_ATTR_HIT_BIT; /* Get the Hit bit */ + nat_entry_attr[0].value.booldata = 0; + nat_entry_attr[1].id = SAI_NAT_ENTRY_ATTR_HIT_BIT_COR; /* clear the hit bit after returning the value */ + nat_entry_attr[1].value.booldata = 1; + + attr_count = 2; + + memset(&snat_entry, 0, sizeof(snat_entry)); + + snat_entry.vr_id = gVirtualRouterId; + snat_entry.switch_id = gSwitchId; + srcIp = ipAddr; + snat_entry.data.key.src_ip = srcIp.getV4Addr(); + snat_entry.data.mask.src_ip = 0xffffffff; + + status = sai_nat_api->get_nat_entry_attribute(&snat_entry, attr_count, nat_entry_attr); + if (status == SAI_STATUS_SUCCESS) + { + SWSS_LOG_DEBUG("SNAT HIT BIT for src-ip %s = %d", srcIp.to_string().c_str(), + nat_entry_attr[0].value.booldata); + + if (nat_entry_attr[0].value.booldata) + { + entry.ageOutTime = now + timeout; + return 1; + } + else + { + auto dnatIter = m_natEntries.find(entry.translated_ip); + if (dnatIter == m_natEntries.end()) + { + return 0; + } + if ((dnatIter->second).addedToHw == false) + { + return 0; + } + + /* If SNAT HitBit is not set, check for the HitBit in the reverse direction */ + memset(&dnat_entry, 0, sizeof(dnat_entry)); + + dnat_entry.vr_id = gVirtualRouterId; + dnat_entry.switch_id = gSwitchId; + dnat_entry.data.key.dst_ip = entry.translated_ip.getV4Addr(); + dnat_entry.data.mask.dst_ip = 0xffffffff; + + status = sai_nat_api->get_nat_entry_attribute(&dnat_entry, attr_count, nat_entry_attr); + if (status == SAI_STATUS_SUCCESS) + { + SWSS_LOG_DEBUG("DNAT HIT BIT for dst-ip %s = %d", entry.translated_ip.to_string().c_str(), + nat_entry_attr[0].value.booldata); + if (nat_entry_attr[0].value.booldata) + { + entry.ageOutTime = now + timeout; + return 1; + } + } + } + } + return 0; +} + +bool NatOrch::checkIfNaptEntryIsActive(const NaptEntry::iterator &iter, time_t now) +{ + const NaptEntryKey &naptKey = iter->first; + NaptEntryValue &entry = iter->second; + int protoType = ((naptKey.prototype == "TCP") ? IPPROTO_TCP : IPPROTO_UDP); + uint32_t attr_count; + IpAddress srcIp; + uint16_t srcPort; + sai_attribute_t nat_entry_attr[4]; + sai_nat_entry_t snat_entry, dnat_entry; + sai_status_t status; + + if (entry.nat_type == "dnat") + { + /* Hitbits are queried for both directions when SNAT entry is checked */ + return 0; + } + + if (entry.addedToHw == false) + { + SWSS_LOG_DEBUG("Skip get hitbits for %s NAPT entry for [proto %s, ip %s, port %d], as not yet added to HW", + entry.nat_type.c_str(), naptKey.prototype.c_str(), naptKey.ip_address.to_string().c_str(), naptKey.l4_port); + return 0; + } + + if (entry.entry_type == "static") + { + /* Static NAPT entries are always treated active */ + return 1; + } + + memset(nat_entry_attr, 0, sizeof(nat_entry_attr)); + nat_entry_attr[0].id = SAI_NAT_ENTRY_ATTR_HIT_BIT; /* Get the Hit bit */ + nat_entry_attr[0].value.booldata = 0; + nat_entry_attr[1].id = SAI_NAT_ENTRY_ATTR_HIT_BIT_COR; /* clear the hit bit after returning the value */ + nat_entry_attr[1].value.booldata = 1; + + attr_count = 2; + + memset(&snat_entry, 0, sizeof(snat_entry)); + + snat_entry.vr_id = gVirtualRouterId; + snat_entry.switch_id = gSwitchId; + + srcIp = naptKey.ip_address; + srcPort = (uint16_t)(naptKey.l4_port); + snat_entry.data.key.src_ip = srcIp.getV4Addr(); + snat_entry.data.key.l4_src_port = srcPort; + + snat_entry.data.mask.src_ip = 0xffffffff; + snat_entry.data.mask.l4_src_port = 0xffff; + snat_entry.data.key.proto = (uint8_t)protoType; + snat_entry.data.mask.proto = 0xff; + + status = sai_nat_api->get_nat_entry_attribute(&snat_entry, attr_count, nat_entry_attr); + if (status == SAI_STATUS_SUCCESS) + { + SWSS_LOG_DEBUG("SNAPT HIT BIT for proto %s, src-ip %s, src-port %d = %d", naptKey.prototype.c_str(), + srcIp.to_string().c_str(), srcPort, nat_entry_attr[0].value.booldata); + if (nat_entry_attr[0].value.booldata) + { + entry.ageOutTime = now + ((protoType == IPPROTO_TCP) ? tcp_timeout : udp_timeout); + return 1; + } + else + { + NaptEntryKey dnaptKey; + dnaptKey.ip_address = entry.translated_ip; + dnaptKey.l4_port = entry.translated_l4_port; + dnaptKey.prototype = naptKey.prototype; + + auto dnaptIter = m_naptEntries.find(dnaptKey); + if (dnaptIter == m_naptEntries.end()) + { + return 0; + } + if ((dnaptIter->second).addedToHw == false) + { + return 0; + } + + + /* If SNAPT HitBit is not set, check for the HitBit in the reverse direction */ + memset(&dnat_entry, 0, sizeof(dnat_entry)); + + dnat_entry.vr_id = gVirtualRouterId; + dnat_entry.switch_id = gSwitchId; + + dnat_entry.data.key.dst_ip = entry.translated_ip.getV4Addr(); + dnat_entry.data.key.l4_dst_port = (uint16_t)(entry.translated_l4_port); + + dnat_entry.data.mask.dst_ip = 0xffffffff; + dnat_entry.data.mask.l4_dst_port = 0xffff; + dnat_entry.data.key.proto = (uint8_t)protoType; + dnat_entry.data.mask.proto = 0xff; + + status = sai_nat_api->get_nat_entry_attribute(&dnat_entry, attr_count, nat_entry_attr); + if (status == SAI_STATUS_SUCCESS) + { + SWSS_LOG_DEBUG("DNAPT HIT BIT for proto %s, dst-ip %s, dst-port %d = %d", naptKey.prototype.c_str(), + entry.translated_ip.to_string().c_str(), entry.translated_l4_port, nat_entry_attr[0].value.booldata); + if (nat_entry_attr[0].value.booldata) + { + entry.ageOutTime = now + ((protoType == IPPROTO_TCP) ? tcp_timeout : udp_timeout); + return 1; + } + } + } + } + return 0; +} + +bool NatOrch::checkIfTwiceNatEntryIsActive(const TwiceNatEntry::iterator &iter, time_t now) +{ + const TwiceNatEntryKey &key = iter->first; + TwiceNatEntryValue &entry = iter->second; + uint32_t attr_count; + sai_attribute_t nat_entry_attr[4]; + sai_nat_entry_t dbl_nat_entry; + sai_status_t status; + + if (entry.entry_type == "static") + { + /* Static NAPT entries are always treated active */ + return 1; + } + + if (entry.addedToHw == false) + { + SWSS_LOG_DEBUG("Skip get hitbits for Twice NAPT entry for [src ip %s, dst-ip %s], as not yet added to HW", + key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str()); + return 0; + } + + memset(nat_entry_attr, 0, sizeof(nat_entry_attr)); + nat_entry_attr[0].id = SAI_NAT_ENTRY_ATTR_HIT_BIT; /* Get the Hit bit */ + nat_entry_attr[0].value.booldata = 0; + nat_entry_attr[1].id = SAI_NAT_ENTRY_ATTR_HIT_BIT_COR; /* clear the hit bit after returning the value */ + nat_entry_attr[1].value.booldata = 1; + + attr_count = 2; + + memset(&dbl_nat_entry, 0, sizeof(dbl_nat_entry)); + + dbl_nat_entry.vr_id = gVirtualRouterId; + dbl_nat_entry.switch_id = gSwitchId; + dbl_nat_entry.data.key.src_ip = key.src_ip.getV4Addr(); + dbl_nat_entry.data.mask.src_ip = 0xffffffff; + dbl_nat_entry.data.key.dst_ip = key.dst_ip.getV4Addr(); + dbl_nat_entry.data.mask.dst_ip = 0xffffffff; + + status = sai_nat_api->get_nat_entry_attribute(&dbl_nat_entry, attr_count, nat_entry_attr); + if (status == SAI_STATUS_SUCCESS) + { + SWSS_LOG_DEBUG("Twice NAT HIT BIT for src-ip %s, dst-ip %s = %d", + key.src_ip.to_string().c_str(), key.dst_ip.to_string().c_str(), nat_entry_attr[0].value.booldata); + if (nat_entry_attr[0].value.booldata) + { + entry.ageOutTime = now + timeout; + return 1; + } + } + return 0; +} + +bool NatOrch::checkIfTwiceNaptEntryIsActive(const TwiceNaptEntry::iterator &iter, time_t now) +{ + const TwiceNaptEntryKey &key = iter->first; + TwiceNaptEntryValue &entry = iter->second; + uint8_t protoType = ((key.prototype == "TCP") ? IPPROTO_TCP : IPPROTO_UDP); + uint32_t attr_count; + sai_attribute_t nat_entry_attr[4]; + sai_nat_entry_t dbl_nat_entry; + sai_status_t status; + + if (entry.addedToHw == false) + { + SWSS_LOG_DEBUG("Skip get hitbits for Twice NAPT entry for [proto %s, src ip %s, src port %d, dst ip %s, dst port %d], as not yet added to HW", + key.prototype.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), key.dst_l4_port); + return 0; + } + + if (entry.entry_type == "static") + { + /* Static NAPT entries are always treated active */ + return 1; + } + + memset(nat_entry_attr, 0, sizeof(nat_entry_attr)); + nat_entry_attr[0].id = SAI_NAT_ENTRY_ATTR_HIT_BIT; /* Get the Hit bit */ + nat_entry_attr[0].value.booldata = 0; + nat_entry_attr[1].id = SAI_NAT_ENTRY_ATTR_HIT_BIT_COR; /* clear the hit bit after returning the value */ + nat_entry_attr[1].value.booldata = 1; + + attr_count = 2; + + memset(&dbl_nat_entry, 0, sizeof(dbl_nat_entry)); + + dbl_nat_entry.vr_id = gVirtualRouterId; + dbl_nat_entry.switch_id = gSwitchId; + dbl_nat_entry.data.key.src_ip = key.src_ip.getV4Addr(); + dbl_nat_entry.data.mask.src_ip = 0xffffffff; + dbl_nat_entry.data.key.l4_src_port = (uint16_t)(key.src_l4_port); + dbl_nat_entry.data.mask.l4_src_port = 0xffff; + dbl_nat_entry.data.key.dst_ip = key.dst_ip.getV4Addr(); + dbl_nat_entry.data.mask.dst_ip = 0xffffffff; + dbl_nat_entry.data.key.l4_dst_port = (uint16_t)(key.dst_l4_port); + dbl_nat_entry.data.mask.l4_dst_port = 0xffff; + dbl_nat_entry.data.key.proto = protoType; + dbl_nat_entry.data.mask.proto = 0xff; + + status = sai_nat_api->get_nat_entry_attribute(&dbl_nat_entry, attr_count, nat_entry_attr); + if (status == SAI_STATUS_SUCCESS) + { + SWSS_LOG_DEBUG("Twice NAPT HIT BIT for [proto %s, src ip %s, src port %d, dst ip %s, dst port %d] = %d", + key.prototype.c_str(), key.src_ip.to_string().c_str(), key.src_l4_port, key.dst_ip.to_string().c_str(), key.dst_l4_port, + nat_entry_attr[0].value.booldata); + if (nat_entry_attr[0].value.booldata) + { + entry.ageOutTime = now + ((protoType == IPPROTO_TCP) ? tcp_timeout : udp_timeout); + return 1; + } + } + return 0; +} + +void NatOrch::updateConnTrackTimeout(string prototype) +{ + if (prototype == "all") + { + NatEntry::iterator natIter = m_natEntries.begin(); + while (natIter != m_natEntries.end()) + { + if (((*natIter).second.addedToHw == true) and + ((*natIter).second.nat_type == "snat")) + { + updateConnTrackTimeout((*natIter).first); + } + natIter++; + } + TwiceNatEntry::iterator tnatIter = m_twiceNatEntries.begin(); + while (tnatIter != m_twiceNatEntries.end()) + { + if ((*tnatIter).second.addedToHw == true) + { + updateConnTrackTimeout((*tnatIter).first); + } + tnatIter++; + } + } + else + { + std::string res; + std::string cmds = std::string("") + CONNTRACK + UPDATE; + int timeout = ((prototype == "tcp") ? tcp_timeout : udp_timeout); + + cmds += (" -p " + prototype + " -t " + to_string(timeout) + REDIRECT_TO_DEV_NULL); + swss::exec(cmds, res); + + SWSS_LOG_INFO("Updated the %s NAT conntrack entries timeout to %d", prototype.c_str(), timeout); + } +} + +void NatOrch::updateConnTrackTimeout(const IpAddress &sourceIpAddr) +{ + std::string res; + std::string cmds = std::string("") + CONNTRACK + UPDATE; + + cmds += (" -s " + sourceIpAddr.to_string() + " -t " + to_string(timeout) + REDIRECT_TO_DEV_NULL); + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Updated the active NAT conntrack entry with src-ip %s, timeout %u", + sourceIpAddr.to_string().c_str(), timeout); + } +} + +void NatOrch::updateConnTrackTimeout(const NaptEntryKey &entry) +{ + uint8_t protoType = ((entry.prototype == string("TCP")) ? IPPROTO_TCP : IPPROTO_UDP); + + std::string res; + std::string prototype = ((entry.prototype == string("TCP")) ? "tcp" : "udp"); + int timeout = ((protoType == IPPROTO_TCP) ? tcp_timeout : udp_timeout); + std::string cmds = std::string("") + CONNTRACK + UPDATE; + + cmds += (" -s " + entry.ip_address.to_string() + " -p " + prototype + " --orig-port-src " + to_string(entry.l4_port) + " -t " + to_string(timeout) + REDIRECT_TO_DEV_NULL); + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Updated active NAPT conntrack entry with protocol %s, src-ip %s, src-port %d, timeout %u", + entry.prototype.c_str(), entry.ip_address.to_string().c_str(), + entry.l4_port, ((protoType == IPPROTO_TCP) ? tcp_timeout : udp_timeout)); + } +} + +void NatOrch::updateConnTrackTimeout(const TwiceNatEntryKey &entry) +{ + std::string res; + std::string cmd = std::string("") + CONNTRACK + UPDATE; + + TwiceNatEntryValue value = m_twiceNatEntries[entry]; + + cmd += (" -s " + entry.src_ip.to_string() + " -d " + entry.dst_ip.to_string() + " -t " + std::to_string(timeout) + REDIRECT_TO_DEV_NULL); + + swss::exec(cmd, res); + + SWSS_LOG_INFO("Updated active Twice NAT conntrack entry with src-ip %s, dst-ip %s, timeout %u", + entry.src_ip.to_string().c_str(), entry.dst_ip.to_string().c_str(), timeout); +} + +void NatOrch::updateConnTrackTimeout(const TwiceNaptEntryKey &entry) +{ + uint8_t protoType = ((entry.prototype == string("TCP")) ? IPPROTO_TCP : IPPROTO_UDP); + + std::string res; + std::string prototype = ((entry.prototype == string("TCP")) ? "tcp" : "udp"); + int timeout = ((protoType == IPPROTO_TCP) ? tcp_timeout : udp_timeout); + std::string cmd = std::string("") + CONNTRACK + UPDATE; + + TwiceNaptEntryValue value = m_twiceNaptEntries[entry]; + + cmd += (" -s " + entry.src_ip.to_string() + " -p " + prototype + " --orig-port-src " + to_string(entry.src_l4_port) + + " -d " + entry.dst_ip.to_string() + " --orig-port-dst " + std::to_string(entry.dst_l4_port) + + " -t " + std::to_string(timeout) + REDIRECT_TO_DEV_NULL); + + swss::exec(cmd, res); + + SWSS_LOG_INFO("Updated active Twice NAPT conntrack entry with protocol %s, src-ip %s, src-port %d, dst-ip %s, dst-port %d, timeout %u", + entry.prototype.c_str(), entry.src_ip.to_string().c_str(), entry.src_l4_port, + entry.dst_ip.to_string().c_str(), entry.dst_l4_port, ((protoType == IPPROTO_TCP) ? tcp_timeout : udp_timeout)); +} + +void NatOrch::addConnTrackEntry(const IpAddress &ipAddr) +{ + std::string res; + std::string cmds = std::string("") + CONNTRACK + ADD; + NatEntryValue entry = m_natEntries[ipAddr]; + + cmds += (" -n " + entry.translated_ip.to_string() + ":1 -g 127.0.0.1:127" + " -p udp -t " + to_string(timeout) + + " --src " + ipAddr.to_string() + " --sport 1 --dst 127.0.0.1 --dport 127 -u ASSURED "); + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Added static NAT conntrack entry with src-ip %s, timeout %u", + ipAddr.to_string().c_str(), timeout); + } +} + +void NatOrch::addConnTrackEntry(const NaptEntryKey &keyEntry) +{ + std::string cmds = std::string("") + CONNTRACK + ADD; + std::string res, prototype, state; + int timeout = 0; + NaptEntryValue entry = m_naptEntries[keyEntry]; + + if (keyEntry.prototype == string("TCP")) + { + prototype = "tcp"; + timeout = tcp_timeout; + state = " --state ESTABLISHED "; + } + else + { + prototype = "udp"; + timeout = udp_timeout; + state = ""; + } + + cmds += (" -n " + entry.translated_ip.to_string() + ":" + to_string(entry.translated_l4_port) + " -g 127.0.0.1:127" + " -p " + prototype + " -t " + to_string(timeout) + + " --src " + keyEntry.ip_address.to_string() + " --sport " + to_string(keyEntry.l4_port) + " --dst 127.0.0.1 --dport 127 -u ASSURED " + state); + int ret = swss::exec(cmds, res); + + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmds.c_str(), ret); + } + else + { + SWSS_LOG_INFO("Added static NAPT conntrack entry with protocol %s, src-ip %s, src-port %d, timeout %u", + keyEntry.prototype.c_str(), keyEntry.ip_address.to_string().c_str(), + keyEntry.l4_port, (keyEntry.prototype == string("TCP") ? tcp_timeout : udp_timeout)); + } +} + +void NatOrch::addConnTrackEntry(const TwiceNatEntryKey &keyEntry) +{ + std::string res; + std::string cmds = std::string("") + CONNTRACK + ADD; + TwiceNatEntryValue entry = m_twiceNatEntries[keyEntry]; + + cmds += (" -n " + entry.translated_src_ip.to_string() + ":1" + " -g " + entry.translated_dst_ip.to_string() + + ":1" + " -p udp" + " -t " + to_string(timeout) + + " --src " + keyEntry.src_ip.to_string() + " --sport 1" + " --dst " + keyEntry.dst_ip.to_string() + + " --dport 1" + " -u ASSURED " + REDIRECT_TO_DEV_NULL); + + swss::exec(cmds, res); + + SWSS_LOG_INFO("Added Static Twice NAT conntrack entry with src-ip %s, dst-ip %s, timeout %u", + keyEntry.src_ip.to_string().c_str(), keyEntry.dst_ip.to_string().c_str(), timeout); +} + +void NatOrch::addConnTrackEntry(const TwiceNaptEntryKey &keyEntry) +{ + std::string cmds = std::string("") + CONNTRACK + ADD; + std::string res, prototype, state; + int timeout = 0; + TwiceNaptEntryValue entry = m_twiceNaptEntries[keyEntry]; + + if (keyEntry.prototype == string("TCP")) + { + prototype = "tcp"; + timeout = tcp_timeout; + state = " --state ESTABLISHED "; + } + else + { + prototype = "udp"; + timeout = udp_timeout; + state = ""; + } + + cmds += (" -n " + entry.translated_src_ip.to_string() + ":" + to_string(entry.translated_src_l4_port) + " -g " + entry.translated_dst_ip.to_string() + + ":" + to_string(entry.translated_dst_l4_port) + " -p " + prototype + " -t " + to_string(timeout) + + " --src " + keyEntry.src_ip.to_string() + " --sport " + to_string(keyEntry.src_l4_port) + " --dst " + keyEntry.dst_ip.to_string() + + " --dport " + to_string(keyEntry.dst_l4_port) + " -u ASSURED " + state + REDIRECT_TO_DEV_NULL); + + swss::exec(cmds, res); + + SWSS_LOG_INFO("Added static Twice NAPT conntrack entry with protocol %s, src-ip %s, src-port %d, dst-ip %s, dst-port %d, timeout %u", + keyEntry.prototype.c_str(), keyEntry.src_ip.to_string().c_str(), keyEntry.src_l4_port, + keyEntry.dst_ip.to_string().c_str(), keyEntry.dst_l4_port, (keyEntry.prototype == string("TCP") ? tcp_timeout : udp_timeout)); + +} + +void NatOrch::doTask(NotificationConsumer& consumer) +{ + SWSS_LOG_ENTER(); + + std::string op; + std::string data; + std::vector values; + + unique_lock lock(m_natMutex); + + consumer.pop(op, data, values); + + if (&consumer == m_flushNotificationsConsumer) + { + if ((op == "ENTRIES") and (data == "ALL")) + { + SWSS_LOG_INFO("Received All Entries notification"); + flushAllNatEntries(); + addAllStaticConntrackEntries(); + } + else if ((op == "STATISTICS") and (data == "ALL")) + { + SWSS_LOG_INFO("Received All Statistics notification"); + clearCounters(); + } + else + { + SWSS_LOG_ERROR("Received unknown flush nat request"); + } + } + else if (&consumer == m_cleanupNotificationConsumer) + { + SWSS_LOG_NOTICE("Received RedisDB and ASIC cleanup notification on NAT docker stop"); + cleanupAppDbEntries(); + } +} + +void NatOrch::updateStaticNatCounters(int count) +{ + std::vector values; + std::string key = "Values"; + + swss::FieldValueTuple p("STATIC_NAT_ENTRIES", to_string(count)); + values.push_back(p); + + m_countersGlobalNatTable.set(key, values); +} + +void NatOrch::updateStaticNaptCounters(int count) +{ + std::vector values; + std::string key = "Values"; + + swss::FieldValueTuple p("STATIC_NAPT_ENTRIES", to_string(count)); + values.push_back(p); + + m_countersGlobalNatTable.set(key, values); +} + +void NatOrch::updateStaticTwiceNatCounters(int count) +{ + std::vector values; + std::string key = "Values"; + + swss::FieldValueTuple p("STATIC_TWICE_NAT_ENTRIES", to_string(count)); + values.push_back(p); + + m_countersGlobalNatTable.set(key, values); +} + +void NatOrch::updateStaticTwiceNaptCounters(int count) +{ + std::vector values; + std::string key = "Values"; + + swss::FieldValueTuple p("STATIC_TWICE_NAPT_ENTRIES", to_string(count)); + values.push_back(p); + + m_countersGlobalNatTable.set(key, values); +} + +void NatOrch::updateDynamicNatCounters(int count) +{ + std::vector values; + std::string key = "Values"; + + swss::FieldValueTuple p("DYNAMIC_NAT_ENTRIES", to_string(count)); + values.push_back(p); + + m_countersGlobalNatTable.set(key, values); +} + +void NatOrch::updateDynamicNaptCounters(int count) +{ + std::vector values; + std::string key = "Values"; + + swss::FieldValueTuple p("DYNAMIC_NAPT_ENTRIES", to_string(count)); + values.push_back(p); + + m_countersGlobalNatTable.set(key, values); +} + +void NatOrch::updateDynamicTwiceNatCounters(int count) +{ + std::vector values; + std::string key = "Values"; + + swss::FieldValueTuple p("DYNAMIC_TWICE_NAT_ENTRIES", to_string(count)); + values.push_back(p); + + m_countersGlobalNatTable.set(key, values); +} + +void NatOrch::updateDynamicTwiceNaptCounters(int count) +{ + std::vector values; + std::string key = "Values"; + + swss::FieldValueTuple p("DYNAMIC_TWICE_NAPT_ENTRIES", to_string(count)); + values.push_back(p); + + m_countersGlobalNatTable.set(key, values); +} + +void NatOrch::updateSnatCounters(int count) +{ + std::vector values; + std::string key = "Values"; + + swss::FieldValueTuple p("SNAT_ENTRIES", to_string(count)); + values.push_back(p); + + m_countersGlobalNatTable.set(key, values); +} + +void NatOrch::updateDnatCounters(int count) +{ + std::vector values; + std::string key = "Values"; + + swss::FieldValueTuple p("DNAT_ENTRIES", to_string(count)); + values.push_back(p); + + m_countersGlobalNatTable.set(key, values); +} + +#ifdef DEBUG_FRAMEWORK +/* Dump all internal operational information */ +bool NatOrch::debugdumpCLI(KeyOpFieldsValuesTuple t) +{ + SWSS_LOG_NOTICE("debugdumpcli called"); + debugdumpALL(); + + return true; +} + +void NatOrch::debugdumpALL() +{ + int count = 0; + IpAddress ipAddr; + NatEntryValue value; + NaptEntryKey naptKey; + NaptEntryValue naptValue; + TwiceNatEntryKey twiceNatKey; + TwiceNatEntryValue twiceNatValue; + TwiceNaptEntryKey twiceNaptKey; + TwiceNaptEntryValue twiceNaptValue; + struct timespec time_now; + + SWSS_LOG_ENTER(); + + unique_lock lock(m_natMutex); + + if (clock_gettime (CLOCK_MONOTONIC, &time_now) < 0) + { + return; + } + + SWSS_LOG_NOTICE("debugdumpall called"); + SWSS_DEBUG_PRINT(m_dbgCompName, "--- NatOrch Dump All Start --->"); + + SWSS_DEBUG_PRINT(m_dbgCompName, "\nNatOrch Internal values"); + SWSS_DEBUG_PRINT(m_dbgCompName, "-----------------------"); + SWSS_DEBUG_PRINT(m_dbgCompName, " Admin Mode : %s", admin_mode.c_str()); + SWSS_DEBUG_PRINT(m_dbgCompName, " Timeout : %d", timeout); + SWSS_DEBUG_PRINT(m_dbgCompName, " TCP timeout : %d", tcp_timeout); + SWSS_DEBUG_PRINT(m_dbgCompName, " UDP timeout : %d", udp_timeout); + SWSS_DEBUG_PRINT(m_dbgCompName, " Total Entries : %d", totalEntries); + SWSS_DEBUG_PRINT(m_dbgCompName, " Total Static Nat Entries : %d", totalStaticNatEntries); + SWSS_DEBUG_PRINT(m_dbgCompName, " Total Dynamic Nat Entries : %d", totalDynamicNatEntries); + SWSS_DEBUG_PRINT(m_dbgCompName, " Total Static Napt Entries : %d", totalStaticNaptEntries); + SWSS_DEBUG_PRINT(m_dbgCompName, " Total Dynamic Napt Entries : %d", totalDynamicNaptEntries); + SWSS_DEBUG_PRINT(m_dbgCompName, " Total Static Twice Nat Entries : %d", totalStaticTwiceNatEntries); + SWSS_DEBUG_PRINT(m_dbgCompName, " Total Dynamic Twice Nat Entries : %d", totalDynamicTwiceNatEntries); + SWSS_DEBUG_PRINT(m_dbgCompName, " Total Static Twice Napt Entries : %d", totalStaticTwiceNaptEntries); + SWSS_DEBUG_PRINT(m_dbgCompName, " Total Dynamic Twice Napt Entries: %d", totalDynamicTwiceNaptEntries); + SWSS_DEBUG_PRINT(m_dbgCompName, " Total Snat Entries : %d", totalSnatEntries); + SWSS_DEBUG_PRINT(m_dbgCompName, " Total Dnat Entries : %d", totalDnatEntries); + SWSS_DEBUG_PRINT(m_dbgCompName, " Max allowed NAT entries : %d", maxAllowedSNatEntries); + + SWSS_DEBUG_PRINT(m_dbgCompName, "\n\nNatOrch NAT entries Cache"); + SWSS_DEBUG_PRINT(m_dbgCompName, "--------------------------"); + + auto natIter = m_natEntries.begin(); + while (natIter != m_natEntries.end()) + { + ipAddr = natIter->first; + value = natIter->second; + count++; + SWSS_DEBUG_PRINT(m_dbgCompName, "%8d. IP: %s", count, ipAddr.to_string().c_str()); + SWSS_DEBUG_PRINT(m_dbgCompName, " Translated IP: %s, NAT Type: %s, Entry Type: %s", + value.translated_ip.to_string().c_str(), value.nat_type.c_str(), value.entry_type.c_str()); + SWSS_DEBUG_PRINT(m_dbgCompName, " Age-out time: %ld secs, Added-to-Hw: %s", + (value.ageOutTime - time_now.tv_sec), ((value.addedToHw) ? "Yes" : "No")); + natIter++; + } + count = 0; + SWSS_DEBUG_PRINT(m_dbgCompName, "\n\nNatOrch NAPT entries Cache"); + SWSS_DEBUG_PRINT(m_dbgCompName, "--------------------------"); + + auto naptIter = m_naptEntries.begin(); + while (naptIter != m_naptEntries.end()) + { + naptKey = naptIter->first; + naptValue = naptIter->second; + count++; + SWSS_DEBUG_PRINT(m_dbgCompName, "%8d. IP: %s, L4 Port: %d, Proto: %s", count, + naptKey.ip_address.to_string().c_str(), naptKey.l4_port, naptKey.prototype.c_str()); + SWSS_DEBUG_PRINT(m_dbgCompName, " Translated IP: %s, L4 Port: %d, NAT Type: %s, Entry Type: %s", + naptValue.translated_ip.to_string().c_str(), naptValue.translated_l4_port, + naptValue.nat_type.c_str(), naptValue.entry_type.c_str()); + SWSS_DEBUG_PRINT(m_dbgCompName, " Age-out time: %ld secs, Added-to-Hw: %s", + (naptValue.ageOutTime - time_now.tv_sec), ((naptValue.addedToHw) ? "Yes" : "No")); + naptIter++; + } + count = 0; + + SWSS_DEBUG_PRINT(m_dbgCompName, "\n\nNatOrch Twice NAT entries Cache"); + SWSS_DEBUG_PRINT(m_dbgCompName, "-------------------------------"); + + auto twiceNatIter = m_twiceNatEntries.begin(); + while (twiceNatIter != m_twiceNatEntries.end()) + { + twiceNatKey = twiceNatIter->first; + twiceNatValue = twiceNatIter->second; + count++; + SWSS_DEBUG_PRINT(m_dbgCompName, "%8d. Src IP: %s, Dst IP: %s", count, + twiceNatKey.src_ip.to_string().c_str(), twiceNatKey.dst_ip.to_string().c_str()); + SWSS_DEBUG_PRINT(m_dbgCompName, " Translated Src IP: %s, Dst IP: %s, Entry Type: %s", + twiceNatValue.translated_src_ip.to_string().c_str(), twiceNatValue.translated_dst_ip.to_string().c_str(), + twiceNatValue.entry_type.c_str()); + SWSS_DEBUG_PRINT(m_dbgCompName, " Age-out time: %ld secs, Added-to-Hw: %s", + (twiceNatValue.ageOutTime - time_now.tv_sec), ((twiceNatValue.addedToHw) ? "Yes" : "No")); + twiceNatIter++; + } + count = 0; + + SWSS_DEBUG_PRINT(m_dbgCompName, "\n\nNatOrch Twice NAPT entries Cache"); + SWSS_DEBUG_PRINT(m_dbgCompName, "--------------------------------"); + + auto twiceNaptIter = m_twiceNaptEntries.begin(); + while (twiceNaptIter != m_twiceNaptEntries.end()) + { + twiceNaptKey = twiceNaptIter->first; + twiceNaptValue = twiceNaptIter->second; + count++; + SWSS_DEBUG_PRINT(m_dbgCompName, "%8d. Src IP: %s, L4 Port: %d, Dst IP: %s, L4 Port: %d, Proto: %s", count, + twiceNaptKey.src_ip.to_string().c_str(), twiceNaptKey.src_l4_port, twiceNaptKey.dst_ip.to_string().c_str(), + twiceNaptKey.dst_l4_port, twiceNaptKey.prototype.c_str()); + SWSS_DEBUG_PRINT(m_dbgCompName, " Translated Src IP: %s, L4 Port: %d, Dst IP: %s, L4 Port: %d, Entry Type: %s", + twiceNaptValue.translated_src_ip.to_string().c_str(), twiceNaptValue.translated_src_l4_port, + twiceNaptValue.translated_dst_ip.to_string().c_str(), twiceNaptValue.translated_dst_l4_port, + twiceNaptValue.entry_type.c_str()); + SWSS_DEBUG_PRINT(m_dbgCompName, " Age-out time: %ld secs, Added-to-Hw: %s", + (twiceNaptValue.ageOutTime - time_now.tv_sec), ((twiceNaptValue.addedToHw) ? "Yes" : "No")); + twiceNaptIter++; + } + count = 0; + + if (gNhTrackingSupported == true) + { + SWSS_DEBUG_PRINT(m_dbgCompName, "\n\nNatOrch Dump NextHop resolution entries Cache"); + SWSS_DEBUG_PRINT(m_dbgCompName, "---------------------------------------------"); + + auto dnatNhIter = m_nhResolvCache.begin(); + while (dnatNhIter != m_nhResolvCache.end()) + { + ipAddr = dnatNhIter->first; + DnatEntries &dnatEntries = dnatNhIter->second; + count++; + SWSS_DEBUG_PRINT(m_dbgCompName, "%8d. Translated DNAT IP: %s, neighResolved: %d", count, + ipAddr.to_string().c_str(), dnatEntries.neighResolved); + if (dnatEntries.nextHopGroup != NextHopGroupKey()) + { + SWSS_DEBUG_PRINT(m_dbgCompName, " NextHop Group: %s", dnatEntries.nextHopGroup.to_string().c_str()); + } + if (dnatEntries.dnatIp != nullIpv4Addr) + { + SWSS_DEBUG_PRINT(m_dbgCompName, " DNAT Entry Key: DIP %s", dnatEntries.dnatIp.to_string().c_str()); + } + if (! dnatEntries.dnapt.empty()) + { + auto iter1 = dnatEntries.dnapt.begin(); + while (iter1 != dnatEntries.dnapt.end()) + { + SWSS_DEBUG_PRINT(m_dbgCompName, " DNAPT entry Key: DIP %s, Port %d, Proto %s", + (*iter1).ip_address.to_string().c_str(), (*iter1).l4_port, (*iter1).prototype.c_str()); + iter1++; + } + } + if (! dnatEntries.twiceNat.empty()) + { + auto iter2 = dnatEntries.twiceNat.begin(); + while (iter2 != dnatEntries.twiceNat.end()) + { + SWSS_DEBUG_PRINT(m_dbgCompName, " Twice NAT entry key: Src IP: %s, Dst IP: %s", + (*iter2).src_ip.to_string().c_str(), (*iter2).dst_ip.to_string().c_str()); + iter2++; + } + } + if (! dnatEntries.twiceNapt.empty()) + { + auto iter3 = dnatEntries.twiceNapt.begin(); + while (iter3 != dnatEntries.twiceNapt.end()) + { + SWSS_DEBUG_PRINT(m_dbgCompName, " Twice NAPT entry key: Src IP: %s, L4 Port: %d, Dst IP: %s, L4 Port: %d, Proto: %s", + (*iter3).src_ip.to_string().c_str(), (*iter3).src_l4_port, + (*iter3).dst_ip.to_string().c_str(), (*iter3).dst_l4_port, (*iter3).prototype.c_str()); + iter3++; + } + } + dnatNhIter++; + } + } +} +#endif diff --git a/orchagent/natorch.h b/orchagent/natorch.h new file mode 100644 index 0000000000..b0fb88cd40 --- /dev/null +++ b/orchagent/natorch.h @@ -0,0 +1,349 @@ +/* + * Copyright 2019 Broadcom Inc. + * + * 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. + */ + +#ifndef SWSS_NATORCH_H +#define SWSS_NATORCH_H + +#include "orch.h" +#include "observer.h" +#include "portsorch.h" +#include "intfsorch.h" +#include "ipaddress.h" +#include "ipaddresses.h" +#include "ipprefix.h" +#include "nfnetlink.h" +#include "timer.h" +#include "routeorch.h" +#include "nexthopgroupkey.h" +#ifdef DEBUG_FRAMEWORK +#include "debugdumporch.h" +#endif + +#define VALUES "Values" // Global Values Key +#define NAT_HITBIT_N_CNTRS_QUERY_PERIOD 5 // 5 secs +#define NAT_HITBIT_QUERY_MULTIPLE 6 // Hit bits are queried every 30 secs +#define CONNTRACK "/usr/sbin/conntrack" +#define REDIRECT_TO_DEV_NULL " &> /dev/null" +#define FLUSH " -F" +#define UPDATE " -U" +#define DELETE " -D" +#define ADD " -I" + +struct NatEntryValue +{ + IpAddress translated_ip; // Translated IP address + string nat_type; // Nat Type - SNAT or DNAT + string entry_type; // Entry type - Static or Dynamic + time_t ageOutTime; // Timestamp in secs when the entry expires + bool addedToHw; // Boolean to represent added to hardware + + bool operator<(const NatEntryValue& other) const + { + return tie(translated_ip, nat_type, entry_type) < tie(other.translated_ip, other.nat_type, other.entry_type); + } +}; + +struct NaptEntryKey +{ + IpAddress ip_address; // IP address + int l4_port; // Port address + string prototype; // Prototype - TCP or UDP + + bool operator<(const NaptEntryKey& other) const + { + return tie(ip_address, l4_port, prototype) < tie(other.ip_address, other.l4_port, other.prototype); + } +}; + +struct NaptEntryValue +{ + IpAddress translated_ip; // Translated IP address + int translated_l4_port; // Translated port address + string nat_type; // Nat Type - SNAT or DNAT + string entry_type; // Entry type - Static or Dynamic + time_t ageOutTime; // Timestamp in secs when the entry expires + bool addedToHw; // Boolean to represent added to hardware + + bool operator<(const NaptEntryValue& other) const + { + return tie(translated_ip, translated_l4_port, nat_type, entry_type) < tie(other.translated_ip, other.translated_l4_port, other.nat_type, other.entry_type); + } +}; + +struct TwiceNatEntryKey +{ + IpAddress src_ip; + IpAddress dst_ip; + + bool operator<(const TwiceNatEntryKey& other) const + { + return tie(src_ip, dst_ip) < tie(other.src_ip, other.dst_ip); + } +}; + +struct TwiceNatEntryValue +{ + IpAddress translated_src_ip; + IpAddress translated_dst_ip; + string entry_type; // Entry type - Static or Dynamic + time_t ageOutTime; // Timestamp in secs when the entry expires + bool addedToHw; // Boolean to represent added to hardware + + bool operator<(const TwiceNatEntryValue& other) const + { + return tie(translated_src_ip, translated_dst_ip, entry_type) < tie(other.translated_src_ip, other.translated_dst_ip, other.entry_type); + } +}; + +struct TwiceNaptEntryKey +{ + IpAddress src_ip; + int src_l4_port; + IpAddress dst_ip; + int dst_l4_port; + string prototype; + + bool operator<(const TwiceNaptEntryKey& other) const + { + return tie(src_ip, src_l4_port, dst_ip, dst_l4_port, prototype) < tie(other.src_ip, other.src_l4_port, other.dst_ip, other.dst_l4_port, other.prototype); + } +}; + +struct TwiceNaptEntryValue +{ + IpAddress translated_src_ip; + int translated_src_l4_port; + IpAddress translated_dst_ip; + int translated_dst_l4_port; + string entry_type; // Entry type - Static or Dynamic + time_t ageOutTime; // Timestamp in secs when the entry expires + bool addedToHw; // Boolean to represent added to hardware + + bool operator<(const TwiceNaptEntryValue& other) const + { + return tie(translated_src_ip, translated_src_l4_port, translated_dst_ip, translated_dst_l4_port, entry_type) < + tie(other.translated_src_ip, other.translated_src_l4_port, other.translated_dst_ip, other.translated_dst_l4_port, other.entry_type); + } +}; + +/* Here Key is IpAddress (global address) and + * NatEntryValue contains translated values (local address, nat_type, entry_type) + */ +typedef std::map NatEntry; + +/* Here Key is NaptEntryKey (global address, global port and prototype) + * NaptEntryValue contains translated values (local address, local port, nat_type and entry_type) + */ +typedef std::map NaptEntry; + +typedef std::map TwiceNatEntry; + +typedef std::map TwiceNaptEntry; + +/* Cache of DNAT entries that are dependent on the + * nexthop resolution of the translated destination ip address. + */ +typedef std::set DnaptCache; +typedef std::set TwiceNatCache; +typedef std::set TwiceNaptCache; + +struct DnatEntries +{ + IpAddress dnatIp; /* NAT entry cache */ + DnaptCache dnapt; /* NAPT entries cache */ + TwiceNatCache twiceNat; /* Twice NAT entries cache */ + TwiceNaptCache twiceNapt; /* Twice NAPT entries cache */ + + NextHopGroupKey nextHopGroup; + bool neighResolved; +}; + +typedef std::map DnatNhResolvCache; + +class NatOrch: public Orch, public Subject, public Observer +{ +public: + + NatOrch(DBConnector *appDb, DBConnector *stateDb, vector &tableNames, RouteOrch *routeOrch, NeighOrch *neighOrch); + + ~NatOrch() + { + // do nothing + } + + void update(SubjectType, void *); + bool debugdumpCLI(KeyOpFieldsValuesTuple t); + void debugdumpALL(); + + NeighOrch *m_neighOrch; + RouteOrch *m_routeOrch; + +private: + + /* Netfilter socket to delete conntrack entries corresponding to aged out NAT entries */ + NfNetlink nfnl; + NatEntry m_natEntries; + NaptEntry m_naptEntries; + TwiceNatEntry m_twiceNatEntries; + TwiceNaptEntry m_twiceNaptEntries; + SelectableTimer *m_natQueryTimer; + DBConnector m_countersDb; + Table m_countersNatTable; + Table m_countersNaptTable; + Table m_countersTwiceNatTable; + Table m_countersTwiceNaptTable; + Table m_countersGlobalNatTable; + Table m_stateWarmRestartEnableTable; + Table m_stateWarmRestartTable; + Table m_natQueryTable; + Table m_naptQueryTable; + Table m_twiceNatQueryTable; + Table m_twiceNaptQueryTable; + NotificationConsumer *m_flushNotificationsConsumer; + NotificationConsumer *m_cleanupNotificationConsumer; + mutex m_natMutex; + string m_dbgCompName; + IpAddress nullIpv4Addr; + + /* DNAT/DNAPT entry is cached, to delete and re-add it whenever the direct NextHop (connected neighbor) + * or indirect NextHop (via route) to reach the DNAT IP is changed. */ + DnatNhResolvCache m_nhResolvCache; + + int timeout; + int tcp_timeout; + int udp_timeout; + int totalEntries; + int totalStaticNatEntries; + int totalDynamicNatEntries; + int totalStaticTwiceNatEntries; + int totalDynamicTwiceNatEntries; + int totalStaticNaptEntries; + int totalDynamicNaptEntries; + int totalStaticTwiceNaptEntries; + int totalDynamicTwiceNaptEntries; + int totalSnatEntries; + int totalDnatEntries; + int maxAllowedSNatEntries; + string admin_mode; + + void doTask(Consumer& consumer); + void doTask(SelectableTimer &timer); + void doTask(NotificationConsumer& consumer); + void doNatTableTask(Consumer& consumer); + void doNaptTableTask(Consumer& consumer); + void doTwiceNatTableTask(Consumer& consumer); + void doTwiceNaptTableTask(Consumer& consumer); + void doNatGlobalTableTask(Consumer& consumer); + + bool addNatEntry(const IpAddress &ip_address, const NatEntryValue &entry); + bool removeNatEntry(const IpAddress &ip_address); + bool addNaptEntry(const NaptEntryKey &keyEntry, const NaptEntryValue &entry); + bool removeNaptEntry(const NaptEntryKey &keyEntry); + + bool addTwiceNatEntry(const TwiceNatEntryKey &key, const TwiceNatEntryValue &value); + bool removeTwiceNatEntry(const TwiceNatEntryKey &key); + bool addTwiceNaptEntry(const TwiceNaptEntryKey &key, const TwiceNaptEntryValue &value); + bool removeTwiceNaptEntry(const TwiceNaptEntryKey &key); + + void updateNextHop(const NextHopUpdate& update); + void updateNeighbor(const NeighborUpdate& update); + bool isNextHopResolved(const NextHopUpdate &update); + void addNhCacheDnatEntries(const IpAddress &nhIp, bool add); + void addDnatToNhCache(const IpAddress &translatedIp, const IpAddress &dstIp); + void removeDnatFromNhCache(const IpAddress &translatedIp, const IpAddress &dstIp); + void addDnaptToNhCache(const IpAddress &translatedIp, const NaptEntryKey &key); + void removeDnaptFromNhCache(const IpAddress &translatedIp, const NaptEntryKey &key); + void addTwiceNatToNhCache(const IpAddress &translatedIp, const TwiceNatEntryKey &key); + void addTwiceNaptToNhCache(const IpAddress &translatedIp, const TwiceNaptEntryKey &key); + void removeTwiceNatFromNhCache(const IpAddress &translatedIp, const TwiceNatEntryKey &key); + void removeTwiceNaptFromNhCache(const IpAddress &translatedIp, const TwiceNaptEntryKey &key); + bool addHwSnatEntry(const IpAddress &ip_address); + bool addHwSnaptEntry(const NaptEntryKey &key); + bool addHwTwiceNatEntry(const TwiceNatEntryKey &key); + bool addHwTwiceNaptEntry(const TwiceNaptEntryKey &key); + bool removeHwSnatEntry(const IpAddress &dstIp); + bool removeHwSnaptEntry(const NaptEntryKey &key); + bool removeHwTwiceNatEntry(const TwiceNatEntryKey &key); + bool removeHwTwiceNaptEntry(const TwiceNaptEntryKey &key); + bool addHwDnatEntry(const IpAddress &ip_address); + bool addHwDnaptEntry(const NaptEntryKey &key); + bool removeHwDnatEntry(const IpAddress &dstIp); + bool removeHwDnaptEntry(const NaptEntryKey &key); + + void addAllStaticConntrackEntries(void); + void addConnTrackEntry(const IpAddress &ipAddr); + void addConnTrackEntry(const NaptEntryKey &key); + void addConnTrackEntry(const TwiceNatEntryKey &key); + void addConnTrackEntry(const TwiceNaptEntryKey &key); + void updateConnTrackTimeout(string prototype); + void updateConnTrackTimeout(const IpAddress &sourceIpAddr); + void updateConnTrackTimeout(const NaptEntryKey &entry); + void updateConnTrackTimeout(const TwiceNatEntryKey &entry); + void updateConnTrackTimeout(const TwiceNaptEntryKey &entry); + void deleteConnTrackEntry(const IpAddress &ipAddr); + void deleteConnTrackEntry(const NaptEntryKey &key); + void deleteConnTrackEntry(const TwiceNatEntryKey &key); + void deleteConnTrackEntry(const TwiceNaptEntryKey &key); + + bool checkIfNatEntryIsActive(const NatEntry::iterator &iter, time_t now); + bool checkIfNaptEntryIsActive(const NaptEntry::iterator &iter, time_t now); + bool checkIfTwiceNatEntryIsActive(const TwiceNatEntry::iterator &iter, time_t now); + bool checkIfTwiceNaptEntryIsActive(const TwiceNaptEntry::iterator &iter, time_t now); + + bool warmBootingInProgress(void); + void enableNatFeature(void); + void disableNatFeature(void); + void addAllNatEntries(void); + void flushAllNatEntries(void); + void clearAllDnatEntries(void); + void cleanupAppDbEntries(void); + void clearCounters(void); + void queryCounters(void); + void queryHitBits(void); + bool isNatEnabled(void); + bool getNatCounters(const NatEntry::iterator &iter); + bool getTwiceNatCounters(const TwiceNatEntry::iterator &iter); + bool getNaptCounters(const NaptEntry::iterator &iter); + bool getTwiceNaptCounters(const TwiceNaptEntry::iterator &iter); + bool setNatCounters(const NatEntry::iterator &iter); + bool setTwiceNatCounters(const TwiceNatEntry::iterator &iter); + bool setNaptCounters(const NaptEntry::iterator &iter); + bool setTwiceNaptCounters(const TwiceNaptEntry::iterator &iter); + void updateStaticNatCounters(int count); + void updateDynamicNatCounters(int count); + void updateStaticNaptCounters(int count); + void updateDynamicNaptCounters(int count); + void updateStaticTwiceNatCounters(int count); + void updateDynamicTwiceNatCounters(int count); + void updateStaticTwiceNaptCounters(int count); + void updateDynamicTwiceNaptCounters(int count); + void updateSnatCounters(int count); + void updateDnatCounters(int count); + void updateNatCounters(const IpAddress &ipAddr, + uint64_t snat_translations_pkts, uint64_t snat_translations_bytes); + void updateNaptCounters(const string &protocol, const IpAddress &ipAddr, int l4_port, + uint64_t snat_translations_pkts, uint64_t snat_translations_bytes); + void deleteNatCounters(const IpAddress &ipAddr); + void deleteNaptCounters(const string &protocol, const IpAddress &ipAddr, int l4_port); + void deleteTwiceNatCounters(const TwiceNatEntryKey &key); + void deleteTwiceNaptCounters(const TwiceNaptEntryKey &key); + void updateTwiceNatCounters(const TwiceNatEntryKey &key, + uint64_t nat_translations_pkts, uint64_t nat_translations_bytes); + void updateTwiceNaptCounters(const TwiceNaptEntryKey &key, + uint64_t nat_translations_pkts, uint64_t nat_translations_bytes); +}; + +#endif /* SWSS_NATORCH_H */ diff --git a/orchagent/nexthopgroupkey.h b/orchagent/nexthopgroupkey.h index 6e90845d90..d60aee8a32 100644 --- a/orchagent/nexthopgroupkey.h +++ b/orchagent/nexthopgroupkey.h @@ -86,6 +86,18 @@ class NextHopGroupKey return true; } + bool hasIntfNextHop() const + { + for (const auto &nh : m_nexthops) + { + if (nh.isIntfNextHop()) + { + return true; + } + } + return false; + } + void remove(const std::string &ip, const std::string &alias) { NextHopKey nh(ip, alias); diff --git a/orchagent/nexthopkey.h b/orchagent/nexthopkey.h index a86aac2cc1..757436226b 100644 --- a/orchagent/nexthopkey.h +++ b/orchagent/nexthopkey.h @@ -64,6 +64,11 @@ struct NextHopKey { return !(*this == o); } + + bool isIntfNextHop() const + { + return (ip_address.getV4Addr() == 0); + } }; #endif /* SWSS_NEXTHOPKEY_H */ diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index d71bafab85..6782a16087 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -35,6 +35,9 @@ CrmOrch *gCrmOrch; BufferOrch *gBufferOrch; SwitchOrch *gSwitchOrch; Directory gDirectory; +NatOrch *gNatOrch; + +bool gIsNatSupported = false; OrchDaemon::OrchDaemon(DBConnector *applDb, DBConnector *configDb, DBConnector *stateDb) : m_applDb(applDb), @@ -208,6 +211,18 @@ bool OrchDaemon::init() DebugCounterOrch *debug_counter_orch = new DebugCounterOrch(m_configDb, debug_counter_tables, 1000); + const int natorch_base_pri = 50; + + vector nat_tables = { + { APP_NAT_TABLE_NAME, natorch_base_pri + 4 }, + { APP_NAPT_TABLE_NAME, natorch_base_pri + 3 }, + { APP_NAT_TWICE_TABLE_NAME, natorch_base_pri + 2 }, + { APP_NAPT_TWICE_TABLE_NAME, natorch_base_pri + 1 }, + { APP_NAT_GLOBAL_TABLE_NAME, natorch_base_pri } + }; + + gNatOrch = new NatOrch(m_applDb, m_stateDb, nat_tables, gRouteOrch, gNeighOrch); + /* * The order of the orch list is important for state restore of warm start and * the queued processing in m_toSync map after gPortsOrch->allPortsReady() is set. @@ -262,6 +277,7 @@ bool OrchDaemon::init() m_orchList.push_back(cfg_vnet_rt_orch); m_orchList.push_back(vnet_orch); m_orchList.push_back(vnet_rt_orch); + m_orchList.push_back(gNatOrch); m_select = new Select(); diff --git a/orchagent/orchdaemon.h b/orchagent/orchdaemon.h index 5e9c9b914a..3094692df6 100644 --- a/orchagent/orchdaemon.h +++ b/orchagent/orchdaemon.h @@ -30,6 +30,7 @@ #include "sfloworch.h" #include "debugcounterorch.h" #include "directory.h" +#include "natorch.h" using namespace swss; diff --git a/orchagent/port.h b/orchagent/port.h index 2af0eb85db..d5b8827c51 100644 --- a/orchagent/port.h +++ b/orchagent/port.h @@ -96,6 +96,8 @@ class Port std::vector m_priority_group_ids; sai_port_priority_flow_control_mode_t m_pfc_asym = SAI_PORT_PRIORITY_FLOW_CONTROL_MODE_COMBINED; uint8_t m_pfc_bitmask = 0; + uint32_t m_nat_zone_id = 0; + /* * Following two bit vectors are used to lock * the PG/queue from being changed in BufferOrch. diff --git a/orchagent/saihelper.cpp b/orchagent/saihelper.cpp index 9fbe449179..ec990d605e 100644 --- a/orchagent/saihelper.cpp +++ b/orchagent/saihelper.cpp @@ -43,6 +43,7 @@ sai_dtel_api_t* sai_dtel_api; sai_bmtor_api_t* sai_bmtor_api; sai_samplepacket_api_t* sai_samplepacket_api; sai_debug_counter_api_t* sai_debug_counter_api; +sai_nat_api_t* sai_nat_api; extern sai_object_id_t gSwitchId; extern bool gSairedisRecord; @@ -134,6 +135,7 @@ void initSaiApi() sai_api_query((sai_api_t)SAI_API_BMTOR, (void **)&sai_bmtor_api); sai_api_query(SAI_API_SAMPLEPACKET, (void **)&sai_samplepacket_api); sai_api_query(SAI_API_DEBUG_COUNTER, (void **)&sai_debug_counter_api); + sai_api_query(SAI_API_NAT, (void **)&sai_nat_api); sai_log_set(SAI_API_SWITCH, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_BRIDGE, SAI_LOG_LEVEL_NOTICE); @@ -162,6 +164,7 @@ void initSaiApi() sai_log_set((sai_api_t)SAI_API_BMTOR, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_SAMPLEPACKET, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_DEBUG_COUNTER, SAI_LOG_LEVEL_NOTICE); + sai_log_set((sai_api_t)SAI_API_NAT, SAI_LOG_LEVEL_NOTICE); } void initSaiRedis(const string &record_location) diff --git a/swssconfig/sample/00-copp.config.json b/swssconfig/sample/00-copp.config.json index de3ec33c7f..997b73bdb3 100644 --- a/swssconfig/sample/00-copp.config.json +++ b/swssconfig/sample/00-copp.config.json @@ -43,8 +43,8 @@ "OP": "SET" }, { - "COPP_TABLE:trap.group.ip2me": { - "trap_ids": "ip2me", + "COPP_TABLE:trap.group.nat.ip2me": { + "trap_ids": "ip2me,src_nat_miss,dest_nat_miss", "trap_action":"trap", "trap_priority":"1", "queue": "1", diff --git a/tests/conftest.py b/tests/conftest.py index 5e1c496c71..5b74ff0c7a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -157,7 +157,8 @@ def __init__(self, name=None, imgname=None, keeptb=False, fakeplatform=None): self.syncd = ['syncd'] self.rtd = ['fpmsyncd', 'zebra'] self.teamd = ['teamsyncd', 'teammgrd'] - self.alld = self.basicd + self.swssd + self.syncd + self.rtd + self.teamd + self.natd = ['natsyncd', 'natmgrd'] + self.alld = self.basicd + self.swssd + self.syncd + self.rtd + self.teamd + self.natd self.client = docker.from_env() if subprocess.check_call(["/sbin/modprobe", "team"]) != 0: diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 73574f5b62..4c0b5582ce 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -60,7 +60,8 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/watermarkorch.cpp \ $(top_srcdir)/orchagent/chassisorch.cpp \ $(top_srcdir)/orchagent/sfloworch.cpp \ - $(top_srcdir)/orchagent/debugcounterorch.cpp + $(top_srcdir)/orchagent/debugcounterorch.cpp \ + $(top_srcdir)/orchagent/natorch.cpp tests_SOURCES += $(FLEX_CTR_DIR)/flex_counter_manager.cpp $(FLEX_CTR_DIR)/flex_counter_stat_manager.cpp tests_SOURCES += $(DEBUG_CTR_DIR)/debug_counter.cpp $(DEBUG_CTR_DIR)/drop_counter.cpp diff --git a/tests/test_nat.py b/tests/test_nat.py new file mode 100644 index 0000000000..9dea37aeb3 --- /dev/null +++ b/tests/test_nat.py @@ -0,0 +1,362 @@ +from swsscommon import swsscommon +import time +import re +import json +import pytest +import pdb +import os + + +class TestNatFeature(object): + def setup_db(self, dvs): + self.appdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) + self.asicdb = swsscommon.DBConnector(1, dvs.redis_sock, 0) + self.configdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) + + def set_interfaces(self, dvs): + intf_tbl = swsscommon.Table(self.configdb, "INTERFACE") + fvs = swsscommon.FieldValuePairs([("NULL","NULL")]) + intf_tbl.set("Ethernet0|67.66.65.1/24", fvs) + intf_tbl.set("Ethernet4|18.18.18.1/24", fvs) + intf_tbl.set("Ethernet0", fvs) + intf_tbl.set("Ethernet4", fvs) + dvs.runcmd("ifconfig Ethernet0 up") + dvs.runcmd("ifconfig Ethernet4 up") + + dvs.servers[0].runcmd("ip link set down dev eth0") == 0 + dvs.servers[0].runcmd("ip link set up dev eth0") == 0 + dvs.servers[0].runcmd("ifconfig eth0 67.66.65.2/24") + dvs.servers[0].runcmd("ip route add default via 67.66.65.1") + + dvs.servers[1].runcmd("ip link set down dev eth0") == 0 + dvs.servers[1].runcmd("ip link set up dev eth0") == 0 + dvs.servers[1].runcmd("ifconfig eth0 18.18.18.2/24") + dvs.servers[1].runcmd("ip route add default via 18.18.18.1") + + ps = swsscommon.ProducerStateTable(self.appdb, "ROUTE_TABLE") + fvs = swsscommon.FieldValuePairs([("nexthop","18.18.18.2"), \ + ("ifname", "Ethernet0")]) + + pubsub = dvs.SubscribeAsicDbObject("SAI_OBJECT_TYPE_ROUTE_ENTRY") + + dvs.runcmd("config nat add interface Ethernet0 -nat_zone 1") + + time.sleep(1) + + def clear_interfaces(self, dvs): + dvs.servers[0].runcmd("ifconfig eth0 0.0.0.0") + + dvs.servers[1].runcmd("ifconfig eth0 0.0.0.0") + # dvs.servers[1].runcmd("ip route del default") + + time.sleep(1) + + def test_NatGlobalTable(self, dvs, testlog): + # initialize + self.setup_db(dvs) + + # enable NAT feature + dvs.runcmd("config nat feature enable") + dvs.runcmd("config nat set timeout 450") + dvs.runcmd("config nat set udp-timeout 360") + dvs.runcmd("config nat set tcp-timeout 900") + + # check NAT global values in appdb + tbl = swsscommon.Table(self.appdb, "NAT_GLOBAL_TABLE") + values = tbl.getKeys() + + assert len(values) == 1 + + (status, fvs) = tbl.get("Values") + + assert fvs==(('admin_mode', 'enabled'), ('nat_timeout', '450'), ('nat_udp_timeout', '360'), ('nat_tcp_timeout', '900')) + + def test_NatInterfaceZone(self, dvs, testlog): + # initialize + self.setup_db(dvs) + self.set_interfaces(dvs) + + # check NAT zone is set for interface in app db + tbl = swsscommon.Table(self.appdb, "INTF_TABLE") + keys = tbl.getKeys() + + (status, fvs) = tbl.get("Ethernet0") + + assert fvs==(('NULL', 'NULL'), ('nat_zone', '1')) + + + def test_AddNatStaticEntry(self, dvs, testlog): + # initialize + self.setup_db(dvs) + + # get neighbor and arp entry + dvs.servers[0].runcmd("ping -c 1 18.18.18.2") + + # add a static nat entry + dvs.runcmd("config nat add static basic 67.66.65.1 18.18.18.2") + + # check the entry in the config db + tbl = swsscommon.Table(self.configdb, "STATIC_NAT") + entry = tbl.getKeys() + assert len(entry) == 1 + + (status, fvs) = tbl.get("67.66.65.1") + + assert fvs==(('local_ip', '18.18.18.2'),) + + # check the entry in app db + tbl = swsscommon.Table(self.appdb, "NAT_TABLE") + entry = tbl.getKeys() + assert len(entry) == 2 + + (status, fvs) = tbl.get("67.66.65.1") + + assert fvs== (('translated_ip', '18.18.18.2'), ('nat_type', 'dnat'), ('entry_type', 'static')) + + #check the entry in asic db + tbl = swsscommon.Table(self.asicdb, "ASIC_STATE:SAI_OBJECT_TYPE_NAT_ENTRY") + keys = tbl.getKeys() + assert len(keys) == 2 + + for key in keys: + if (key.find("dst_ip:67.66.65.1")) or (key.find("src_ip:18.18.18.2")): + assert True + else: + assert False + + def test_DelNatStaticEntry(self, dvs, testlog): + # initialize + self.setup_db(dvs) + + # delete a static nat entry + dvs.runcmd("config nat remove static basic 67.66.65.1 18.18.18.2") + + # check the entry is no there in the config db + tbl = swsscommon.Table(self.configdb, "STATIC_NAT") + entry = tbl.getKeys() + assert entry == () + + # check the entry is not there in app db + tbl = swsscommon.Table(self.appdb, "NAT_TABLE") + entry = tbl.getKeys() + assert entry == () + + #check the entry is not there in asic db + tbl = swsscommon.Table(self.asicdb, "ASIC_STATE:SAI_OBJECT_TYPE_NAT_ENTRY") + key = tbl.getKeys() + assert key == () + + def test_AddNaPtStaticEntry(self, dvs, testlog): + # initialize + self.setup_db(dvs) + + # get neighbor and arp entry + dvs.servers[0].runcmd("ping -c 1 18.18.18.2") + + # add a static nat entry + dvs.runcmd("config nat add static udp 67.66.65.1 670 18.18.18.2 180") + + # check the entry in the config db + tbl = swsscommon.Table(self.configdb, "STATIC_NAPT") + entry = tbl.getKeys() + assert len(entry) == 1 + + (status, fvs) = tbl.get("67.66.65.1|UDP|670") + + assert fvs==(('local_ip', '18.18.18.2'),('local_port', '180')) + + # check the entry in app db + tbl = swsscommon.Table(self.appdb, "NAPT_TABLE:UDP") + entry = tbl.getKeys() + assert len(entry) == 2 + + (status, fvs) = tbl.get("67.66.65.1:670") + + assert fvs== (('translated_ip', '18.18.18.2'), ('translated_l4_port', '180'), ('nat_type', 'dnat'), ('entry_type', 'static')) + + #check the entry in asic db + tbl = swsscommon.Table(self.asicdb, "ASIC_STATE:SAI_OBJECT_TYPE_NAT_ENTRY") + keys = tbl.getKeys() + assert len(keys) == 2 + + for key in keys: + if (key.find("dst_ip:67.66.65.1")) and (key.find("key.l4_dst_port:670")): + assert True + if (key.find("src_ip:18.18.18.2")) or (key.find("key.l4_src_port:180")): + assert True + else: + assert False + + def test_DelNaPtStaticEntry(self, dvs, testlog): + # initialize + self.setup_db(dvs) + + # delete a static nat entry + dvs.runcmd("config nat remove static udp 67.66.65.1 670 18.18.18.2 180") + + # check the entry is no there in the config db + tbl = swsscommon.Table(self.configdb, "STATIC_NAPT") + entry = tbl.getKeys() + assert entry == () + + # check the entry is not there in app db + tbl = swsscommon.Table(self.appdb, "NAPT_TABLE") + entry = tbl.getKeys() + assert entry == () + + #check the entry is not there in asic db + tbl = swsscommon.Table(self.asicdb, "ASIC_STATE:SAI_OBJECT_TYPE_NAT_ENTRY") + key = tbl.getKeys() + assert key == () + + + def test_AddTwiceNatEntry(self, dvs, testlog): + # initialize + self.setup_db(dvs) + + # get neighbor and arp entry + dvs.servers[0].runcmd("ping -c 1 18.18.18.2") + dvs.servers[1].runcmd("ping -c 1 67.66.65.2") + + # add a twice nat entry + dvs.runcmd("config nat add static basic 67.66.65.2 18.18.18.1 -nat_type snat -twice_nat_id 9") + dvs.runcmd("config nat add static basic 67.66.65.1 18.18.18.2 -nat_type dnat -twice_nat_id 9") + + # check the entry in the config db + tbl = swsscommon.Table(self.configdb, "STATIC_NAT") + entry = tbl.getKeys() + assert len(entry) == 2 + + (status, fvs) = tbl.get("67.66.65.1") + + assert fvs== (('nat_type', 'dnat'), ('twice_nat_id', '9'), ('local_ip', '18.18.18.2')) + + (status, fvs) = tbl.get("67.66.65.2") + + assert fvs== (('nat_type', 'snat'), ('twice_nat_id', '9'), ('local_ip', '18.18.18.1')) + + # check the entry in app db + tbl = swsscommon.Table(self.appdb, "NAT_TWICE_TABLE") + entry = tbl.getKeys() + assert len(entry) == 2 + + (status, fvs) = tbl.get("67.66.65.2:67.66.65.1") + + assert fvs== (('translated_src_ip', '18.18.18.1'), ('translated_dst_ip', '18.18.18.2'), ('entry_type', 'static')) + + (status, fvs) = tbl.get("18.18.18.2:18.18.18.1") + + assert fvs== (('translated_src_ip', '67.66.65.1'), ('translated_dst_ip', '67.66.65.2'), ('entry_type', 'static')) + + #check the entry in asic db + tbl = swsscommon.Table(self.asicdb, "ASIC_STATE:SAI_OBJECT_TYPE_NAT_ENTRY") + keys = tbl.getKeys() + assert len(keys) == 2 + for key in keys: + if (key.find("dst_ip:18.18.18.1")) or (key.find("src_ip:18.18.18.2")): + assert True + else: + assert False + + def test_DelTwiceNatStaticEntry(self, dvs, testlog): + # initialize + self.setup_db(dvs) + + # delete a static nat entry + dvs.runcmd("config nat remove static basic 67.66.65.2 18.18.18.1") + dvs.runcmd("config nat remove static basic 67.66.65.1 18.18.18.2") + + # check the entry is no there in the config db + tbl = swsscommon.Table(self.configdb, "STATIC_NAT") + entry = tbl.getKeys() + assert entry == () + + # check the entry is not there in app db + tbl = swsscommon.Table(self.appdb, "NAT_TWICE_TABLE") + entry = tbl.getKeys() + assert entry == () + + #check the entry is not there in asic db + tbl = swsscommon.Table(self.asicdb, "ASIC_STATE:SAI_OBJECT_TYPE_NAT_ENTRY") + key = tbl.getKeys() + assert key == () + + + def test_AddTwiceNaPtEntry(self, dvs, testlog): + # initialize + self.setup_db(dvs) + + # get neighbor and arp entry + dvs.servers[0].runcmd("ping -c 1 18.18.18.2") + dvs.servers[1].runcmd("ping -c 1 67.66.65.2") + + # add a twice nat entry + dvs.runcmd("config nat add static udp 67.66.65.2 670 18.18.18.1 181 -nat_type snat -twice_nat_id 7") + dvs.runcmd("config nat add static udp 67.66.65.1 660 18.18.18.2 182 -nat_type dnat -twice_nat_id 7") + + # check the entry in the config db + tbl = swsscommon.Table(self.configdb, "STATIC_NAPT") + entry = tbl.getKeys() + assert len(entry) == 2 + + (status, fvs) = tbl.get("67.66.65.1|UDP|660") + + assert fvs== (('nat_type', 'dnat'), ('local_ip', '18.18.18.2'), ('twice_nat_id', '7'), ('local_port', '182')) + (status, fvs) = tbl.get("67.66.65.2|UDP|670") + + assert fvs== (('nat_type', 'snat'), ('local_ip', '18.18.18.1'),('twice_nat_id', '7'), ('local_port', '181')) + + + + # check the entry in app db + tbl = swsscommon.Table(self.appdb, "NAPT_TWICE_TABLE") + entry = tbl.getKeys() + assert len(entry) == 2 + + (status, fvs) = tbl.get("UDP:67.66.65.2:670:67.66.65.1:660") + + assert fvs== (('translated_src_ip', '18.18.18.1'), ('translated_src_l4_port', '181'), ('translated_dst_ip', '18.18.18.2'), ('translated_dst_l4_port', '182'), ('entry_type', 'static')) + + (status, fvs) = tbl.get("UDP:18.18.18.2:182:18.18.18.1:181") + + assert fvs== (('translated_src_ip', '67.66.65.1'), ('translated_src_l4_port', '660'),('translated_dst_ip', '67.66.65.2'),('translated_dst_l4_port', '670'), ('entry_type', 'static')) + + #check the entry in asic db + tbl = swsscommon.Table(self.asicdb, "ASIC_STATE:SAI_OBJECT_TYPE_NAT_ENTRY") + keys = tbl.getKeys() + assert len(keys) == 2 + for key in keys: + if (key.find("src_ip:18.18.18.2")) or (key.find("l4_src_port:182")): + assert True + if (key.find("dst_ip:18.18.18.1")) or (key.find("l4_dst_port:181")): + assert True + else: + assert False + + + def test_DelTwiceNaPtStaticEntry(self, dvs, testlog): + # initialize + self.setup_db(dvs) + + # delete a static nat entry + dvs.runcmd("config nat remove static udp 67.66.65.2 670 18.18.18.1 181") + dvs.runcmd("config nat remove static udp 67.66.65.1 660 18.18.18.2 182") + + # check the entry is not there in the config db + tbl = swsscommon.Table(self.configdb, "STATIC_NAPT") + entry = tbl.getKeys() + assert entry == () + + # check the entry is not there in app db + tbl = swsscommon.Table(self.appdb, "NAPT_TWICE_TABLE") + entry = tbl.getKeys() + assert entry == () + + #check the entry is not there in asic db + tbl = swsscommon.Table(self.asicdb, "ASIC_STATE:SAI_OBJECT_TYPE_NAT_ENTRY") + key = tbl.getKeys() + assert key == () + + # clear interfaces + self.clear_interfaces(dvs) + From e3af8ffd192e798015029f0a2593ef3690cd6797 Mon Sep 17 00:00:00 2001 From: chaoskao <48510427+chaoskao@users.noreply.github.com> Date: Fri, 17 Jan 2020 10:16:39 +0800 Subject: [PATCH 40/63] [AclOrch] add validation for check CRM (#1082) --- tests/mock_tests/aclorch_ut.cpp | 69 +++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/tests/mock_tests/aclorch_ut.cpp b/tests/mock_tests/aclorch_ut.cpp index 19d52cafbf..66b0090d23 100644 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -628,16 +628,71 @@ namespace aclorch_test // consistency validation with CRM bool validateResourceCountWithCrm(const AclOrch *aclOrch, CrmOrch *crmOrch) { - auto resourceMap = Portal::CrmOrchInternal::getResourceMap(crmOrch); - auto ifpPortKey = Portal::CrmOrchInternal::getCrmAclKey(crmOrch, SAI_ACL_STAGE_INGRESS, SAI_ACL_BIND_POINT_TYPE_PORT); - auto efpPortKey = Portal::CrmOrchInternal::getCrmAclKey(crmOrch, SAI_ACL_STAGE_EGRESS, SAI_ACL_BIND_POINT_TYPE_PORT); + // Verify ACL Tables + auto const &resourceMap = Portal::CrmOrchInternal::getResourceMap(crmOrch); + uint32_t crm_acl_table_cnt = 0; + for (auto const &kv : resourceMap.at(CrmResourceType::CRM_ACL_TABLE).countersMap) + { + crm_acl_table_cnt += kv.second.usedCounter; + } + + if (crm_acl_table_cnt != Portal::AclOrchInternal::getAclTables(aclOrch).size()) + { + ADD_FAILURE() << "ACL table size is not consistent between CrmOrch (" << crm_acl_table_cnt + << ") and AclOrch " << Portal::AclOrchInternal::getAclTables(aclOrch).size(); + return false; + } + + + // Verify ACL Rules + // + // for each CRM_ACL_ENTRY and CRM_ACL_COUNTER's entry => the ACL TABLE should exist + // + for (auto acl_entry_or_counter : { CrmResourceType::CRM_ACL_ENTRY, CrmResourceType::CRM_ACL_COUNTER }) + { + auto const &resourceMap = Portal::CrmOrchInternal::getResourceMap(crmOrch); + for (auto const &kv : resourceMap.at(acl_entry_or_counter).countersMap) + { + auto acl_oid = kv.second.id; - auto ifpPortAclTableCount = resourceMap.at(CrmResourceType::CRM_ACL_TABLE).countersMap[ifpPortKey].usedCounter; - auto efpPortAclTableCount = resourceMap.at(CrmResourceType::CRM_ACL_TABLE).countersMap[efpPortKey].usedCounter; + const auto &aclTables = Portal::AclOrchInternal::getAclTables(aclOrch); + if (aclTables.find(acl_oid) == aclTables.end()) + { + ADD_FAILURE() << "Can't find ACL '" << sai_serialize_object_id(acl_oid) << "' in AclOrch"; + return false; + } + + if (kv.second.usedCounter != aclTables.at(acl_oid).rules.size()) + { + ADD_FAILURE() << "CRM usedCounter (" << kv.second.usedCounter + << ") is not equal rule in ACL (" << aclTables.at(acl_oid).rules.size() << ")"; + return false; + } + } + } - return ifpPortAclTableCount + efpPortAclTableCount == Portal::AclOrchInternal::getAclTables(aclOrch).size(); + // + // for each ACL TABLE with rule count larger than one => it shoule exist a corresponding entry in CRM_ACL_ENTRY and CRM_ACL_COUNTER + // + for (const auto &kv : Portal::AclOrchInternal::getAclTables(aclOrch)) + { + if (0 < kv.second.rules.size()) + { + auto key = Portal::CrmOrchInternal::getCrmAclTableKey(crmOrch, kv.first); + for (auto acl_entry_or_counter : { CrmResourceType::CRM_ACL_ENTRY, CrmResourceType::CRM_ACL_COUNTER }) + { + const auto &cntMap = Portal::CrmOrchInternal::getResourceMap(crmOrch).at(acl_entry_or_counter).countersMap; + if (cntMap.find(key) == cntMap.end()) + { + ADD_FAILURE() << "Can't find ACL (" << sai_serialize_object_id(kv.first) + << ") in " << (acl_entry_or_counter == CrmResourceType::CRM_ACL_ENTRY ? "CrmResourceType::CRM_ACL_ENTRY" : "CrmResourceType::CRM_ACL_COUNTER"); + return false; + } + } + } + } - // TODO: add rule check + return true; } // leakage check From 32a9014528d3a74d5c300703566b637566f121e8 Mon Sep 17 00:00:00 2001 From: Danny Allen Date: Thu, 16 Jan 2020 18:44:20 -0800 Subject: [PATCH 41/63] [aclorch] Validate that provided IN/OUT_PORTS are physical interfaces (#1156) * [aclorch] Validate that provided IN/OUT_PORTS are physical interfaces - Add a check during the rule validation step to make sure provided IN/OUT_PORTS rules only include physical interfaces - Add vs test cases for invalid IN/OUT_PORTS inputs Signed-off-by: Danny Allen * Clarify test names * Add extra delay for table teardown * Check if new tests are causing tests to fail * Check if new tests are causing tests to fail * Check if ProducerStateTable is having problems * Make delete ACL table test more strict * Remove invalid interface test cases * Undo change to delete test * Undo change to delete test --- orchagent/aclorch.cpp | 14 ++++++++ tests/test_acl.py | 78 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index fe0163b58f..76062bfec3 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -207,6 +207,13 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) SWSS_LOG_ERROR("Failed to locate port %s", alias.c_str()); return false; } + + if (port.m_type != Port::PHY) + { + SWSS_LOG_ERROR("Cannot bind rule to %s: IN_PORTS can only match physical interfaces", alias.c_str()); + return false; + } + m_inPorts.push_back(port.m_port_id); } @@ -231,6 +238,13 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) SWSS_LOG_ERROR("Failed to locate port %s", alias.c_str()); return false; } + + if (port.m_type != Port::PHY) + { + SWSS_LOG_ERROR("Cannot bind rule to %s: OUT_PORTS can only match physical interfaces", alias.c_str()); + return false; + } + m_outPorts.push_back(port.m_port_id); } diff --git a/tests/test_acl.py b/tests/test_acl.py index 83909b7c81..43316bd34c 100644 --- a/tests/test_acl.py +++ b/tests/test_acl.py @@ -329,19 +329,85 @@ def test_AclRuleInOutPorts(self, dvs, testlog): (status, fvs) = atbl.get(acl_entry[0]) assert status == False - def test_AclTableDeletion(self, dvs, testlog): + def test_AclRuleInPortsNonExistingInterface(self, dvs, testlog): + self.setup_db(dvs) + + # Create ACL rule with a completely wrong interface + tbl = swsscommon.Table(self.cdb, "ACL_RULE") + fvs = swsscommon.FieldValuePairs([("priority", "55"), + ("PACKET_ACTION", "FORWARD"), + ("IN_PORTS", "FOO_BAR_BAZ")]) + tbl.set("test|foo_bar_baz", fvs) + time.sleep(1) + + # Make sure no rules were created + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_ENTRY") + keys = atbl.getKeys() + acl_entry = [k for k in keys if k not in dvs.asicdb.default_acl_entries] + assert len(acl_entry) == 0 + + # Create ACL rule with a correct interface and a completely wrong interface + tbl = swsscommon.Table(self.cdb, "ACL_RULE") + fvs = swsscommon.FieldValuePairs([("priority", "55"), + ("PACKET_ACTION", "FORWARD"), + ("IN_PORTS", "Ethernet0,FOO_BAR_BAZ")]) + tbl.set("test|foo_bar_baz", fvs) + time.sleep(1) + + # Make sure no rules were created + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_ENTRY") + keys = atbl.getKeys() + acl_entry = [k for k in keys if k not in dvs.asicdb.default_acl_entries] + assert len(acl_entry) == 0 + # Delete rule + tbl._del("test|foo_bar_baz") + time.sleep(1) + + def test_AclRuleOutPortsNonExistingInterface(self, dvs, testlog): self.setup_db(dvs) - db = swsscommon.DBConnector(4, dvs.redis_sock, 0) - adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) - # get ACL_TABLE in config db - tbl = swsscommon.Table(db, "ACL_TABLE") + # Create ACL rule with a completely wrong interface + tbl = swsscommon.Table(self.cdb, "ACL_RULE") + fvs = swsscommon.FieldValuePairs([("priority", "55"), + ("PACKET_ACTION", "FORWARD"), + ("OUT_PORTS", "FOO_BAR_BAZ")]) + tbl.set("test|foo_bar_baz", fvs) + time.sleep(1) + + # Make sure no rules were created + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_ENTRY") + keys = atbl.getKeys() + acl_entry = [k for k in keys if k not in dvs.asicdb.default_acl_entries] + assert len(acl_entry) == 0 + + # Create ACL rule with a correct interface and a completely wrong interface + tbl = swsscommon.Table(self.cdb, "ACL_RULE") + fvs = swsscommon.FieldValuePairs([("priority", "55"), + ("PACKET_ACTION", "FORWARD"), + ("OUT_PORTS", "Ethernet0,FOO_BAR_BAZ")]) + tbl.set("test|foo_bar_baz", fvs) + time.sleep(1) + + # Make sure no rules were created + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_ENTRY") + keys = atbl.getKeys() + acl_entry = [k for k in keys if k not in dvs.asicdb.default_acl_entries] + assert len(acl_entry) == 0 + + # Delete rule + tbl._del("test|foo_bar_baz") + time.sleep(1) + + def test_AclTableDeletion(self, dvs, testlog): + self.setup_db(dvs) + + tbl = swsscommon.Table(self.cdb, "ACL_TABLE") tbl._del("test") time.sleep(1) - atbl = swsscommon.Table(adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_TABLE") + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_TABLE") keys = atbl.getKeys() # only the default table was left along with DTel tables assert len(keys) >= 1 From bc2450f78a7c547748f37fe8e4215a14c1d96b84 Mon Sep 17 00:00:00 2001 From: Qi Luo Date: Thu, 16 Jan 2020 22:16:49 -0800 Subject: [PATCH 42/63] Fix bug: shellquote the inner string (#1168) --- cfgmgr/vlanmgr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfgmgr/vlanmgr.cpp b/cfgmgr/vlanmgr.cpp index d8834d12d9..888ced509a 100644 --- a/cfgmgr/vlanmgr.cpp +++ b/cfgmgr/vlanmgr.cpp @@ -209,7 +209,7 @@ bool VlanMgr::removeHostVlanMember(int vlan_id, const string &port_alias) IP_CMD " link set " << shellquote(port_alias) << " nomaster; " "elif [ $ret -eq 1 ]; then exit 0; " "else exit $ret; fi )"; - cmds << BASH_CMD " -c " << shellquote(cmds.str()); + cmds << BASH_CMD " -c " << shellquote(inner.str()); std::string res; EXEC_WITH_ERROR_THROW(cmds.str(), res); From c7650a8432eb863cabecd3a6a071f778a39b029e Mon Sep 17 00:00:00 2001 From: Danny Allen Date: Tue, 21 Jan 2020 10:12:27 -0800 Subject: [PATCH 43/63] [portsorch] Refactor portsorch to use FlexCounterManager to setup port and queue stats (#1170) - Updates portsorch to use FlexCounterManager for port and queue stats instead of direct redis operations - Updates FlexCounterManager to allow clients to enable the group in the constructor, for convenience - Updates the makefile for cfgmgr to include new flex_counter dependency from portsorch Signed-off-by: Danny Allen --- cfgmgr/Makefile.am | 2 +- orchagent/debugcounterorch.cpp | 3 +- .../flex_counter/flex_counter_manager.cpp | 10 ++- orchagent/flex_counter/flex_counter_manager.h | 5 +- .../flex_counter_stat_manager.cpp | 5 +- .../flex_counter/flex_counter_stat_manager.h | 3 +- orchagent/portsorch.cpp | 80 ++++++------------- orchagent/portsorch.h | 6 +- 8 files changed, 47 insertions(+), 67 deletions(-) diff --git a/cfgmgr/Makefile.am b/cfgmgr/Makefile.am index 5a783ce5ff..7178cad1c6 100644 --- a/cfgmgr/Makefile.am +++ b/cfgmgr/Makefile.am @@ -1,4 +1,4 @@ -INCLUDES = -I $(top_srcdir) -I $(top_srcdir)/orchagent -I $(top_srcdir)/warmrestart +INCLUDES = -I $(top_srcdir) -I $(top_srcdir)/orchagent -I $(top_srcdir)/warmrestart -I $(top_srcdir)/orchagent/flex_counter CFLAGS_SAI = -I /usr/include/sai LIBNL_CFLAGS = -I/usr/include/libnl3 LIBNL_LIBS = -lnl-genl-3 -lnl-route-3 -lnl-3 diff --git a/orchagent/debugcounterorch.cpp b/orchagent/debugcounterorch.cpp index b70c6625c6..fd1005d452 100644 --- a/orchagent/debugcounterorch.cpp +++ b/orchagent/debugcounterorch.cpp @@ -25,7 +25,7 @@ static const unordered_map flex_counter_type_lookup = { // object should only be initialized once. DebugCounterOrch::DebugCounterOrch(DBConnector *db, const vector& table_names, int poll_interval) : Orch(db, table_names), - flex_counter_manager(DEBUG_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, poll_interval), + flex_counter_manager(DEBUG_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, poll_interval, true), m_stateDb(new DBConnector("STATE_DB", 0)), m_debugCapabilitiesTable(new Table(m_stateDb.get(), STATE_DEBUG_COUNTER_CAPABILITIES_NAME)), m_countersDb(new DBConnector("COUNTERS_DB", 0)), @@ -34,7 +34,6 @@ DebugCounterOrch::DebugCounterOrch(DBConnector *db, const vector& table_ { SWSS_LOG_ENTER(); publishDropCounterCapabilities(); - flex_counter_manager.enableFlexCounterGroup(); } DebugCounterOrch::~DebugCounterOrch(void) diff --git a/orchagent/flex_counter/flex_counter_manager.cpp b/orchagent/flex_counter/flex_counter_manager.cpp index 323bd309c4..130ea2833d 100644 --- a/orchagent/flex_counter/flex_counter_manager.cpp +++ b/orchagent/flex_counter/flex_counter_manager.cpp @@ -34,17 +34,19 @@ const unordered_map FlexCounterManager::counter_id_field_lo { { CounterType::PORT_DEBUG, PORT_DEBUG_COUNTER_ID_LIST }, { CounterType::SWITCH_DEBUG, SWITCH_DEBUG_COUNTER_ID_LIST }, + { CounterType::PORT, PORT_COUNTER_ID_LIST }, + { CounterType::QUEUE, QUEUE_COUNTER_ID_LIST } }; -// This constructor will create a group that is disabled by default. FlexCounterManager::FlexCounterManager( const string& group_name, const StatsMode stats_mode, - const uint polling_interval) : + const uint polling_interval, + const bool enabled) : group_name(group_name), stats_mode(stats_mode), polling_interval(polling_interval), - enabled(false), + enabled(enabled), flex_counter_db(new DBConnector("FLEX_COUNTER_DB", 0)), flex_counter_group_table(new ProducerTable(flex_counter_db.get(), FLEX_COUNTER_GROUP_TABLE)), flex_counter_table(new ProducerTable(flex_counter_db.get(), FLEX_COUNTER_TABLE)) @@ -72,6 +74,8 @@ FlexCounterManager::~FlexCounterManager() void FlexCounterManager::applyGroupConfiguration() { + SWSS_LOG_ENTER(); + vector field_values = { FieldValueTuple(STATS_MODE_FIELD, stats_mode_lookup.at(stats_mode)), diff --git a/orchagent/flex_counter/flex_counter_manager.h b/orchagent/flex_counter/flex_counter_manager.h index 0d444d57ed..19ff26ccf0 100644 --- a/orchagent/flex_counter/flex_counter_manager.h +++ b/orchagent/flex_counter/flex_counter_manager.h @@ -18,6 +18,8 @@ enum class StatsMode enum class CounterType { + PORT, + QUEUE, PORT_DEBUG, SWITCH_DEBUG }; @@ -33,7 +35,8 @@ class FlexCounterManager FlexCounterManager( const std::string& group_name, const StatsMode stats_mode, - const uint polling_interval); + const uint polling_interval, + const bool enabled); FlexCounterManager(const FlexCounterManager&) = delete; FlexCounterManager& operator=(const FlexCounterManager&) = delete; diff --git a/orchagent/flex_counter/flex_counter_stat_manager.cpp b/orchagent/flex_counter/flex_counter_stat_manager.cpp index 4f6cab843e..f636e5af1e 100644 --- a/orchagent/flex_counter/flex_counter_stat_manager.cpp +++ b/orchagent/flex_counter/flex_counter_stat_manager.cpp @@ -13,8 +13,9 @@ using swss::FieldValueTuple; FlexCounterStatManager::FlexCounterStatManager( const string& group_name, const StatsMode stats_mode, - const int polling_interval) : - FlexCounterManager(group_name, stats_mode, polling_interval) + const int polling_interval, + const bool enabled) : + FlexCounterManager(group_name, stats_mode, polling_interval, enabled) { SWSS_LOG_ENTER(); } diff --git a/orchagent/flex_counter/flex_counter_stat_manager.h b/orchagent/flex_counter/flex_counter_stat_manager.h index 250adb724c..7a388ae100 100644 --- a/orchagent/flex_counter/flex_counter_stat_manager.h +++ b/orchagent/flex_counter/flex_counter_stat_manager.h @@ -11,7 +11,8 @@ class FlexCounterStatManager : public FlexCounterManager FlexCounterStatManager( const std::string& group_name, const StatsMode stats_mode, - const int polling_interval); + const int polling_interval, + const bool enabled); FlexCounterStatManager(const FlexCounterStatManager&) = delete; FlexCounterStatManager& operator=(const FlexCounterStatManager&) = delete; diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index ab9c37b371..440c52aaf1 100644 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "net/if.h" @@ -41,10 +42,11 @@ extern BufferOrch *gBufferOrch; #define VLAN_PREFIX "Vlan" #define DEFAULT_VLAN_ID 1 #define MAX_VALID_VLAN_ID 4094 -#define PORT_FLEX_STAT_COUNTER_POLL_MSECS "1000" -#define QUEUE_FLEX_STAT_COUNTER_POLL_MSECS "10000" + +#define PORT_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS 1000 +#define QUEUE_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS 10000 #define QUEUE_WATERMARK_FLEX_STAT_COUNTER_POLL_MSECS "10000" -#define PG_WATERMARK_FLEX_STAT_COUNTER_POLL_MSECS "10000" +#define PG_WATERMARK_FLEX_STAT_COUNTER_POLL_MSECS "10000" static map fec_mode_map = @@ -70,7 +72,7 @@ static map learn_mode_map = { "notification", SAI_BRIDGE_PORT_FDB_LEARNING_MODE_FDB_NOTIFICATION} }; -const vector portStatIds = +const vector port_stat_ids = { SAI_PORT_STAT_IF_IN_OCTETS, SAI_PORT_STAT_IF_IN_UCAST_PKTS, @@ -113,7 +115,7 @@ const vector portStatIds = SAI_PORT_STAT_ETHER_IN_PKTS_128_TO_255_OCTETS, }; -static const vector queueStatIds = +static const vector queue_stat_ids = { SAI_QUEUE_STAT_PACKETS, SAI_QUEUE_STAT_BYTES, @@ -151,7 +153,9 @@ static char* hostif_vlan_tag[] = { * default VLAN and all ports removed from .1Q bridge. */ PortsOrch::PortsOrch(DBConnector *db, vector &tableNames) : - Orch(db, tableNames) + Orch(db, tableNames), + port_stat_manager(PORT_STAT_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, PORT_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS, true), + queue_stat_manager(QUEUE_STAT_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, QUEUE_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS, true) { SWSS_LOG_ENTER(); @@ -177,15 +181,6 @@ PortsOrch::PortsOrch(DBConnector *db, vector &tableNames) m_flexCounterTable = unique_ptr(new ProducerTable(m_flex_db.get(), FLEX_COUNTER_TABLE)); m_flexCounterGroupTable = unique_ptr(new ProducerTable(m_flex_db.get(), FLEX_COUNTER_GROUP_TABLE)); - vector fields; - fields.emplace_back(POLL_INTERVAL_FIELD, PORT_FLEX_STAT_COUNTER_POLL_MSECS); - fields.emplace_back(STATS_MODE_FIELD, STATS_MODE_READ); - m_flexCounterGroupTable->set(PORT_STAT_COUNTER_FLEX_COUNTER_GROUP, fields); - - fields.emplace_back(POLL_INTERVAL_FIELD, QUEUE_FLEX_STAT_COUNTER_POLL_MSECS); - fields.emplace_back(STATS_MODE_FIELD, STATS_MODE_READ); - m_flexCounterGroupTable->set(QUEUE_STAT_COUNTER_FLEX_COUNTER_GROUP, fields); - string queueWmSha, pgWmSha; string queueWmPluginName = "watermark_queue.lua"; string pgWmPluginName = "watermark_pg.lua"; @@ -1461,16 +1456,6 @@ bool PortsOrch::removePort(sai_object_id_t port_id) return true; } -string PortsOrch::getPortFlexCounterTableKey(string key) -{ - return string(PORT_STAT_COUNTER_FLEX_COUNTER_GROUP) + ":" + key; -} - -string PortsOrch::getQueueFlexCounterTableKey(string key) -{ - return string(QUEUE_STAT_COUNTER_FLEX_COUNTER_GROUP) + ":" + key; -} - string PortsOrch::getQueueWatermarkFlexCounterTableKey(string key) { return string(QUEUE_WATERMARK_STAT_COUNTER_FLEX_COUNTER_GROUP) + ":" + key; @@ -1514,22 +1499,15 @@ bool PortsOrch::initPort(const string &alias, const set &lane_set) fields.push_back(tuple); m_counterTable->set("", fields); - /* Add port to flex_counter for updating stat counters */ - string key = getPortFlexCounterTableKey(sai_serialize_object_id(p.m_port_id)); - std::string delimiter = ""; - std::ostringstream counters_stream; - for (const auto &id: portStatIds) + // Install a flex counter for this port to track stats + std::unordered_set counter_stats; + for (const auto& it: port_stat_ids) { - counters_stream << delimiter << sai_serialize_port_stat(id); - delimiter = comma; + counter_stats.emplace(sai_serialize_port_stat(it)); } + port_stat_manager.setCounterIdList(p.m_port_id, CounterType::PORT, counter_stats); - fields.clear(); - fields.emplace_back(PORT_COUNTER_ID_LIST, counters_stream.str()); - - m_flexCounterTable->set(key, fields); - - PortUpdate update = {p, true }; + PortUpdate update = { p, true }; notify(SUBJECT_TYPE_PORT_CHANGE, static_cast(&update)); SWSS_LOG_NOTICE("Initialized port %s", alias.c_str()); @@ -3385,34 +3363,26 @@ void PortsOrch::generateQueueMapPerPort(const Port& port) queueIndexVector.emplace_back(id, to_string(queueRealIndex)); } - /* add ordinary Queue stat counters */ - string key = getQueueFlexCounterTableKey(id); - - std::string delimiter = ""; - std::ostringstream counters_stream; - for (const auto& it: queueStatIds) + // Install a flex counter for this queue to track stats + std::unordered_set counter_stats; + for (const auto& it: queue_stat_ids) { - counters_stream << delimiter << sai_serialize_queue_stat(it); - delimiter = comma; + counter_stats.emplace(sai_serialize_queue_stat(it)); } - - vector fieldValues; - fieldValues.emplace_back(QUEUE_COUNTER_ID_LIST, counters_stream.str()); - - m_flexCounterTable->set(key, fieldValues); + queue_stat_manager.setCounterIdList(port.m_queue_ids[queueIndex], CounterType::QUEUE, counter_stats); /* add watermark queue counters */ - key = getQueueWatermarkFlexCounterTableKey(id); + string key = getQueueWatermarkFlexCounterTableKey(id); - delimiter = ""; - counters_stream.str(""); + string delimiter(""); + std::ostringstream counters_stream; for (const auto& it: queueWatermarkStatIds) { counters_stream << delimiter << sai_serialize_queue_stat(it); delimiter = comma; } - fieldValues.clear(); + vector fieldValues; fieldValues.emplace_back(QUEUE_COUNTER_ID_LIST, counters_stream.str()); m_flexCounterTable->set(key, fieldValues); diff --git a/orchagent/portsorch.h b/orchagent/portsorch.h index a1a99a9406..5278589706 100644 --- a/orchagent/portsorch.h +++ b/orchagent/portsorch.h @@ -9,6 +9,7 @@ #include "observer.h" #include "macaddress.h" #include "producertable.h" +#include "flex_counter_manager.h" #define FCS_LEN 4 #define VLAN_TAG_LEN 4 @@ -104,14 +105,15 @@ class PortsOrch : public Orch, public Subject unique_ptr m_flexCounterTable; unique_ptr m_flexCounterGroupTable; - std::string getQueueFlexCounterTableKey(std::string s); std::string getQueueWatermarkFlexCounterTableKey(std::string s); - std::string getPortFlexCounterTableKey(std::string s); std::string getPriorityGroupWatermarkFlexCounterTableKey(std::string s); shared_ptr m_counter_db; shared_ptr m_flex_db; + FlexCounterManager port_stat_manager; + FlexCounterManager queue_stat_manager; + std::map m_portSupportedSpeeds; bool m_initDone = false; From d03e0acc031b6a9c6ef778eded63b8df924b8e07 Mon Sep 17 00:00:00 2001 From: Danny Allen Date: Thu, 23 Jan 2020 15:27:25 -0800 Subject: [PATCH 44/63] [vs tests] Disable NAT tests to unblock Jenkins (#1179) Signed-off-by: Danny Allen --- tests/conftest.py | 6 ++++-- tests/test_nat.py | 54 ++++++++++++++++++++++------------------------- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5b74ff0c7a..82d7359d33 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -157,8 +157,10 @@ def __init__(self, name=None, imgname=None, keeptb=False, fakeplatform=None): self.syncd = ['syncd'] self.rtd = ['fpmsyncd', 'zebra'] self.teamd = ['teamsyncd', 'teammgrd'] - self.natd = ['natsyncd', 'natmgrd'] - self.alld = self.basicd + self.swssd + self.syncd + self.rtd + self.teamd + self.natd + # FIXME: We need to verify that NAT processes are running, once the + # appropriate changes are merged into sonic-buildimage + # self.natd = ['natsyncd', 'natmgrd'] + self.alld = self.basicd + self.swssd + self.syncd + self.rtd + self.teamd # + self.natd self.client = docker.from_env() if subprocess.check_call(["/sbin/modprobe", "team"]) != 0: diff --git a/tests/test_nat.py b/tests/test_nat.py index 9dea37aeb3..909d17a03e 100644 --- a/tests/test_nat.py +++ b/tests/test_nat.py @@ -6,7 +6,9 @@ import pdb import os - +# FIXME: These tests depend on changes in sonic-buildimage, we need to reenable +# them once those changes are merged. +@pytest.mark.skip(reason="Depends on changes in sonic-buildimage") class TestNatFeature(object): def setup_db(self, dvs): self.appdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) @@ -53,9 +55,9 @@ def clear_interfaces(self, dvs): def test_NatGlobalTable(self, dvs, testlog): # initialize - self.setup_db(dvs) + self.setup_db(dvs) - # enable NAT feature + # enable NAT feature dvs.runcmd("config nat feature enable") dvs.runcmd("config nat set timeout 450") dvs.runcmd("config nat set udp-timeout 360") @@ -73,28 +75,28 @@ def test_NatGlobalTable(self, dvs, testlog): def test_NatInterfaceZone(self, dvs, testlog): # initialize - self.setup_db(dvs) + self.setup_db(dvs) self.set_interfaces(dvs) - + # check NAT zone is set for interface in app db tbl = swsscommon.Table(self.appdb, "INTF_TABLE") keys = tbl.getKeys() (status, fvs) = tbl.get("Ethernet0") - assert fvs==(('NULL', 'NULL'), ('nat_zone', '1')) + assert fvs==(('NULL', 'NULL'), ('nat_zone', '1')) def test_AddNatStaticEntry(self, dvs, testlog): # initialize - self.setup_db(dvs) - + self.setup_db(dvs) + # get neighbor and arp entry dvs.servers[0].runcmd("ping -c 1 18.18.18.2") - + # add a static nat entry dvs.runcmd("config nat add static basic 67.66.65.1 18.18.18.2") - + # check the entry in the config db tbl = swsscommon.Table(self.configdb, "STATIC_NAT") entry = tbl.getKeys() @@ -102,7 +104,7 @@ def test_AddNatStaticEntry(self, dvs, testlog): (status, fvs) = tbl.get("67.66.65.1") - assert fvs==(('local_ip', '18.18.18.2'),) + assert fvs==(('local_ip', '18.18.18.2'),) # check the entry in app db tbl = swsscommon.Table(self.appdb, "NAT_TABLE") @@ -111,7 +113,7 @@ def test_AddNatStaticEntry(self, dvs, testlog): (status, fvs) = tbl.get("67.66.65.1") - assert fvs== (('translated_ip', '18.18.18.2'), ('nat_type', 'dnat'), ('entry_type', 'static')) + assert fvs== (('translated_ip', '18.18.18.2'), ('nat_type', 'dnat'), ('entry_type', 'static')) #check the entry in asic db tbl = swsscommon.Table(self.asicdb, "ASIC_STATE:SAI_OBJECT_TYPE_NAT_ENTRY") @@ -126,7 +128,7 @@ def test_AddNatStaticEntry(self, dvs, testlog): def test_DelNatStaticEntry(self, dvs, testlog): # initialize - self.setup_db(dvs) + self.setup_db(dvs) # delete a static nat entry dvs.runcmd("config nat remove static basic 67.66.65.1 18.18.18.2") @@ -148,7 +150,7 @@ def test_DelNatStaticEntry(self, dvs, testlog): def test_AddNaPtStaticEntry(self, dvs, testlog): # initialize - self.setup_db(dvs) + self.setup_db(dvs) # get neighbor and arp entry dvs.servers[0].runcmd("ping -c 1 18.18.18.2") @@ -163,7 +165,7 @@ def test_AddNaPtStaticEntry(self, dvs, testlog): (status, fvs) = tbl.get("67.66.65.1|UDP|670") - assert fvs==(('local_ip', '18.18.18.2'),('local_port', '180')) + assert fvs==(('local_ip', '18.18.18.2'),('local_port', '180')) # check the entry in app db tbl = swsscommon.Table(self.appdb, "NAPT_TABLE:UDP") @@ -178,7 +180,7 @@ def test_AddNaPtStaticEntry(self, dvs, testlog): tbl = swsscommon.Table(self.asicdb, "ASIC_STATE:SAI_OBJECT_TYPE_NAT_ENTRY") keys = tbl.getKeys() assert len(keys) == 2 - + for key in keys: if (key.find("dst_ip:67.66.65.1")) and (key.find("key.l4_dst_port:670")): assert True @@ -189,7 +191,7 @@ def test_AddNaPtStaticEntry(self, dvs, testlog): def test_DelNaPtStaticEntry(self, dvs, testlog): # initialize - self.setup_db(dvs) + self.setup_db(dvs) # delete a static nat entry dvs.runcmd("config nat remove static udp 67.66.65.1 670 18.18.18.2 180") @@ -209,10 +211,9 @@ def test_DelNaPtStaticEntry(self, dvs, testlog): key = tbl.getKeys() assert key == () - def test_AddTwiceNatEntry(self, dvs, testlog): # initialize - self.setup_db(dvs) + self.setup_db(dvs) # get neighbor and arp entry dvs.servers[0].runcmd("ping -c 1 18.18.18.2") @@ -260,7 +261,7 @@ def test_AddTwiceNatEntry(self, dvs, testlog): def test_DelTwiceNatStaticEntry(self, dvs, testlog): # initialize - self.setup_db(dvs) + self.setup_db(dvs) # delete a static nat entry dvs.runcmd("config nat remove static basic 67.66.65.2 18.18.18.1") @@ -281,10 +282,9 @@ def test_DelTwiceNatStaticEntry(self, dvs, testlog): key = tbl.getKeys() assert key == () - def test_AddTwiceNaPtEntry(self, dvs, testlog): # initialize - self.setup_db(dvs) + self.setup_db(dvs) # get neighbor and arp entry dvs.servers[0].runcmd("ping -c 1 18.18.18.2") @@ -306,8 +306,6 @@ def test_AddTwiceNaPtEntry(self, dvs, testlog): assert fvs== (('nat_type', 'snat'), ('local_ip', '18.18.18.1'),('twice_nat_id', '7'), ('local_port', '181')) - - # check the entry in app db tbl = swsscommon.Table(self.appdb, "NAPT_TWICE_TABLE") entry = tbl.getKeys() @@ -315,11 +313,11 @@ def test_AddTwiceNaPtEntry(self, dvs, testlog): (status, fvs) = tbl.get("UDP:67.66.65.2:670:67.66.65.1:660") - assert fvs== (('translated_src_ip', '18.18.18.1'), ('translated_src_l4_port', '181'), ('translated_dst_ip', '18.18.18.2'), ('translated_dst_l4_port', '182'), ('entry_type', 'static')) + assert fvs== (('translated_src_ip', '18.18.18.1'), ('translated_src_l4_port', '181'), ('translated_dst_ip', '18.18.18.2'), ('translated_dst_l4_port', '182'), ('entry_type', 'static')) (status, fvs) = tbl.get("UDP:18.18.18.2:182:18.18.18.1:181") - assert fvs== (('translated_src_ip', '67.66.65.1'), ('translated_src_l4_port', '660'),('translated_dst_ip', '67.66.65.2'),('translated_dst_l4_port', '670'), ('entry_type', 'static')) + assert fvs== (('translated_src_ip', '67.66.65.1'), ('translated_src_l4_port', '660'),('translated_dst_ip', '67.66.65.2'),('translated_dst_l4_port', '670'), ('entry_type', 'static')) #check the entry in asic db tbl = swsscommon.Table(self.asicdb, "ASIC_STATE:SAI_OBJECT_TYPE_NAT_ENTRY") @@ -333,10 +331,9 @@ def test_AddTwiceNaPtEntry(self, dvs, testlog): else: assert False - def test_DelTwiceNaPtStaticEntry(self, dvs, testlog): # initialize - self.setup_db(dvs) + self.setup_db(dvs) # delete a static nat entry dvs.runcmd("config nat remove static udp 67.66.65.2 670 18.18.18.1 181") @@ -359,4 +356,3 @@ def test_DelTwiceNaPtStaticEntry(self, dvs, testlog): # clear interfaces self.clear_interfaces(dvs) - From ce9d87906c337543f82f6b6521ca673f96ef556c Mon Sep 17 00:00:00 2001 From: Danny Allen Date: Sun, 26 Jan 2020 12:38:57 -0800 Subject: [PATCH 45/63] [debugcounterorch] Add checks for supported counter types and drop reasons (#1173) - Refactor drop reason capability query to trim SAI prefixes - Store device capabilities in orchagent to perform safety checks Fixes #1136 - Rather than depending on each ASIC vendor to follow the same error handling doctrine, this PR validates HW support in orchagent, which should be more reliable. Related to Azure/sonic-utilities#785 - In order to validate user input, we need to remove the SAI prefixes before we store the results. This removes the need for the CLI to perform these checks. Signed-off-by: Danny Allen --- orchagent/debug_counter/drop_counter.cpp | 17 +++++-- orchagent/debug_counter/drop_counter.h | 5 +- orchagent/debugcounterorch.cpp | 60 +++++++++++++++++------- orchagent/debugcounterorch.h | 4 ++ 4 files changed, 61 insertions(+), 25 deletions(-) diff --git a/orchagent/debug_counter/drop_counter.cpp b/orchagent/debug_counter/drop_counter.cpp index aab9c4b547..d68c7081cf 100644 --- a/orchagent/debug_counter/drop_counter.cpp +++ b/orchagent/debug_counter/drop_counter.cpp @@ -3,6 +3,8 @@ #include "logger.h" #include "sai_serialize.h" +#include + using std::runtime_error; using std::string; using std::unordered_map; @@ -12,6 +14,9 @@ using std::vector; extern sai_object_id_t gSwitchId; extern sai_debug_counter_api_t *sai_debug_counter_api; +#define INGRESS_DROP_REASON_PREFIX_LENGTH 19 // "SAI_IN_DROP_REASON_" +#define EGRESS_DROP_REASON_PREFIX_LENGTH 20 // "SAI_OUT_DROP_REASON_" + const unordered_map DropCounter::ingress_drop_reason_lookup = { { L2_ANY, SAI_IN_DROP_REASON_L2_ANY }, @@ -290,7 +295,7 @@ void DropCounter::updateDropReasonsInSAI() // // If the device does not support querying drop reasons, this method will // return an empty list. -vector DropCounter::getSupportedDropReasons(sai_debug_counter_attr_t drop_reason_type) +unordered_set DropCounter::getSupportedDropReasons(sai_debug_counter_attr_t drop_reason_type) { sai_s32_list_t drop_reason_list; int32_t supported_reasons[maxDropReasons]; @@ -306,20 +311,22 @@ vector DropCounter::getSupportedDropReasons(sai_debug_counter_attr_t dro return {}; } - vector supported_drop_reasons; + unordered_set supported_drop_reasons; for (uint32_t i = 0; i < drop_reason_list.count; i++) { string drop_reason; if (drop_reason_type == SAI_DEBUG_COUNTER_ATTR_IN_DROP_REASON_LIST) { drop_reason = sai_serialize_ingress_drop_reason(static_cast(drop_reason_list.list[i])); + drop_reason = drop_reason.substr(INGRESS_DROP_REASON_PREFIX_LENGTH); } else { drop_reason = sai_serialize_egress_drop_reason(static_cast(drop_reason_list.list[i])); + drop_reason = drop_reason.substr(EGRESS_DROP_REASON_PREFIX_LENGTH); } - supported_drop_reasons.push_back(drop_reason); + supported_drop_reasons.emplace(drop_reason); } return supported_drop_reasons; @@ -330,7 +337,7 @@ vector DropCounter::getSupportedDropReasons(sai_debug_counter_attr_t dro // // e.g. { "SMAC_EQUALS_DMAC", "INGRESS_VLAN_FILTER" } -> "["SMAC_EQUALS_DMAC","INGRESS_VLAN_FILTER"]" // e.g. { } -> "[]" -string DropCounter::serializeSupportedDropReasons(vector drop_reasons) +string DropCounter::serializeSupportedDropReasons(unordered_set drop_reasons) { if (drop_reasons.size() == 0) { @@ -338,7 +345,7 @@ string DropCounter::serializeSupportedDropReasons(vector drop_reasons) } string supported_drop_reasons; - for (auto const &drop_reason : drop_reasons) + for (auto const& drop_reason : drop_reasons) { supported_drop_reasons += ','; supported_drop_reasons += drop_reason; diff --git a/orchagent/debug_counter/drop_counter.h b/orchagent/debug_counter/drop_counter.h index 8ae34e3d4b..bc548b34c7 100644 --- a/orchagent/debug_counter/drop_counter.h +++ b/orchagent/debug_counter/drop_counter.h @@ -2,7 +2,6 @@ #define SWSS_UTIL_DROP_COUNTER_H_ #include -#include #include #include #include "debug_counter.h" @@ -33,8 +32,8 @@ class DropCounter : public DebugCounter static bool isIngressDropReasonValid(const std::string& drop_reason); static bool isEgressDropReasonValid(const std::string& drop_reason); - static std::vector getSupportedDropReasons(sai_debug_counter_attr_t drop_reason_type); - static std::string serializeSupportedDropReasons(std::vector drop_reasons); + static std::unordered_set getSupportedDropReasons(sai_debug_counter_attr_t drop_reason_type); + static std::string serializeSupportedDropReasons(std::unordered_set drop_reasons); static uint64_t getSupportedDebugCounterAmounts(sai_debug_counter_type_t counter_type); private: diff --git a/orchagent/debugcounterorch.cpp b/orchagent/debugcounterorch.cpp index fd1005d452..b4f2407ca4 100644 --- a/orchagent/debugcounterorch.cpp +++ b/orchagent/debugcounterorch.cpp @@ -181,35 +181,46 @@ void DebugCounterOrch::doTask(Consumer& consumer) // DROP_COUNTER_CAPABILITIES table in STATE_DB. void DebugCounterOrch::publishDropCounterCapabilities() { - string supported_ingress_drop_reasons = DropCounter::serializeSupportedDropReasons( - DropCounter::getSupportedDropReasons(SAI_DEBUG_COUNTER_ATTR_IN_DROP_REASON_LIST)); - string supported_egress_drop_reasons = DropCounter::serializeSupportedDropReasons( - DropCounter::getSupportedDropReasons(SAI_DEBUG_COUNTER_ATTR_OUT_DROP_REASON_LIST)); + supported_ingress_drop_reasons = DropCounter::getSupportedDropReasons(SAI_DEBUG_COUNTER_ATTR_IN_DROP_REASON_LIST); + supported_egress_drop_reasons = DropCounter::getSupportedDropReasons(SAI_DEBUG_COUNTER_ATTR_OUT_DROP_REASON_LIST); + + string ingress_drop_reason_str = DropCounter::serializeSupportedDropReasons(supported_ingress_drop_reasons); + string egress_drop_reason_str = DropCounter::serializeSupportedDropReasons(supported_egress_drop_reasons); for (auto const &counter_type : DebugCounter::getDebugCounterTypeLookup()) { - string num_counters = std::to_string(DropCounter::getSupportedDebugCounterAmounts(counter_type.second)); - string drop_reasons; if (counter_type.first == PORT_INGRESS_DROPS || counter_type.first == SWITCH_INGRESS_DROPS) { - drop_reasons = supported_ingress_drop_reasons; + drop_reasons = ingress_drop_reason_str; } else { - drop_reasons = supported_egress_drop_reasons; + drop_reasons = egress_drop_reason_str; } - // Only include available capabilities in State DB - if (num_counters != "0" && !drop_reasons.empty()) + // Don't bother publishing counters that have no drop reasons + if (drop_reasons.empty()) { - vector fieldValues; - fieldValues.push_back(FieldValueTuple("count", num_counters)); - fieldValues.push_back(FieldValueTuple("reasons", drop_reasons)); + continue; + } - SWSS_LOG_DEBUG("Setting '%s' capabilities to count='%s', reasons='%s'", counter_type.first.c_str(), num_counters.c_str(), drop_reasons.c_str()); - m_debugCapabilitiesTable->set(counter_type.first, fieldValues); + string num_counters = std::to_string(DropCounter::getSupportedDebugCounterAmounts(counter_type.second)); + + // Don't bother publishing counters that aren't available. + if (num_counters == "0") + { + continue; } + + supported_counter_types.emplace(counter_type.first); + + vector fieldValues; + fieldValues.push_back(FieldValueTuple("count", num_counters)); + fieldValues.push_back(FieldValueTuple("reasons", drop_reasons)); + + SWSS_LOG_DEBUG("Setting '%s' capabilities to count='%s', reasons='%s'", counter_type.first.c_str(), num_counters.c_str(), drop_reasons.c_str()); + m_debugCapabilitiesTable->set(counter_type.first, fieldValues); } } @@ -228,12 +239,19 @@ task_process_status DebugCounterOrch::installDebugCounter(const string& counter_ return task_process_status::task_success; } - // Note: this method currently assumes that all counters are drop counters. + // NOTE: this method currently assumes that all counters are drop counters. // If you are adding support for a non-drop counter than it may make sense // to either: a) dispatch to different handlers in doTask or b) dispatch to // different helper methods in this method. string counter_type = getDebugCounterType(attributes); + + if (supported_counter_types.find(counter_type) == supported_counter_types.end()) + { + SWSS_LOG_ERROR("Specified counter type '%s' is not supported.", counter_type.c_str()); + return task_process_status::task_failed; + } + addFreeCounter(counter_name, counter_type); reconcileFreeDropCounters(counter_name); @@ -286,7 +304,15 @@ task_process_status DebugCounterOrch::addDropReason(const string& counter_name, if (!isDropReasonValid(drop_reason)) { - return task_failed; + SWSS_LOG_ERROR("Specified drop reason '%s' is invalid.", drop_reason.c_str()); + return task_process_status::task_failed; + } + + if (supported_ingress_drop_reasons.find(drop_reason) == supported_ingress_drop_reasons.end() && + supported_egress_drop_reasons.find(drop_reason) == supported_egress_drop_reasons.end()) + { + SWSS_LOG_ERROR("Specified drop reason '%s' is not supported.", drop_reason.c_str()); + return task_process_status::task_failed; } auto it = debug_counters.find(counter_name); diff --git a/orchagent/debugcounterorch.h b/orchagent/debugcounterorch.h index 49ca64b7f7..e5b512c8e4 100644 --- a/orchagent/debugcounterorch.h +++ b/orchagent/debugcounterorch.h @@ -77,6 +77,10 @@ class DebugCounterOrch: public Orch std::shared_ptr m_counterNameToPortStatMap = nullptr; std::shared_ptr m_counterNameToSwitchStatMap = nullptr; + std::unordered_set supported_counter_types; + std::unordered_set supported_ingress_drop_reasons; + std::unordered_set supported_egress_drop_reasons; + FlexCounterStatManager flex_counter_manager; std::unordered_map> debug_counters; From eceb423624e633a4274490ed717fdc383e764db5 Mon Sep 17 00:00:00 2001 From: Tyler Li Date: Mon, 27 Jan 2020 04:42:26 +0800 Subject: [PATCH 46/63] [cfgmgr] clear loopback and vrf in kernel if not warmstart (#1141) Clear loopback interfaces in kernel at intfmgrd starting except warmstart. Clear vrf interfaces in kernel at vrfmgrd starting except warmstart. --- cfgmgr/intfmgr.cpp | 27 +++++++++++++++++++++++++++ cfgmgr/intfmgr.h | 1 + cfgmgr/intfmgrd.cpp | 4 ++++ cfgmgr/vrfmgr.cpp | 22 +++++++++++++++++++--- cfgmgr/vrfmgrd.cpp | 4 ++++ 5 files changed, 55 insertions(+), 3 deletions(-) diff --git a/cfgmgr/intfmgr.cpp b/cfgmgr/intfmgr.cpp index a897216f4e..7997f2e19b 100644 --- a/cfgmgr/intfmgr.cpp +++ b/cfgmgr/intfmgr.cpp @@ -7,6 +7,7 @@ #include "intfmgr.h" #include "exec.h" #include "shellcmd.h" +#include "warm_restart.h" using namespace std; using namespace swss; @@ -29,6 +30,10 @@ IntfMgr::IntfMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, c m_stateIntfTable(stateDb, STATE_INTERFACE_TABLE_NAME), m_appIntfTableProducer(appDb, APP_INTF_TABLE_NAME) { + if (!WarmStart::isWarmStart()) + { + flushLoopbackIntfs(); + } } void IntfMgr::setIntfIp(const string &alias, const string &opCmd, @@ -107,6 +112,28 @@ void IntfMgr::delLoopbackIntf(const string &alias) } } +void IntfMgr::flushLoopbackIntfs() +{ + stringstream cmd; + string res; + + cmd << IP_CMD << " link show type dummy | grep -o '" << LOOPBACK_PREFIX << "[^:]*'"; + + int ret = swss::exec(cmd.str(), res); + if (ret) + { + SWSS_LOG_DEBUG("Command '%s' failed with rc %d", cmd.str().c_str(), ret); + return; + } + + auto aliases = tokenize(res, '\n'); + for (string &alias : aliases) + { + SWSS_LOG_NOTICE("Remove loopback device %s", alias.c_str()); + delLoopbackIntf(alias); + } +} + int IntfMgr::getIntfIpCount(const string &alias) { stringstream cmd; diff --git a/cfgmgr/intfmgr.h b/cfgmgr/intfmgr.h index 7aab9fe447..73448b74c9 100644 --- a/cfgmgr/intfmgr.h +++ b/cfgmgr/intfmgr.h @@ -34,6 +34,7 @@ class IntfMgr : public Orch int getIntfIpCount(const std::string &alias); void addLoopbackIntf(const std::string &alias); void delLoopbackIntf(const std::string &alias); + void flushLoopbackIntfs(void); void addHostSubIntf(const std::string&intf, const std::string &subIntf, const std::string &vlan); void setHostSubIntfMtu(const std::string &subIntf, const std::string &mtu); diff --git a/cfgmgr/intfmgrd.cpp b/cfgmgr/intfmgrd.cpp index d184b66e4a..d92aff9ceb 100644 --- a/cfgmgr/intfmgrd.cpp +++ b/cfgmgr/intfmgrd.cpp @@ -8,6 +8,7 @@ #include "intfmgr.h" #include #include +#include "warm_restart.h" using namespace std; using namespace swss; @@ -52,6 +53,9 @@ int main(int argc, char **argv) DBConnector appDb("APPL_DB", 0); DBConnector stateDb("STATE_DB", 0); + WarmStart::initialize("intfmgrd", "swss"); + WarmStart::checkWarmStart("intfmgrd", "swss"); + IntfMgr intfmgr(&cfgDb, &appDb, &stateDb, cfg_intf_tables); // TODO: add tables in stateDB which interface depends on to monitor list diff --git a/cfgmgr/vrfmgr.cpp b/cfgmgr/vrfmgr.cpp index d8718813ef..f0db83a04d 100644 --- a/cfgmgr/vrfmgr.cpp +++ b/cfgmgr/vrfmgr.cpp @@ -7,6 +7,7 @@ #include "vrfmgr.h" #include "exec.h" #include "shellcmd.h" +#include "warm_restart.h" #define VRF_TABLE_START 1001 #define VRF_TABLE_END 2000 @@ -58,9 +59,24 @@ VrfMgr::VrfMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, con rowType = DETAILS_ROW; break; case DETAILS_ROW: - table = static_cast(stoul(items[6])); - m_vrfTableMap[vrfName] = table; - m_freeTables.erase(table); + if (WarmStart::isWarmStart()) + { + table = static_cast(stoul(items[6])); + m_vrfTableMap[vrfName] = table; + m_freeTables.erase(table); + } + else + { + SWSS_LOG_NOTICE("Remove vrf device %s", vrfName.c_str()); + cmd.str(""); + cmd.clear(); + cmd << IP_CMD << " link del " << vrfName; + int ret = swss::exec(cmd.str(), res); + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmd.str().c_str(), ret); + } + } rowType = LINK_ROW; break; } diff --git a/cfgmgr/vrfmgrd.cpp b/cfgmgr/vrfmgrd.cpp index 6a347896b3..c81f7bdadd 100644 --- a/cfgmgr/vrfmgrd.cpp +++ b/cfgmgr/vrfmgrd.cpp @@ -8,6 +8,7 @@ #include "vrfmgr.h" #include #include +#include "warm_restart.h" using namespace std; using namespace swss; @@ -49,6 +50,9 @@ int main(int argc, char **argv) DBConnector appDb("APPL_DB", 0); DBConnector stateDb("STATE_DB", 0); + WarmStart::initialize("vrfmgrd", "swss"); + WarmStart::checkWarmStart("vrfmgrd", "swss"); + VrfMgr vrfmgr(&cfgDb, &appDb, &stateDb, cfg_vrf_tables); // TODO: add tables in stateDB which interface depends on to monitor list From da89cf248218cbd2afed8d225a69e3f60ddd7039 Mon Sep 17 00:00:00 2001 From: shine4chen <37530989+shine4chen@users.noreply.github.com> Date: Tue, 28 Jan 2020 02:17:33 +0800 Subject: [PATCH 47/63] [Teamdmgrd]Fix "teamd cannot start when port channel was down before warm reboot" issue (#1171) * during warm-reboot teamd need to use same system-id before warm-reboot Signed-off-by: shine.chen * refine per review comment Signed-off-by: shine.chen * skip saved pdu on negotiation stage Signed-off-by: shine.chen * reformat teammgr file --- cfgmgr/teammgr.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cfgmgr/teammgr.cpp b/cfgmgr/teammgr.cpp index 6121c30f0c..2120ab033f 100644 --- a/cfgmgr/teammgr.cpp +++ b/cfgmgr/teammgr.cpp @@ -492,13 +492,19 @@ task_process_status TeamMgr::addLag(const string &alias, int min_links, bool fal while (getline(aliasfile, line)) { ifstream memberfile(dump_path + line, ios::binary); - uint8_t mac_temp[ETHER_ADDR_LEN]; + uint8_t mac_temp[ETHER_ADDR_LEN] = {0}; + uint8_t null_mac[ETHER_ADDR_LEN] = {0}; if (!memberfile.is_open()) continue; memberfile.seekg(partner_system_id_offset, std::ios::beg); memberfile.read(reinterpret_cast(mac_temp), ETHER_ADDR_LEN); + + /* During negotiation stage partner info of pdu is empty , skip it */ + if (memcmp(mac_temp, null_mac, ETHER_ADDR_LEN) == 0) + continue; + mac_boot = MacAddress(mac_temp); break; } From 6b4b6efff698376562c48372472a98f9d47af59a Mon Sep 17 00:00:00 2001 From: Danny Allen Date: Mon, 27 Jan 2020 14:48:30 -0800 Subject: [PATCH 48/63] [vs tests] Mark VLAN and warm reboot tests as xfail (#1183) Signed-off-by: Danny Allen --- tests/test_vlan.py | 8 ++++++++ tests/test_warm_reboot.py | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/tests/test_vlan.py b/tests/test_vlan.py index 4a0202dd62..4b8bb59776 100644 --- a/tests/test_vlan.py +++ b/tests/test_vlan.py @@ -424,6 +424,10 @@ def test_AddVlanWithIncorrectValueType(self, dvs, testlog, test_input, expected) #remove vlan dvs.remove_vlan(vlan) + # FIXME: This test is extremely unstable and requires several retries + # for it to pass - we need to stabilize this test before putting it back + # into the pipeline. + @pytest.mark.xfail(reason="test case is unstable") def test_AddPortChannelToVlan(self, dvs, testlog): self.setup_db(dvs) marker = dvs.add_log_marker() @@ -644,6 +648,10 @@ def test_AddMaxVlan(self, dvs, testlog): vlan_entries = [k for k in tbl.getKeys() if k != dvs.asicdb.default_vlan_id] assert len(vlan_entries) == 0 + # FIXME: This test is extremely unstable and requires several retries + # for it to pass - we need to stabilize this test before putting it back + # into the pipeline. + @pytest.mark.xfail(reason="test case is unstable") def test_RemoveVlanWithRouterInterface(self, dvs, testlog): dvs.setup_db() marker = dvs.add_log_marker() diff --git a/tests/test_warm_reboot.py b/tests/test_warm_reboot.py index fc8ca3debd..4e4e65796a 100644 --- a/tests/test_warm_reboot.py +++ b/tests/test_warm_reboot.py @@ -234,6 +234,7 @@ def ping_new_ips(dvs): dvs.runcmd(['sh', '-c', "ping -c 1 -W 0 -q {}.0.0.{} > /dev/null 2>&1".format(i*4,j+NUM_NEIGH_PER_INTF+2)]) dvs.runcmd(['sh', '-c', "ping6 -c 1 -W 0 -q {}00::{} > /dev/null 2>&1".format(i*4,j+NUM_NEIGH_PER_INTF+2)]) + class TestWarmReboot(object): def test_PortSyncdWarmRestart(self, dvs, testlog): @@ -1794,6 +1795,10 @@ def test_routing_WarmRestart(self, dvs, testlog): intf_tbl._del("{}".format(intfs[2])) time.sleep(2) + # FIXME: This test is extremely unstable and requires several retries + # for it to pass - we need to stabilize this test before putting it back + # into the pipeline. + @pytest.mark.xfail(reason="test case is unstable") def test_system_warmreboot_neighbor_syncup(self, dvs, testlog): appl_db = swsscommon.DBConnector(swsscommon.APPL_DB, dvs.redis_sock, 0) From 1130bba61e9d28e4ac8a7bdbd1f2483edb4512fa Mon Sep 17 00:00:00 2001 From: Stepan Blyshchak <38952541+stepanblyschak@users.noreply.github.com> Date: Tue, 28 Jan 2020 03:02:17 +0200 Subject: [PATCH 49/63] [restore_neighbors.py] build arp packet with correct hwsrc and psrc (#1158) Otherwise, we can see that DUT after warm reboot sends ARP packets with management IP (10.112.71.4) Signed-off-by: Stepan Blyschak --- neighsyncd/restore_neighbors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neighsyncd/restore_neighbors.py b/neighsyncd/restore_neighbors.py index 73453ce7d2..63242bf1c1 100755 --- a/neighsyncd/restore_neighbors.py +++ b/neighsyncd/restore_neighbors.py @@ -194,7 +194,7 @@ def set_neigh_in_kernel(ipclass, family, intf_idx, dst_ip, dmac): def build_arp_ns_pkt(family, smac, src_ip, dst_ip): if family == 'IPv4': eth = Ether(src=smac, dst='ff:ff:ff:ff:ff:ff') - pkt = eth/ARP(op='who-has', pdst=dst_ip) + pkt = eth/ARP(op='who-has', pdst=dst_ip, psrc=src_ip, hwsrc=smac) elif family == 'IPv6': nsma = in6_getnsma(inet_pton(AF_INET6, dst_ip)) mcast_dst_ip = inet_ntop(AF_INET6, nsma) From 739a690f5affab223753d6b465531cad5793e0a3 Mon Sep 17 00:00:00 2001 From: Sujin Kang Date: Mon, 27 Jan 2020 20:53:17 -0800 Subject: [PATCH 50/63] remove the obsoleted platforms build links (#1169) --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index a484d1a5a5..3d1ac2fc05 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -Broadcom[![Broadcom](https://sonic-jenkins.westus2.cloudapp.azure.com/job/broadcom/job/sonic-swss-build/badge/icon)](https://sonic-jenkins.westus2.cloudapp.azure.com/job/broadcom/job/sonic-swss-build/) -Mellanox[![Mellanox](https://sonic-jenkins.westus2.cloudapp.azure.com/job/mellanox/job/sonic-swss-build/badge/icon)](https://sonic-jenkins.westus2.cloudapp.azure.com/job/mellanox/job/sonic-swss-build/) VS[![VS](https://sonic-jenkins.westus2.cloudapp.azure.com/job/vs/job/sonic-swss-build/badge/icon)](https://sonic-jenkins.westus2.cloudapp.azure.com/job/vs/job/sonic-swss-build/) # SONiC - SWitch State Service - SWSS From 84261524ddb86be798029e1bad264f0e6d27df08 Mon Sep 17 00:00:00 2001 From: Stepan Blyshchak <38952541+stepanblyschak@users.noreply.github.com> Date: Tue, 28 Jan 2020 06:56:05 +0200 Subject: [PATCH 51/63] [portsorch] use ingress/egress disable for LAG member enable/disable (#1166) Originally portsorch was designed to remove LAG member from LAG when member gets disabled by teamd. This could lead to potential issues including flood to that port and loops, since after removal member becomes a switch port. Now, portsorch will make use of SAI_LAG_MEMBER_ATTR_INGRESS_DISABLE and SAI_LAG_MEMBER_ATTR_EGRESS_DISABLE to control collection/distribution through that LAG member port. With this new flow, teammgrd and teamsyncd are candidates to be refactored, especially teamsyncd warm reboot logic, since now we don't need to compare old, new lags and lag members. Teamsyncd's job is simply to signal "status" field update without waiting for reconciliation timer to expire. e.g. one port channel went down on peer: ``` admin@arc-switch1025:~$ show interfaces portchannel Flags: A - active, I - inactive, Up - up, Dw - Down, N/A - not available, S - selected, D - deselected, * - not synced No. Team Dev Protocol Ports ----- --------------- ----------- -------------- 0001 PortChannel0001 LACP(A)(Up) Ethernet112(S) 0002 PortChannel0002 LACP(A)(Up) Ethernet116(S) 0003 PortChannel0003 LACP(A)(Up) Ethernet120(S) 0004 PortChannel0004 LACP(A)(Dw) Ethernet124(D) admin@arc-switch1025:~$ docker exec -it syncd sx_api_lag_dump.py LAG Members Table =============================================================================================================== | SWID| LAG Logical Port| LAG Oper State| PVID| Member Port ID| Port Label| Collector| Distributor| Oper State| =============================================================================================================== | 0 0x10000000 UP 1| 0x11900| 29| Enable| Enable| UP| =============================================================================================================== | 0 0x10000100 UP 1| 0x11b00| 30| Enable| Enable| UP| =============================================================================================================== | 0 0x10000200 UP 1| 0x11d00| 31| Enable| Enable| UP| =============================================================================================================== | 0 0x10000300 DOWN 1| 0x11f00| 32| Disable| Disable| UP| =============================================================================================================== ``` Signed-off-by: Stepan Blyschak --- orchagent/portsorch.cpp | 94 ++++++++++++++++++++++++++++++++------- orchagent/portsorch.h | 2 + tests/test_portchannel.py | 42 ++++++++++++++--- 3 files changed, 115 insertions(+), 23 deletions(-) diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index 440c52aaf1..66750c59e8 100644 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -2472,39 +2472,51 @@ void PortsOrch::doLagMemberTask(Consumer &consumer) status = fvValue(i); } - /* Sync an enabled member */ - if (status == "enabled") + if (lag.m_members.find(port_alias) == lag.m_members.end()) { - /* Duplicate entry */ - if (lag.m_members.find(port_alias) != lag.m_members.end()) + /* Assert the port doesn't belong to any LAG already */ + assert(!port.m_lag_id && !port.m_lag_member_id); + + if (!addLagMember(lag, port)) { - it = consumer.m_toSync.erase(it); + it++; continue; } + } - /* Assert the port doesn't belong to any LAG */ - assert(!port.m_lag_id && !port.m_lag_member_id); - - if (addLagMember(lag, port)) + /* Sync an enabled member */ + if (status == "enabled") + { + /* enable collection first, distribution-only mode + * is not supported on Mellanox platform + */ + if (setCollectionOnLagMember(port, true) && + setDistributionOnLagMember(port, true)) + { it = consumer.m_toSync.erase(it); + } else + { it++; + continue; + } } /* Sync an disabled member */ else /* status == "disabled" */ { - /* "status" is "disabled" at start when m_lag_id and - * m_lag_member_id are absent */ - if (!port.m_lag_id || !port.m_lag_member_id) + /* disable distribution first, distribution-only mode + * is not supported on Mellanox platform + */ + if (setDistributionOnLagMember(port, false) && + setCollectionOnLagMember(port, false)) { it = consumer.m_toSync.erase(it); - continue; } - - if (removeLagMember(lag, port)) - it = consumer.m_toSync.erase(it); else + { it++; + continue; + } } } /* Remove a LAG member */ @@ -2522,9 +2534,13 @@ void PortsOrch::doLagMemberTask(Consumer &consumer) } if (removeLagMember(lag, port)) + { it = consumer.m_toSync.erase(it); + } else + { it++; + } } else { @@ -3318,6 +3334,52 @@ bool PortsOrch::removeLagMember(Port &lag, Port &port) return true; } +bool PortsOrch::setCollectionOnLagMember(Port &lagMember, bool enableCollection) +{ + /* Port must be LAG member */ + assert(port.m_lag_member_id); + + sai_status_t status = SAI_STATUS_FAILURE; + sai_attribute_t attr {}; + + attr.id = SAI_LAG_MEMBER_ATTR_INGRESS_DISABLE; + attr.value.booldata = !enableCollection; + + status = sai_lag_api->set_lag_member_attribute(lagMember.m_lag_member_id, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to %s collection on LAG member %s", + enableCollection ? "enable" : "disable", + lagMember.m_alias.c_str()); + return false; + } + + return true; +} + +bool PortsOrch::setDistributionOnLagMember(Port &lagMember, bool enableDistribution) +{ + /* Port must be LAG member */ + assert(port.m_lag_member_id); + + sai_status_t status = SAI_STATUS_FAILURE; + sai_attribute_t attr {}; + + attr.id = SAI_LAG_MEMBER_ATTR_EGRESS_DISABLE; + attr.value.booldata = !enableDistribution; + + status = sai_lag_api->set_lag_member_attribute(lagMember.m_lag_member_id, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to %s distribution on LAG member %s", + enableDistribution ? "enable" : "disable", + lagMember.m_alias.c_str()); + return false; + } + + return true; +} + void PortsOrch::generateQueueMap() { if (m_isQueueMapGenerated) diff --git a/orchagent/portsorch.h b/orchagent/portsorch.h index 5278589706..b35c050bfc 100644 --- a/orchagent/portsorch.h +++ b/orchagent/portsorch.h @@ -172,6 +172,8 @@ class PortsOrch : public Orch, public Subject bool removeLag(Port lag); bool addLagMember(Port &lag, Port &port); bool removeLagMember(Port &lag, Port &port); + bool setCollectionOnLagMember(Port &lagMember, bool enableCollection); + bool setDistributionOnLagMember(Port &lagMember, bool enableDistribution); void getLagMember(Port &lag, vector &portv); bool addPort(const set &lane_set, uint32_t speed, int an=0, string fec=""); diff --git a/tests/test_portchannel.py b/tests/test_portchannel.py index 0bc6db1e2c..d95a2e5eed 100644 --- a/tests/test_portchannel.py +++ b/tests/test_portchannel.py @@ -33,13 +33,41 @@ def test_Portchannel(self, dvs, testlog): assert len(lagms) == 1 (status, fvs) = lagmtbl.get(lagms[0]) - for fv in fvs: - if fv[0] == "SAI_LAG_MEMBER_ATTR_LAG_ID": - assert fv[1] == lags[0] - elif fv[0] == "SAI_LAG_MEMBER_ATTR_PORT_ID": - assert dvs.asicdb.portoidmap[fv[1]] == "Ethernet0" - else: - assert False + fvs = dict(fvs) + assert status + assert "SAI_LAG_MEMBER_ATTR_LAG_ID" in fvs + assert fvs.pop("SAI_LAG_MEMBER_ATTR_LAG_ID") == lags[0] + assert "SAI_LAG_MEMBER_ATTR_PORT_ID" in fvs + assert dvs.asicdb.portoidmap[fvs.pop("SAI_LAG_MEMBER_ATTR_PORT_ID")] == "Ethernet0" + assert "SAI_LAG_MEMBER_ATTR_INGRESS_DISABLE" in fvs + assert fvs.pop("SAI_LAG_MEMBER_ATTR_INGRESS_DISABLE") == "false" + assert "SAI_LAG_MEMBER_ATTR_EGRESS_DISABLE" in fvs + assert fvs.pop("SAI_LAG_MEMBER_ATTR_EGRESS_DISABLE") == "false" + assert not fvs + + ps = swsscommon.ProducerStateTable(db, "LAG_MEMBER_TABLE") + fvs = swsscommon.FieldValuePairs([("status", "disabled")]) + + ps.set("PortChannel0001:Ethernet0", fvs) + + time.sleep(1) + + lagmtbl = swsscommon.Table(asicdb, "ASIC_STATE:SAI_OBJECT_TYPE_LAG_MEMBER") + lagms = lagmtbl.getKeys() + assert len(lagms) == 1 + + (status, fvs) = lagmtbl.get(lagms[0]) + fvs = dict(fvs) + assert status + assert "SAI_LAG_MEMBER_ATTR_LAG_ID" in fvs + assert fvs.pop("SAI_LAG_MEMBER_ATTR_LAG_ID") == lags[0] + assert "SAI_LAG_MEMBER_ATTR_PORT_ID" in fvs + assert dvs.asicdb.portoidmap[fvs.pop("SAI_LAG_MEMBER_ATTR_PORT_ID")] == "Ethernet0" + assert "SAI_LAG_MEMBER_ATTR_INGRESS_DISABLE" in fvs + assert fvs.pop("SAI_LAG_MEMBER_ATTR_INGRESS_DISABLE") == "true" + assert "SAI_LAG_MEMBER_ATTR_EGRESS_DISABLE" in fvs + assert fvs.pop("SAI_LAG_MEMBER_ATTR_EGRESS_DISABLE") == "true" + assert not fvs # remove port channel member ps = swsscommon.ProducerStateTable(db, "LAG_MEMBER_TABLE") From 9a5f95cbe8e76a318485150f75b71e14d6b1a1ff Mon Sep 17 00:00:00 2001 From: Myron Sosyak <49795530+msosyak@users.noreply.github.com> Date: Mon, 27 Jan 2020 20:57:04 -0800 Subject: [PATCH 52/63] Enable MIRRORv6 for BFN platform (#1177) --- orchagent/aclorch.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index 76062bfec3..9bb6d786bd 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -2072,6 +2072,7 @@ void AclOrch::init(vector& connectors, PortsOrch *portOrch, Mirr string platform = getenv("platform") ? getenv("platform") : ""; if (platform == BRCM_PLATFORM_SUBSTRING || platform == MLNX_PLATFORM_SUBSTRING || + platform == BFN_PLATFORM_SUBSTRING || platform == NPS_PLATFORM_SUBSTRING) { m_mirrorTableCapabilities = @@ -2102,7 +2103,8 @@ void AclOrch::init(vector& connectors, PortsOrch *portOrch, Mirr } // In Mellanox platform, V4 and V6 rules are stored in different tables - if (platform == MLNX_PLATFORM_SUBSTRING) { + if (platform == MLNX_PLATFORM_SUBSTRING || + platform == BFN_PLATFORM_SUBSTRING) { m_isCombinedMirrorV6Table = false; } From 11fe6b520693c31c5e1398274035e103d2977a31 Mon Sep 17 00:00:00 2001 From: Danny Allen Date: Mon, 27 Jan 2020 20:58:29 -0800 Subject: [PATCH 53/63] [vs tests] Refactor fdb_update tests to be consistent with vs test suite (#1185) Signed-off-by: Danny Allen --- tests/test_fdb_update.py | 515 +++++++++++++++++++-------------------- 1 file changed, 257 insertions(+), 258 deletions(-) diff --git a/tests/test_fdb_update.py b/tests/test_fdb_update.py index d74c3f0c0f..1771ac5aea 100644 --- a/tests/test_fdb_update.py +++ b/tests/test_fdb_update.py @@ -5,261 +5,260 @@ import json from distutils.version import StrictVersion -def create_entry(tbl, key, pairs): - fvs = swsscommon.FieldValuePairs(pairs) - tbl.set(key, fvs) - - # FIXME: better to wait until DB create them - time.sleep(1) -def remove_entry(tbl, key): - tbl._del(key) - time.sleep(1) - -def create_entry_tbl(db, table, key, pairs): - tbl = swsscommon.Table(db, table) - create_entry(tbl, key, pairs) - -def remove_entry_tbl(db, table, key): - tbl = swsscommon.Table(db, table) - remove_entry(tbl, key) - -def create_entry_pst(db, table, key, pairs): - tbl = swsscommon.ProducerStateTable(db, table) - create_entry(tbl, key, pairs) - -def remove_entry_pst(db, table, key): - tbl = swsscommon.ProducerStateTable(db, table) - remove_entry(tbl, key) - -def how_many_entries_exist(db, table): - tbl = swsscommon.Table(db, table) - return len(tbl.getKeys()) - -def get_mac_by_bridge_id(dvs, bridge_id): - tbl = swsscommon.Table(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") - keys = tbl.getKeys() - - mac = [] - for key in keys: - (status, fvs) = tbl.get(key) - assert status - value = dict(fvs) - if value["SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID"] == bridge_id: - try: - d_key = json.loads(key) - except ValueError: - d_key = json.loads('{' + key + '}') - mac.append(d_key["mac"]) - return mac - -def test_FDBAddedAndUpdated(dvs, testlog): - dvs.setup_db() - - dvs.runcmd("sonic-clear fdb all") - time.sleep(2) - - # create a FDB entry in Application DB - create_entry_pst( - dvs.pdb, - "FDB_TABLE", "Vlan2:52-54-00-25-06-E9", - [ - ("port", "Ethernet0"), - ("type", "dynamic"), - ] - ) - - # check that the FDB entry wasn't inserted into ASIC DB - assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 0, "The fdb entry leaked to ASIC" - - vlan_before = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") - bp_before = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT") - vm_before = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN_MEMBER") - - # create vlan - create_entry_tbl( - dvs.cdb, - "VLAN", "Vlan2", - [ - ("vlanid", "2"), - ] - ) - - # create vlan member entry in Config db - create_entry_tbl( - dvs.cdb, - "VLAN_MEMBER", "Vlan2|Ethernet0", - [ - ("tagging_mode", "untagged"), - ] - ) - - # check that the vlan information was propagated - vlan_after = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") - bp_after = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT") - vm_after = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN_MEMBER") - - assert vlan_after - vlan_before == 1, "The Vlan2 wasn't created" - assert bp_after - bp_before == 1, "The bridge port wasn't created" - assert vm_after - vm_before == 1, "The vlan member wasn't added" - - # Get bvid from vlanid - ok, bvid = dvs.get_vlan_oid(dvs.adb, "2") - assert ok, bvid - - # Get mapping between interface name and its bridge port_id - iface_2_bridge_port_id = dvs.get_map_iface_bridge_port_id(dvs.adb) - - # check that the FDB entry was inserted into ASIC DB - assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 1, "The fdb entry wasn't inserted to ASIC" - - ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", - [("mac", "52:54:00:25:06:E9"), ("bvid", bvid)], - [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_DYNAMIC"), - ("SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID", iface_2_bridge_port_id["Ethernet0"]), - ('SAI_FDB_ENTRY_ATTR_PACKET_ACTION', 'SAI_PACKET_ACTION_FORWARD')] - ) - assert ok, str(extra) - - # create vlan member entry in Config DB - create_entry_tbl( - dvs.cdb, - "VLAN_MEMBER", "Vlan2|Ethernet4", - [ - ("tagging_mode", "untagged"), - ] - ) - - # update FDB entry port in Application DB - create_entry_pst( - dvs.pdb, - "FDB_TABLE", "Vlan2:52-54-00-25-06-E9", - [ - ("port", "Ethernet4"), - ("type", "dynamic"), - ] - ) - - # Get mapping between interface name and its bridge port_id - iface_2_bridge_port_id = dvs.get_map_iface_bridge_port_id(dvs.adb) - - ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", - [("mac", "52:54:00:25:06:E9"), ("bvid", bvid)], - [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_DYNAMIC"), - ("SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID", iface_2_bridge_port_id["Ethernet4"]), - ('SAI_FDB_ENTRY_ATTR_PACKET_ACTION', 'SAI_PACKET_ACTION_FORWARD')] - ) - assert ok, str(extra) - - # remove FDB entry from Application DB - remove_entry_pst( - dvs.pdb, - "FDB_TABLE", "Vlan2:52-54-00-25-06-E9" - ) - - # remove vlan member entry from Config DB - remove_entry_tbl( - dvs.cdb, - "VLAN_MEMBER", "Vlan2|Ethernet4" - ) - remove_entry_tbl( - dvs.cdb, - "VLAN_MEMBER", "Vlan2|Ethernet0" - ) - - #remove vlan entry from Config DB - remove_entry_tbl( - dvs.cdb, - "VLAN", "Vlan2" - ) - -def test_FDBLearnedAndUpdated(dvs, testlog): - dvs.setup_db() - - dvs.runcmd("sonic-clear fdb all") - - # create vlan; create vlan member - dvs.create_vlan("6") - dvs.create_vlan_member("6", "Ethernet64") - dvs.create_vlan_member("6", "Ethernet68") - dvs.create_vlan_member("6", "Ethernet72") - - # Get mapping between interface name and its bridge port_id - time.sleep(2) - iface_2_bridge_port_id = dvs.get_map_iface_bridge_port_id(dvs.adb) - - # bring up vlan and member - dvs.set_interface_status("Vlan6", "up") - - dvs.add_ip_address("Vlan6", "6.6.6.1/24") - - dvs.set_interface_status("Ethernet64", "up") - dvs.set_interface_status("Ethernet68", "up") - dvs.set_interface_status("Ethernet72", "up") - - dvs.servers[16].runcmd("ifconfig eth0 hw ether 00:00:00:00:00:11") - dvs.servers[16].runcmd("ifconfig eth0 6.6.6.6/24 up") - dvs.servers[16].runcmd("ip route add default via 6.6.6.1") - dvs.servers[17].runcmd("ifconfig eth0 6.6.6.7/24 up") - dvs.servers[17].runcmd("ip route add default via 6.6.6.1") - time.sleep(2) - - # get neighbor and arp entry - rc = dvs.servers[16].runcmd("ping -c 1 6.6.6.7") - assert rc == 0 - rc = dvs.servers[17].runcmd("ping -c 1 6.6.6.6") - assert rc == 0 - time.sleep(2) - - # check that the FDB entries were inserted into ASIC DB - Ethernet64_mac = get_mac_by_bridge_id(dvs, iface_2_bridge_port_id["Ethernet64"]) - assert "00:00:00:00:00:11" in Ethernet64_mac - - # update FDB entry port in Application DB - create_entry_pst( - dvs.pdb, - "FDB_TABLE", "Vlan6:00-00-00-00-00-11", - [ - ("port", "Ethernet72"), - ("type", "dynamic"), - ] - ) - - # check that the FDB entry was updated in ASIC DB - Ethernet72_mac = get_mac_by_bridge_id(dvs, iface_2_bridge_port_id["Ethernet72"]) - assert "00:00:00:00:00:11" in Ethernet72_mac, "Updating fdb entry to Ethernet72 failed" - - Ethernet64_mac = get_mac_by_bridge_id(dvs, iface_2_bridge_port_id["Ethernet64"]) - assert "00:00:00:00:00:11" not in Ethernet64_mac, "Updating fdb entry from Ethernet64 failed" - - # remove FDB entry from Application DB - remove_entry_pst( - dvs.pdb, - "FDB_TABLE", "Vlan6:00-00-00-00-00-11" - ) - - # restore the default value of the servers - dvs.servers[16].runcmd("ip route del default via 6.6.6.1") - dvs.servers[16].runcmd("ifconfig eth0 0") - dvs.servers[17].runcmd("ip route del default via 6.6.6.1") - dvs.servers[17].runcmd("ifconfig eth0 0") - - # bring down port - dvs.set_interface_status("Ethernet64", "down") - dvs.set_interface_status("Ethernet68", "down") - dvs.set_interface_status("Ethernet72", "down") - - # remove vlan ip - key = "Vlan6" + "|" + "6.6.6.1/24" - remove_entry_tbl(dvs.cdb, "VLAN_INTERFACE", key) - - # bring down vlan - dvs.set_interface_status("Vlan6", "down") - - # remove vlan member; remove vlan - dvs.remove_vlan_member("6", "Ethernet64") - dvs.remove_vlan_member("6", "Ethernet68") - dvs.remove_vlan_member("6", "Ethernet72") - dvs.remove_vlan("6") - - # clear fdb - dvs.runcmd("sonic-clear fdb all") + +class TestFdbUpdate(object): + def create_entry(self, tbl, key, pairs): + fvs = swsscommon.FieldValuePairs(pairs) + tbl.set(key, fvs) + time.sleep(1) + + def remove_entry(self, tbl, key): + tbl._del(key) + time.sleep(1) + + def create_entry_tbl(self, db, table, key, pairs): + tbl = swsscommon.Table(db, table) + self.create_entry(tbl, key, pairs) + + def remove_entry_tbl(self, db, table, key): + tbl = swsscommon.Table(db, table) + self.remove_entry(tbl, key) + + def create_entry_pst(self, db, table, key, pairs): + tbl = swsscommon.ProducerStateTable(db, table) + self.create_entry(tbl, key, pairs) + + def remove_entry_pst(self, db, table, key): + tbl = swsscommon.ProducerStateTable(db, table) + self.remove_entry(tbl, key) + + def how_many_entries_exist(self, db, table): + tbl = swsscommon.Table(db, table) + return len(tbl.getKeys()) + + def get_mac_by_bridge_id(self, dvs, bridge_id): + tbl = swsscommon.Table(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") + keys = tbl.getKeys() + + mac = [] + for key in keys: + (status, fvs) = tbl.get(key) + assert status + value = dict(fvs) + if value["SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID"] == bridge_id: + try: + d_key = json.loads(key) + except ValueError: + d_key = json.loads('{' + key + '}') + mac.append(d_key["mac"]) + return mac + + def test_FDBAddedAndUpdated(self, dvs, testlog): + dvs.setup_db() + + dvs.runcmd("sonic-clear fdb all") + time.sleep(2) + + # create a FDB entry in Application DB + self.create_entry_pst( + dvs.pdb, + "FDB_TABLE", "Vlan2:52-54-00-25-06-E9", + [ + ("port", "Ethernet0"), + ("type", "dynamic"), + ] + ) + + # check that the FDB entry wasn't inserted into ASIC DB + assert self.how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 0, "The fdb entry leaked to ASIC" + + vlan_before = self.how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + bp_before = self.how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT") + vm_before = self.how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN_MEMBER") + + # create vlan + self.create_entry_tbl( + dvs.cdb, + "VLAN", "Vlan2", + [ + ("vlanid", "2"), + ] + ) + + # create vlan member entry in Config db + self.create_entry_tbl( + dvs.cdb, + "VLAN_MEMBER", "Vlan2|Ethernet0", + [ + ("tagging_mode", "untagged"), + ] + ) + + # check that the vlan information was propagated + vlan_after = self.how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + bp_after = self.how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT") + vm_after = self.how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN_MEMBER") + + assert vlan_after - vlan_before == 1, "The Vlan2 wasn't created" + assert bp_after - bp_before == 1, "The bridge port wasn't created" + assert vm_after - vm_before == 1, "The vlan member wasn't added" + + # Get bvid from vlanid + ok, bvid = dvs.get_vlan_oid(dvs.adb, "2") + assert ok, bvid + + # Get mapping between interface name and its bridge port_id + iface_2_bridge_port_id = dvs.get_map_iface_bridge_port_id(dvs.adb) + + # check that the FDB entry was inserted into ASIC DB + assert self.how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 1, "The fdb entry wasn't inserted to ASIC" + + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "52:54:00:25:06:E9"), ("bvid", bvid)], + [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_DYNAMIC"), + ("SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID", iface_2_bridge_port_id["Ethernet0"]), + ('SAI_FDB_ENTRY_ATTR_PACKET_ACTION', 'SAI_PACKET_ACTION_FORWARD')]) + assert ok, str(extra) + + # create vlan member entry in Config DB + self.create_entry_tbl( + dvs.cdb, + "VLAN_MEMBER", "Vlan2|Ethernet4", + [ + ("tagging_mode", "untagged"), + ] + ) + + # update FDB entry port in Application DB + self.create_entry_pst( + dvs.pdb, + "FDB_TABLE", "Vlan2:52-54-00-25-06-E9", + [ + ("port", "Ethernet4"), + ("type", "dynamic"), + ] + ) + + # Get mapping between interface name and its bridge port_id + iface_2_bridge_port_id = dvs.get_map_iface_bridge_port_id(dvs.adb) + + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "52:54:00:25:06:E9"), ("bvid", bvid)], + [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_DYNAMIC"), + ("SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID", iface_2_bridge_port_id["Ethernet4"]), + ('SAI_FDB_ENTRY_ATTR_PACKET_ACTION', 'SAI_PACKET_ACTION_FORWARD')]) + assert ok, str(extra) + + # remove FDB entry from Application DB + self.remove_entry_pst( + dvs.pdb, + "FDB_TABLE", "Vlan2:52-54-00-25-06-E9" + ) + + # remove vlan member entry from Config DB + self.remove_entry_tbl( + dvs.cdb, + "VLAN_MEMBER", "Vlan2|Ethernet4" + ) + self.remove_entry_tbl( + dvs.cdb, + "VLAN_MEMBER", "Vlan2|Ethernet0" + ) + + # remove vlan entry from Config DB + self.remove_entry_tbl( + dvs.cdb, + "VLAN", "Vlan2" + ) + + def test_FDBLearnedAndUpdated(self, dvs, testlog): + dvs.setup_db() + + dvs.runcmd("sonic-clear fdb all") + + # create vlan; create vlan member + dvs.create_vlan("6") + dvs.create_vlan_member("6", "Ethernet64") + dvs.create_vlan_member("6", "Ethernet68") + dvs.create_vlan_member("6", "Ethernet72") + + # Get mapping between interface name and its bridge port_id + time.sleep(2) + iface_2_bridge_port_id = dvs.get_map_iface_bridge_port_id(dvs.adb) + + # bring up vlan and member + dvs.set_interface_status("Vlan6", "up") + + dvs.add_ip_address("Vlan6", "6.6.6.1/24") + + dvs.set_interface_status("Ethernet64", "up") + dvs.set_interface_status("Ethernet68", "up") + dvs.set_interface_status("Ethernet72", "up") + + dvs.servers[16].runcmd("ifconfig eth0 hw ether 00:00:00:00:00:11") + dvs.servers[16].runcmd("ifconfig eth0 6.6.6.6/24 up") + dvs.servers[16].runcmd("ip route add default via 6.6.6.1") + dvs.servers[17].runcmd("ifconfig eth0 6.6.6.7/24 up") + dvs.servers[17].runcmd("ip route add default via 6.6.6.1") + time.sleep(2) + + # get neighbor and arp entry + rc = dvs.servers[16].runcmd("ping -c 1 6.6.6.7") + assert rc == 0 + rc = dvs.servers[17].runcmd("ping -c 1 6.6.6.6") + assert rc == 0 + time.sleep(2) + + # check that the FDB entries were inserted into ASIC DB + Ethernet64_mac = self.get_mac_by_bridge_id(dvs, iface_2_bridge_port_id["Ethernet64"]) + assert "00:00:00:00:00:11" in Ethernet64_mac + + # update FDB entry port in Application DB + self.create_entry_pst( + dvs.pdb, + "FDB_TABLE", "Vlan6:00-00-00-00-00-11", + [ + ("port", "Ethernet72"), + ("type", "dynamic"), + ] + ) + + # check that the FDB entry was updated in ASIC DB + Ethernet72_mac = self.get_mac_by_bridge_id(dvs, iface_2_bridge_port_id["Ethernet72"]) + assert "00:00:00:00:00:11" in Ethernet72_mac, "Updating fdb entry to Ethernet72 failed" + + Ethernet64_mac = self.get_mac_by_bridge_id(dvs, iface_2_bridge_port_id["Ethernet64"]) + assert "00:00:00:00:00:11" not in Ethernet64_mac, "Updating fdb entry from Ethernet64 failed" + + # remove FDB entry from Application DB + self.remove_entry_pst( + dvs.pdb, + "FDB_TABLE", "Vlan6:00-00-00-00-00-11" + ) + + # restore the default value of the servers + dvs.servers[16].runcmd("ip route del default via 6.6.6.1") + dvs.servers[16].runcmd("ifconfig eth0 0") + dvs.servers[17].runcmd("ip route del default via 6.6.6.1") + dvs.servers[17].runcmd("ifconfig eth0 0") + + # bring down port + dvs.set_interface_status("Ethernet64", "down") + dvs.set_interface_status("Ethernet68", "down") + dvs.set_interface_status("Ethernet72", "down") + + # remove vlan ip + key = "Vlan6" + "|" + "6.6.6.1/24" + self.remove_entry_tbl(dvs.cdb, "VLAN_INTERFACE", key) + + # bring down vlan + dvs.set_interface_status("Vlan6", "down") + + # remove vlan member; remove vlan + dvs.remove_vlan_member("6", "Ethernet64") + dvs.remove_vlan_member("6", "Ethernet68") + dvs.remove_vlan_member("6", "Ethernet72") + dvs.remove_vlan("6") + + # clear fdb + dvs.runcmd("sonic-clear fdb all") From 5eb1ea3267deac7287cc5817d1e6239fa7cca1d0 Mon Sep 17 00:00:00 2001 From: zhenggen-xu Date: Tue, 28 Jan 2020 10:15:04 -0800 Subject: [PATCH 54/63] [qosorch]: Remove Init Color ACLs (#1084) InitSystemAcl table is removed. ECN ACL rules: DSCP 0/8 mapping to yellow are removed. Signed-off-by: Zhenggen Xu --- orchagent/qosorch.cpp | 131 ------------------------------------------ orchagent/qosorch.h | 5 -- tests/conftest.py | 4 +- 3 files changed, 2 insertions(+), 138 deletions(-) diff --git a/orchagent/qosorch.cpp b/orchagent/qosorch.cpp index a3a317a986..e7761cd5d6 100644 --- a/orchagent/qosorch.cpp +++ b/orchagent/qosorch.cpp @@ -729,20 +729,6 @@ QosOrch::QosOrch(DBConnector *db, vector &tableNames) : Orch(db, tableNa { SWSS_LOG_ENTER(); - // we should really introduce capability query in SAI so that we can first - // query the capability and then decide what to do instead of hardcoding the - // platform-specfic logic like this here, which is ugly and difficult to - // understand the underlying rationale. - - // Do not create color ACL on p4 platform as it does not support match dscp and ecn - char *platform = getenv("platform"); - if (!platform || - (platform && strcmp(platform, "x86_64-barefoot_p4-r0") != 0)) - { - // add ACLs to support Sonic WRED profile. - initColorAcl(); // FIXME: Should be removed as soon as we have ACL configuration support - } - initTableHandlers(); }; @@ -752,123 +738,6 @@ type_map& QosOrch::getTypeMap() return m_qos_maps; } -void QosOrch::initColorAcl() -{ - SWSS_LOG_ENTER(); - sai_object_id_t acl_table_id; - - // init ACL system table - acl_table_id = initSystemAclTable(); - - // Add entry to match packets with dscp=8, ecn=0 and set yellow color to them - initAclEntryForEcn(acl_table_id, 1000, 0x00, 0x08, SAI_PACKET_COLOR_YELLOW); - // Add entry to match packets with dscp=0, ecn=0 and set yellow color to them - initAclEntryForEcn(acl_table_id, 999, 0x00, 0x00, SAI_PACKET_COLOR_YELLOW); -} - -sai_object_id_t QosOrch::initSystemAclTable() -{ - SWSS_LOG_ENTER(); - vector attrs; - sai_attribute_t attr; - sai_object_id_t acl_table_id; - sai_status_t status; - - /* Create system ACL table */ - attr.id = SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST; - vector bpoint_list; - bpoint_list.push_back(SAI_ACL_BIND_POINT_TYPE_PORT); - attr.value.s32list.count = 1; - attr.value.s32list.list = bpoint_list.data(); - attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; - attr.value.s32 = SAI_ACL_STAGE_INGRESS; - attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ECN; - attr.value.booldata = true; - attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DSCP; - attr.value.booldata = true; - attrs.push_back(attr); - - status = sai_acl_api->create_acl_table(&acl_table_id, gSwitchId, (uint32_t)attrs.size(), attrs.data()); - if (status != SAI_STATUS_SUCCESS) - { - SWSS_LOG_ERROR("Failed to create a system ACL table for ECN coloring, rv:%d", status); - throw runtime_error("Failed to create a system ACL table for ECN coloring"); - } - SWSS_LOG_NOTICE("Create a system ACL table for ECN coloring"); - - gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, SAI_ACL_STAGE_INGRESS, SAI_ACL_BIND_POINT_TYPE_PORT); - - for (auto& pair: gPortsOrch->getAllPorts()) - { - auto& port = pair.second; - if (port.m_type != Port::PHY) continue; - - sai_object_id_t group_member_oid; - // Note: group member OID is discarded - if (!gPortsOrch->bindAclTable(port.m_port_id, acl_table_id, group_member_oid)) - { - SWSS_LOG_ERROR("Failed to bind the system ACL table globally, rv:%d", status); - throw runtime_error("Failed to bind the system ACL table globally"); - } - } - - - SWSS_LOG_NOTICE("Bind the system ACL table globally"); - - return acl_table_id; -} - -void QosOrch::initAclEntryForEcn(sai_object_id_t acl_table_id, sai_uint32_t priority, - sai_uint8_t ecn_field, sai_uint8_t dscp_field, sai_int32_t color) -{ - SWSS_LOG_ENTER(); - vector attrs; - sai_attribute_t attr; - sai_object_id_t acl_entry_id; - sai_status_t status; - - attr.id = SAI_ACL_ENTRY_ATTR_TABLE_ID; - attr.value.oid = acl_table_id; - attrs.push_back(attr); - - attr.id = SAI_ACL_ENTRY_ATTR_PRIORITY; - attr.value.u32 = priority; - attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ECN; - attr.value.aclfield.enable = true; - attr.value.aclfield.data.u8 = ecn_field; - attr.value.aclfield.mask.u8 = 0x3; - attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DSCP; - attr.value.aclfield.enable = true; - attr.value.aclfield.data.u8 = dscp_field; - attr.value.aclfield.mask.u8 = 0x3f; - attrs.push_back(attr); - - attr.id = SAI_ACL_ENTRY_ATTR_ACTION_SET_PACKET_COLOR; - attr.value.aclaction.enable = true; - attr.value.aclaction.parameter.s32 = color; - attrs.push_back(attr); - - status = sai_acl_api->create_acl_entry(&acl_entry_id, gSwitchId, (uint32_t)attrs.size(), attrs.data()); - if (status != SAI_STATUS_SUCCESS) - { - SWSS_LOG_ERROR("Failed to create a system ACL entry for ECN coloring, rv=%d", status); - throw runtime_error("Failed to create a system ACL entry for ECN coloring"); - } - SWSS_LOG_INFO("Create a system ACL entry for ECN coloring"); - - gCrmOrch->incCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_ENTRY, acl_table_id); -} - void QosOrch::initTableHandlers() { SWSS_LOG_ENTER(); diff --git a/orchagent/qosorch.h b/orchagent/qosorch.h index 88ed96d914..29101d9051 100644 --- a/orchagent/qosorch.h +++ b/orchagent/qosorch.h @@ -134,11 +134,6 @@ class QosOrch : public Orch typedef map qos_table_handler_map; typedef pair qos_handler_pair; - void initColorAcl(); - sai_object_id_t initSystemAclTable(); - void initAclEntryForEcn(sai_object_id_t acl_table_id, sai_uint32_t priority, - sai_uint8_t ecn_field, sai_uint8_t dscp_field, sai_int32_t color); - void initTableHandlers(); task_process_status handleDscpToTcTable(Consumer& consumer); diff --git a/tests/conftest.py b/tests/conftest.py index 82d7359d33..16c0ad10b7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -66,13 +66,13 @@ def __init__(self, dvs): atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_TABLE") keys = atbl.getKeys() - assert len(keys) >= 1 + assert len(keys) >= 0 self.default_acl_tables = keys atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_ENTRY") keys = atbl.getKeys() - assert len(keys) == 2 + assert len(keys) == 0 self.default_acl_entries = keys class ApplDbValidator(object): From 84415f6d607050232a5e6d4d61d003353734428d Mon Sep 17 00:00:00 2001 From: Danny Allen Date: Tue, 28 Jan 2020 14:50:25 -0800 Subject: [PATCH 55/63] [vs tests] Activate flaky plugin for virtual switch pytests (#1186) Signed-off-by: Danny Allen --- tests/README.md | 5 +++-- tests/test_acl.py | 8 +++++++- tests/test_acl_ctrl.py | 6 +++++- tests/test_acl_egress_table.py | 6 +++++- tests/test_acl_portchannel.py | 6 +++++- tests/test_admin_status.py | 7 ++++++- tests/test_crm.py | 6 +++++- tests/test_dirbcast.py | 7 ++++++- tests/test_drop_counters.py | 8 ++++++-- tests/test_dtel.py | 13 ++++++++----- tests/test_fdb.py | 7 ++++++- tests/test_fdb_update.py | 6 +++++- tests/test_interface.py | 8 ++++++-- tests/test_mirror.py | 5 +++-- tests/test_mirror_ipv6_combined.py | 5 +++-- tests/test_mirror_ipv6_separate.py | 3 ++- tests/test_mirror_policer.py | 5 +++-- tests/test_nat.py | 5 ++++- tests/test_neighbor.py | 8 ++++++-- tests/test_nhg.py | 7 ++++++- tests/test_pfc.py | 6 +++++- tests/test_policer.py | 3 +++ tests/test_port.py | 8 ++++++-- tests/test_port_an.py | 7 ++++++- tests/test_port_buffer_rel.py | 7 ++++++- tests/test_port_config.py | 7 ++++++- tests/test_port_mac_learn.py | 8 ++++++-- tests/test_portchannel.py | 7 ++++++- tests/test_qos_map.py | 3 +++ tests/test_route.py | 7 ++++++- tests/test_setro.py | 7 ++++++- tests/test_sflow.py | 7 +++++-- tests/test_speed.py | 12 ++++++------ tests/test_sub_port_intf.py | 3 +++ tests/test_switch.py | 12 +++++++----- tests/test_tunnel.py | 8 +++++++- tests/test_vlan.py | 5 ++++- tests/test_vnet.py | 5 ++++- tests/test_vnet_bitmap.py | 7 ++++++- tests/test_vrf.py | 7 ++++++- tests/test_vxlan_tunnel.py | 6 +++++- tests/test_warm_reboot.py | 6 ++++-- tests/test_watermark.py | 4 +++- 43 files changed, 219 insertions(+), 64 deletions(-) diff --git a/tests/README.md b/tests/README.md index 0f621aae2a..57d9768ed2 100644 --- a/tests/README.md +++ b/tests/README.md @@ -8,8 +8,9 @@ SWSS Integration tests runs on docker-sonic-vs which runs on top of SAI virtual - Install docker and pytest on your dev machine ``` - sudo pip install --system docker==3.5.0 - sudo pip install --system pytest==3.3.0 docker redis + sudo pip install docker==3.5.0 + sudo pip install pytest==3.3.0 + sudo pip install flaky docker redis ``` - Compile and install swss common library. Follow instructions [here](https://github.com/Azure/sonic-swss-common/blob/master/README.md) to first install prerequisites to build swss common library. ``` diff --git a/tests/test_acl.py b/tests/test_acl.py index 43316bd34c..9fb1d3df17 100644 --- a/tests/test_acl.py +++ b/tests/test_acl.py @@ -1,7 +1,10 @@ -from swsscommon import swsscommon import time import re import json +import pytest + +from swsscommon import swsscommon +from flaky import flaky class BaseTestAcl(object): @@ -185,6 +188,7 @@ def verify_acl_rule(self, dvs, field, value): assert False +@pytest.mark.flaky class TestAcl(BaseTestAcl): def test_AclTableCreation(self, dvs, testlog): self.setup_db(dvs) @@ -1370,6 +1374,8 @@ def test_AclRuleRedirectToNexthop(self, dvs, testlog): # bring down interface dvs.set_interface_status("Ethernet4", "down") + +@pytest.mark.flaky class TestAclRuleValidation(BaseTestAcl): """ Test class for cases that check if orchagent corectly validates ACL rules input diff --git a/tests/test_acl_ctrl.py b/tests/test_acl_ctrl.py index 20cb4e0e9a..304066313a 100644 --- a/tests/test_acl_ctrl.py +++ b/tests/test_acl_ctrl.py @@ -1,7 +1,11 @@ +import time +import pytest + from swsscommon import swsscommon +from flaky import flaky -import time +@pytest.mark.flaky class TestPortChannelAcl(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_acl_egress_table.py b/tests/test_acl_egress_table.py index e1d1f4f861..d4ed9a7094 100644 --- a/tests/test_acl_egress_table.py +++ b/tests/test_acl_egress_table.py @@ -1,7 +1,11 @@ +import time +import pytest + from swsscommon import swsscommon +from flaky import flaky -import time +@pytest.mark.flaky class TestEgressAclTable(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_acl_portchannel.py b/tests/test_acl_portchannel.py index 522cabdcb6..435ea32745 100644 --- a/tests/test_acl_portchannel.py +++ b/tests/test_acl_portchannel.py @@ -1,7 +1,11 @@ +import time +import pytest + from swsscommon import swsscommon +from flaky import flaky -import time +@pytest.mark.flaky class TestPortChannelAcl(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_admin_status.py b/tests/test_admin_status.py index 73312a0b76..0c9eef1bc2 100644 --- a/tests/test_admin_status.py +++ b/tests/test_admin_status.py @@ -1,6 +1,11 @@ -from swsscommon import swsscommon import time +import pytest + +from swsscommon import swsscommon +from flaky import flaky + +@pytest.mark.flaky class TestAdminStatus(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_crm.py b/tests/test_crm.py index 5fe74d12a6..bbe3b7880e 100644 --- a/tests/test_crm.py +++ b/tests/test_crm.py @@ -1,10 +1,12 @@ -from swsscommon import swsscommon import os import re import time import json import redis +import pytest +from swsscommon import swsscommon +from flaky import flaky def getCrmCounterValue(dvs, key, counter): @@ -62,6 +64,8 @@ def check_syslog(dvs, marker, err_log, expected_cnt): (exitcode, num) = dvs.runcmd(['sh', '-c', "awk \'/%s/,ENDFILE {print;}\' /var/log/syslog | grep \"%s\" | wc -l" % (marker, err_log)]) assert num.strip() >= str(expected_cnt) + +@pytest.mark.flaky class TestCrm(object): def test_CrmFdbEntry(self, dvs, testlog): diff --git a/tests/test_dirbcast.py b/tests/test_dirbcast.py index 4f966d44cd..0c98bd567a 100644 --- a/tests/test_dirbcast.py +++ b/tests/test_dirbcast.py @@ -1,8 +1,13 @@ -from swsscommon import swsscommon import time import re import json +import pytest + +from swsscommon import swsscommon +from flaky import flaky + +@pytest.mark.flaky class TestDirectedBroadcast(object): def test_DirectedBroadcast(self, dvs, testlog): diff --git a/tests/test_drop_counters.py b/tests/test_drop_counters.py index 473c20885f..d120e75f1a 100644 --- a/tests/test_drop_counters.py +++ b/tests/test_drop_counters.py @@ -1,6 +1,8 @@ -from swsscommon import swsscommon - import time +import pytest + +from swsscommon import swsscommon +from flaky import flaky # Supported drop counters PORT_INGRESS_DROPS = 'PORT_INGRESS_DROPS' @@ -53,9 +55,11 @@ EXPECTED_ASIC_FIELDS = [ASIC_COUNTER_TYPE_FIELD, ASIC_COUNTER_INGRESS_REASON_LIST_FIELD, ASIC_COUNTER_EGRESS_REASON_LIST_FIELD] EXPECTED_NUM_ASIC_FIELDS = 2 + # FIXME: It is really annoying to have to re-run tests due to inconsistent timing, should # implement some sort of polling interface for checking ASIC/flex counter tables after # applying changes to config DB +@pytest.mark.flaky class TestDropCounters(object): def setup_db(self, dvs): self.asic_db = swsscommon.DBConnector(1, dvs.redis_sock, 0) diff --git a/tests/test_dtel.py b/tests/test_dtel.py index a31d7af321..e1be7f449b 100644 --- a/tests/test_dtel.py +++ b/tests/test_dtel.py @@ -1,16 +1,19 @@ -from swsscommon import swsscommon - - import time import re import json +import pytest + +from swsscommon import swsscommon +from flaky import flaky + +@pytest.mark.flaky class TestDtel(object): def test_DtelGlobalAttribs(self, dvs, testlog): - + db = swsscommon.DBConnector(4, dvs.redis_sock, 0) adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) - + # create DTel global attributes in config db tbl = swsscommon.Table(db, "DTEL") diff --git a/tests/test_fdb.py b/tests/test_fdb.py index 37c210c492..aa03e79476 100644 --- a/tests/test_fdb.py +++ b/tests/test_fdb.py @@ -1,8 +1,11 @@ -from swsscommon import swsscommon import os import sys import time import json +import pytest + +from swsscommon import swsscommon +from flaky import flaky from distutils.version import StrictVersion def create_entry(tbl, key, pairs): @@ -24,6 +27,8 @@ def how_many_entries_exist(db, table): tbl = swsscommon.Table(db, table) return len(tbl.getKeys()) + +@pytest.mark.flaky class TestFdb(object): def test_FdbWarmRestartNotifications(self, dvs, testlog): dvs.setup_db() diff --git a/tests/test_fdb_update.py b/tests/test_fdb_update.py index 1771ac5aea..001b278d1e 100644 --- a/tests/test_fdb_update.py +++ b/tests/test_fdb_update.py @@ -1,11 +1,15 @@ -from swsscommon import swsscommon import os import sys import time import json +import pytest + +from swsscommon import swsscommon +from flaky import flaky from distutils.version import StrictVersion +@pytest.mark.flaky class TestFdbUpdate(object): def create_entry(self, tbl, key, pairs): fvs = swsscommon.FieldValuePairs(pairs) diff --git a/tests/test_interface.py b/tests/test_interface.py index 5abe7eb9d0..bf66f66322 100644 --- a/tests/test_interface.py +++ b/tests/test_interface.py @@ -1,8 +1,12 @@ -from swsscommon import swsscommon - import time import json +import pytest + +from swsscommon import swsscommon +from flaky import flaky + +@pytest.mark.flaky class TestRouterInterface(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_mirror.py b/tests/test_mirror.py index 80db250755..cc41b3c882 100644 --- a/tests/test_mirror.py +++ b/tests/test_mirror.py @@ -1,13 +1,14 @@ # This test suite covers the functionality of mirror feature in SwSS - import platform import pytest import time -from distutils.version import StrictVersion from swsscommon import swsscommon +from flaky import flaky +from distutils.version import StrictVersion +@pytest.mark.flaky class TestMirror(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_mirror_ipv6_combined.py b/tests/test_mirror_ipv6_combined.py index b3187dda85..8ad5813600 100644 --- a/tests/test_mirror_ipv6_combined.py +++ b/tests/test_mirror_ipv6_combined.py @@ -1,15 +1,16 @@ # This test suite covers the functionality of mirror feature in SwSS - import platform import pytest import time -from distutils.version import StrictVersion from swsscommon import swsscommon +from flaky import flaky +from distutils.version import StrictVersion DVS_FAKE_PLATFORM = "broadcom" +@pytest.mark.flaky class TestMirror(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_mirror_ipv6_separate.py b/tests/test_mirror_ipv6_separate.py index 06f64e8691..195094c03c 100644 --- a/tests/test_mirror_ipv6_separate.py +++ b/tests/test_mirror_ipv6_separate.py @@ -1,15 +1,16 @@ # This test suite covers the functionality of mirror feature in SwSS - import platform import pytest import time from distutils.version import StrictVersion from swsscommon import swsscommon +from flaky import flaky DVS_FAKE_PLATFORM = "mellanox" +@pytest.mark.flaky class TestMirror(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_mirror_policer.py b/tests/test_mirror_policer.py index e9c5c5baa6..1434ae2a62 100644 --- a/tests/test_mirror_policer.py +++ b/tests/test_mirror_policer.py @@ -1,13 +1,14 @@ # This test suite covers the functionality of mirror feature in SwSS - import platform import pytest import time -from distutils.version import StrictVersion from swsscommon import swsscommon +from flaky import flaky +from distutils.version import StrictVersion +@pytest.mark.flaky class TestMirror(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_nat.py b/tests/test_nat.py index 909d17a03e..058cd7f251 100644 --- a/tests/test_nat.py +++ b/tests/test_nat.py @@ -1,4 +1,3 @@ -from swsscommon import swsscommon import time import re import json @@ -6,6 +5,10 @@ import pdb import os +from swsscommon import swsscommon +from flaky import flaky + + # FIXME: These tests depend on changes in sonic-buildimage, we need to reenable # them once those changes are merged. @pytest.mark.skip(reason="Depends on changes in sonic-buildimage") diff --git a/tests/test_neighbor.py b/tests/test_neighbor.py index 4cf2196334..d083dcf7c8 100644 --- a/tests/test_neighbor.py +++ b/tests/test_neighbor.py @@ -1,8 +1,12 @@ -from swsscommon import swsscommon - import time import json +import pytest + +from swsscommon import swsscommon +from flaky import flaky + +@pytest.mark.flaky class TestNeighbor(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_nhg.py b/tests/test_nhg.py index a2bbe5fd1f..7d1ba2b66b 100644 --- a/tests/test_nhg.py +++ b/tests/test_nhg.py @@ -1,9 +1,14 @@ -from swsscommon import swsscommon import os import re import time import json +import pytest + +from swsscommon import swsscommon +from flaky import flaky + +@pytest.mark.flaky class TestNextHopGroup(object): def test_route_nhg(self, dvs, testlog): config_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) diff --git a/tests/test_pfc.py b/tests/test_pfc.py index 9a59adf20b..569724c420 100644 --- a/tests/test_pfc.py +++ b/tests/test_pfc.py @@ -1,6 +1,8 @@ import time -from swsscommon import swsscommon +import pytest +from swsscommon import swsscommon +from flaky import flaky def getBitMaskStr(bits): @@ -57,6 +59,8 @@ def getPortAttr(dvs, port_oid, port_attr): return '' + +@pytest.mark.flaky class TestPfc(object): def test_PfcAsymmetric(self, dvs, testlog): diff --git a/tests/test_policer.py b/tests/test_policer.py index 4ab455869f..53fdc37930 100644 --- a/tests/test_policer.py +++ b/tests/test_policer.py @@ -3,7 +3,10 @@ import time from swsscommon import swsscommon +from flaky import flaky + +@pytest.mark.flaky class TestPolicer(object): def test_PolicerBasic(self, dvs, testlog): dvs.setup_db() diff --git a/tests/test_port.py b/tests/test_port.py index edf459301c..cd0e416ed3 100644 --- a/tests/test_port.py +++ b/tests/test_port.py @@ -1,8 +1,12 @@ -from swsscommon import swsscommon - import time import os +import pytest + +from swsscommon import swsscommon +from flaky import flaky + +@pytest.mark.flaky class TestPort(object): def test_PortMtu(self, dvs, testlog): pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_port_an.py b/tests/test_port_an.py index 67ce59f317..b9081df85b 100644 --- a/tests/test_port_an.py +++ b/tests/test_port_an.py @@ -1,7 +1,12 @@ -from swsscommon import swsscommon import time import os +import pytest + +from swsscommon import swsscommon +from flaky import flaky + +@pytest.mark.flaky class TestPortAutoNeg(object): def test_PortAutoNegCold(self, dvs, testlog): diff --git a/tests/test_port_buffer_rel.py b/tests/test_port_buffer_rel.py index ed21660562..411923071a 100644 --- a/tests/test_port_buffer_rel.py +++ b/tests/test_port_buffer_rel.py @@ -1,7 +1,12 @@ -from swsscommon import swsscommon import time +import pytest + +from swsscommon import swsscommon +from flaky import flaky + # The test check that the ports will be up, when the admin state is UP by conf db. +@pytest.mark.flaky class TestPortBuffer(object): def test_PortsAreUpAfterBuffers(self, dvs, testlog): num_ports = 32 diff --git a/tests/test_port_config.py b/tests/test_port_config.py index 6fa2d82a92..7f7c69c1a0 100644 --- a/tests/test_port_config.py +++ b/tests/test_port_config.py @@ -1,9 +1,12 @@ -from swsscommon import swsscommon import redis import time import os import pytest +from swsscommon import swsscommon +from flaky import flaky + + @pytest.yield_fixture def port_config(request, dvs): file_name = "/usr/share/sonic/hwsku/port_config.ini" @@ -11,6 +14,8 @@ def port_config(request, dvs): yield file_name dvs.runcmd("mv %s.bak %s" % (file_name, file_name)) + +@pytest.mark.flaky class TestPortConfig(object): def getPortName(self, dvs, port_vid): diff --git a/tests/test_port_mac_learn.py b/tests/test_port_mac_learn.py index 2abf4291ac..b9258b0f36 100644 --- a/tests/test_port_mac_learn.py +++ b/tests/test_port_mac_learn.py @@ -1,8 +1,12 @@ -from swsscommon import swsscommon - import time import os +import pytest + +from swsscommon import swsscommon +from flaky import flaky + +@pytest.mark.flaky class TestPortMacLearn(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(swsscommon.APPL_DB, dvs.redis_sock, 0) diff --git a/tests/test_portchannel.py b/tests/test_portchannel.py index d95a2e5eed..c048c4ba59 100644 --- a/tests/test_portchannel.py +++ b/tests/test_portchannel.py @@ -1,8 +1,13 @@ -from swsscommon import swsscommon import time import re import json +import pytest + +from swsscommon import swsscommon +from flaky import flaky + +@pytest.mark.flaky class TestPortchannel(object): def test_Portchannel(self, dvs, testlog): diff --git a/tests/test_qos_map.py b/tests/test_qos_map.py index 03ff0c63e3..4dd3d3f577 100644 --- a/tests/test_qos_map.py +++ b/tests/test_qos_map.py @@ -2,7 +2,9 @@ import json import sys import time + from swsscommon import swsscommon +from flaky import flaky CFG_DOT1P_TO_TC_MAP_TABLE_NAME = "DOT1P_TO_TC_MAP" CFG_DOT1P_TO_TC_MAP_KEY = "AZURE" @@ -22,6 +24,7 @@ CFG_PORT_TABLE_NAME = "PORT" +@pytest.mark.flaky class TestDot1p(object): def connect_dbs(self, dvs): self.asic_db = swsscommon.DBConnector(1, dvs.redis_sock, 0) diff --git a/tests/test_route.py b/tests/test_route.py index e1c12857e3..f016e798c0 100644 --- a/tests/test_route.py +++ b/tests/test_route.py @@ -1,9 +1,14 @@ -from swsscommon import swsscommon import os import re import time import json +import pytest + +from swsscommon import swsscommon +from flaky import flaky + +@pytest.mark.flaky class TestRoute(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_setro.py b/tests/test_setro.py index 84b004a878..5fbb77f605 100644 --- a/tests/test_setro.py +++ b/tests/test_setro.py @@ -1,9 +1,14 @@ -from swsscommon import swsscommon import time import json import redis +import pytest + from pprint import pprint +from swsscommon import swsscommon +from flaky import flaky + +@pytest.mark.flaky class TestSetRo(object): def test_SetReadOnlyAttribute(self, dvs, testlog): diff --git a/tests/test_sflow.py b/tests/test_sflow.py index 7ab49a3332..37fbd56f03 100644 --- a/tests/test_sflow.py +++ b/tests/test_sflow.py @@ -1,9 +1,12 @@ -from swsscommon import swsscommon - import time import os +import pytest + +from swsscommon import swsscommon +from flaky import flaky +@pytest.mark.flaky class TestSflow(object): speed_rate_table = { "400000":"40000", diff --git a/tests/test_speed.py b/tests/test_speed.py index 765383b60a..82952df81f 100644 --- a/tests/test_speed.py +++ b/tests/test_speed.py @@ -1,18 +1,18 @@ """ test_speed.py implements list of tests to check speed set on interfaces and correct buffer manager behavior on speed change - - These tests need to be run in prepared environment and with the - SONiC version compiled for PLATFORM=vs - - See README.md for details """ -from swsscommon import swsscommon import time import re import json import os +import pytest + +from swsscommon import swsscommon +from flaky import flaky + +@pytest.mark.flaky class TestSpeedSet(object): num_ports = 32 def test_SpeedAndBufferSet(self, dvs, testlog): diff --git a/tests/test_sub_port_intf.py b/tests/test_sub_port_intf.py index 36268e8f80..5ea2198c48 100644 --- a/tests/test_sub_port_intf.py +++ b/tests/test_sub_port_intf.py @@ -1,7 +1,9 @@ import pytest import time import json + from swsscommon import swsscommon +from flaky import flaky CFG_VLAN_SUB_INTF_TABLE_NAME = "VLAN_SUB_INTERFACE" CFG_PORT_TABLE_NAME = "PORT" @@ -17,6 +19,7 @@ ADMIN_STATUS = "admin_status" +@pytest.mark.flaky class TestSubPortIntf(object): PHYSICAL_PORT_UNDER_TEST = "Ethernet64" SUB_PORT_INTERFACE_UNDER_TEST = "Ethernet64.10" diff --git a/tests/test_switch.py b/tests/test_switch.py index a5070f7b1f..13430e9402 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -1,5 +1,8 @@ -from swsscommon import swsscommon import time +import pytest + +from swsscommon import swsscommon +from flaky import flaky def create_entry(tbl, key, pairs): @@ -59,15 +62,14 @@ def vxlan_switch_test(dvs, oid, port, mac): ) +@pytest.mark.flaky class TestSwitch(object): - ''' Test- Check switch attributes ''' def test_switch_attribute(self, dvs, testlog): switch_oid = get_exist_entry(dvs, "ASIC_STATE:SAI_OBJECT_TYPE_SWITCH") - + vxlan_switch_test(dvs, switch_oid, "12345", "00:01:02:03:04:05") - - vxlan_switch_test(dvs, switch_oid, "56789", "00:0A:0B:0C:0D:0E") + vxlan_switch_test(dvs, switch_oid, "56789", "00:0A:0B:0C:0D:0E") diff --git a/tests/test_tunnel.py b/tests/test_tunnel.py index 7aae9f01f1..5880a57b03 100644 --- a/tests/test_tunnel.py +++ b/tests/test_tunnel.py @@ -1,9 +1,15 @@ -from swsscommon import swsscommon import time +import pytest + +from swsscommon import swsscommon +from flaky import flaky + def create_fvs(**kwargs): return swsscommon.FieldValuePairs(kwargs.items()) + +@pytest.mark.flaky class TestTunnelBase(object): APP_TUNNEL_DECAP_TABLE_NAME = "TUNNEL_DECAP_TABLE" ASIC_TUNNEL_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL" diff --git a/tests/test_vlan.py b/tests/test_vlan.py index 4b8bb59776..9b16acd797 100644 --- a/tests/test_vlan.py +++ b/tests/test_vlan.py @@ -1,12 +1,15 @@ -from swsscommon import swsscommon import time import re import json import pytest import platform + +from swsscommon import swsscommon +from flaky import flaky from distutils.version import StrictVersion +@pytest.mark.flaky class TestVlan(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_vnet.py b/tests/test_vnet.py index 76c8c27267..9574c32226 100644 --- a/tests/test_vnet.py +++ b/tests/test_vnet.py @@ -1,9 +1,11 @@ -from swsscommon import swsscommon import time import json import random import time import pytest + +from swsscommon import swsscommon +from flaky import flaky from pprint import pprint @@ -950,6 +952,7 @@ def check_del_vnet_routes(self, dvs, name): self.vnet_bitmap_route_ids.remove(old_bitmap_route[0]) +@pytest.mark.flaky class TestVnetOrch(object): def get_vnet_obj(self): diff --git a/tests/test_vnet_bitmap.py b/tests/test_vnet_bitmap.py index d8f7c94fa1..ac456b279a 100644 --- a/tests/test_vnet_bitmap.py +++ b/tests/test_vnet_bitmap.py @@ -1,7 +1,11 @@ -from swsscommon import swsscommon import time +import pytest + import test_vnet as vnet +from swsscommon import swsscommon +from flaky import flaky + # Define fake platform for "DVS" fixture, so it will set "platform" environment variable for "orchagent". # It is needed in order to enable platform specific "orchagent" code for testing "bitmap" VNET implementation. @@ -14,6 +18,7 @@ Difference between these two implementations is in set SAI attributes, so different values should be checked in ASIC_DB. This class should override "get_vnet_obj()" method in order to return object with appropriate implementation of "check" APIs. ''' +@pytest.mark.flaky class TestVnetBitmapOrch(vnet.TestVnetOrch): ''' diff --git a/tests/test_vrf.py b/tests/test_vrf.py index d8d6c9595b..ff2c2841a8 100644 --- a/tests/test_vrf.py +++ b/tests/test_vrf.py @@ -1,9 +1,14 @@ -from swsscommon import swsscommon import time import json import random +import pytest + +from swsscommon import swsscommon +from flaky import flaky from pprint import pprint + +@pytest.mark.flaky class TestVrf(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_vxlan_tunnel.py b/tests/test_vxlan_tunnel.py index 225a913bb6..ff533b74c7 100644 --- a/tests/test_vxlan_tunnel.py +++ b/tests/test_vxlan_tunnel.py @@ -1,9 +1,11 @@ -from swsscommon import swsscommon import time import json import random import time import pytest + +from swsscommon import swsscommon +from flaky import flaky from pprint import pprint @@ -241,6 +243,8 @@ def get_lo(dvs): return lo_id + +@pytest.mark.flaky class TestVxlan(object): def test_vxlan_term_orch(self, dvs, testlog): tunnel_map_ids = set() diff --git a/tests/test_warm_reboot.py b/tests/test_warm_reboot.py index 4e4e65796a..5f94654fbd 100644 --- a/tests/test_warm_reboot.py +++ b/tests/test_warm_reboot.py @@ -1,11 +1,12 @@ - -from swsscommon import swsscommon import os import re import time import json import pytest +from swsscommon import swsscommon +from flaky import flaky + # macros for number of interfaces and number of neighbors # TBD: NUM_NEIGH_PER_INTF >= 128 ips will cause test framework to hang by default kernel settings @@ -235,6 +236,7 @@ def ping_new_ips(dvs): dvs.runcmd(['sh', '-c', "ping6 -c 1 -W 0 -q {}00::{} > /dev/null 2>&1".format(i*4,j+NUM_NEIGH_PER_INTF+2)]) +@pytest.mark.flaky class TestWarmReboot(object): def test_PortSyncdWarmRestart(self, dvs, testlog): diff --git a/tests/test_watermark.py b/tests/test_watermark.py index 378d183e93..fd79dd8dfd 100644 --- a/tests/test_watermark.py +++ b/tests/test_watermark.py @@ -1,4 +1,3 @@ -from swsscommon import swsscommon import os import re import time @@ -6,6 +5,8 @@ import pytest import redis +from swsscommon import swsscommon +from flaky import flaky class SaiWmStats: queue_shared = "SAI_QUEUE_STAT_SHARED_WATERMARK_BYTES" @@ -19,6 +20,7 @@ class WmTables: user = "USER_WATERMARKS" +@pytest.mark.flaky class TestWatermark(object): DEFAULT_TELEMETRY_INTERVAL = 120 From 8f4c54a4c9a5e6e0d73c560d95c81cb44a50ac4d Mon Sep 17 00:00:00 2001 From: Sabareesh-Kumar-Anandan <59681634+Sabareesh-Kumar-Anandan@users.noreply.github.com> Date: Wed, 29 Jan 2020 10:38:02 +0530 Subject: [PATCH 56/63] [qosorch] converting shaper bandwidth value to unsigned long instead of int (#1167) For higher shaper rates greater than 20Gbps, integer is not sufficient. So I changed the conversion function to stoul() Signed-off-by: Sabareesh Kumar Anandan --- orchagent/qosorch.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/orchagent/qosorch.cpp b/orchagent/qosorch.cpp index e7761cd5d6..390324a1a6 100644 --- a/orchagent/qosorch.cpp +++ b/orchagent/qosorch.cpp @@ -816,25 +816,25 @@ task_process_status QosOrch::handleSchedulerTable(Consumer& consumer) else if (fvField(*i) == scheduler_min_bandwidth_rate_field_name) { attr.id = SAI_SCHEDULER_ATTR_MIN_BANDWIDTH_RATE; - attr.value.u64 = (uint64_t)stoi(fvValue(*i)); + attr.value.u64 = stoul(fvValue(*i)); sai_attr_list.push_back(attr); } else if (fvField(*i) == scheduler_min_bandwidth_burst_rate_field_name) { attr.id = SAI_SCHEDULER_ATTR_MIN_BANDWIDTH_BURST_RATE; - attr.value.u64 = (uint64_t)stoi(fvValue(*i)); + attr.value.u64 = stoul(fvValue(*i)); sai_attr_list.push_back(attr); } else if (fvField(*i) == scheduler_max_bandwidth_rate_field_name) { attr.id = SAI_SCHEDULER_ATTR_MAX_BANDWIDTH_RATE; - attr.value.u64 = (uint64_t)stoi(fvValue(*i)); + attr.value.u64 = stoul(fvValue(*i)); sai_attr_list.push_back(attr); } else if (fvField(*i) == scheduler_max_bandwidth_burst_rate_field_name) { attr.id = SAI_SCHEDULER_ATTR_MAX_BANDWIDTH_BURST_RATE; - attr.value.u64 = (uint64_t)stoi(fvValue(*i)); + attr.value.u64 = stoul(fvValue(*i)); sai_attr_list.push_back(attr); } else { From c6a8a040e5cb3e4dd52c5d6b30d4e9bea3e25cb3 Mon Sep 17 00:00:00 2001 From: shine4chen <37530989+shine4chen@users.noreply.github.com> Date: Wed, 29 Jan 2020 13:13:09 +0800 Subject: [PATCH 57/63] [aclorch]: add support for acl rule to match out port (#810) ACL rule can match out port. Out port could be port intf , lag or vlan. Signed-off-by: shine.chen --- orchagent/aclorch.cpp | 48 +++++++++- orchagent/aclorch.h | 12 ++- orchagent/orchdaemon.cpp | 6 +- tests/test_acl_mclag.py | 199 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 259 insertions(+), 6 deletions(-) create mode 100644 tests/test_acl_mclag.py diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index 9bb6d786bd..09a5778c91 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -107,7 +107,8 @@ static acl_table_type_lookup_t aclTableTypeLookUp = { TABLE_TYPE_MIRROR_DSCP, ACL_TABLE_MIRROR_DSCP }, { TABLE_TYPE_CTRLPLANE, ACL_TABLE_CTRLPLANE }, { TABLE_TYPE_DTEL_FLOW_WATCHLIST, ACL_TABLE_DTEL_FLOW_WATCHLIST }, - { TABLE_TYPE_DTEL_DROP_WATCHLIST, ACL_TABLE_DTEL_DROP_WATCHLIST } + { TABLE_TYPE_DTEL_DROP_WATCHLIST, ACL_TABLE_DTEL_DROP_WATCHLIST }, + { TABLE_TYPE_MCLAG, ACL_TABLE_MCLAG } }; static acl_stage_type_lookup_t aclStageLookUp = @@ -659,7 +660,8 @@ shared_ptr AclRule::makeShared(acl_table_type_t type, AclOrch *acl, Mir type != ACL_TABLE_MIRRORV6 && type != ACL_TABLE_MIRROR_DSCP && type != ACL_TABLE_DTEL_FLOW_WATCHLIST && - type != ACL_TABLE_DTEL_DROP_WATCHLIST) + type != ACL_TABLE_DTEL_DROP_WATCHLIST && + type != ACL_TABLE_MCLAG) { throw runtime_error("Unknown table type"); } @@ -703,6 +705,10 @@ shared_ptr AclRule::makeShared(acl_table_type_t type, AclOrch *acl, Mir throw runtime_error("DTel feature is not enabled. Watchlists cannot be configured"); } } + else if (type == ACL_TABLE_MCLAG) + { + return make_shared(acl, rule, table, type); + } throw runtime_error("Wrong combination of table type and action in rule " + rule); } @@ -1230,6 +1236,33 @@ void AclRuleMirror::update(SubjectType type, void *cntx) } } +AclRuleMclag::AclRuleMclag(AclOrch *aclOrch, string rule, string table, acl_table_type_t type, bool createCounter) : + AclRuleL3(aclOrch, rule, table, type, createCounter) +{ +} + +bool AclRuleMclag::validateAddMatch(string attr_name, string attr_value) +{ + if (attr_name != MATCH_IP_TYPE && attr_name != MATCH_OUT_PORTS) + { + return false; + } + + return AclRule::validateAddMatch(attr_name, attr_value); +} + +bool AclRuleMclag::validate() +{ + SWSS_LOG_ENTER(); + + if (m_matches.size() == 0) + { + return false; + } + + return true; +} + bool AclTable::validate() { if (type == ACL_TABLE_CTRLPLANE) @@ -1455,6 +1488,13 @@ bool AclTable::create() table_attrs.push_back(attr); } + if (type == ACL_TABLE_MCLAG) + { + attr.id = SAI_ACL_TABLE_ATTR_FIELD_OUT_PORTS; + attr.value.booldata = true; + table_attrs.push_back(attr); + } + sai_status_t status = sai_acl_api->create_acl_table(&m_oid, gSwitchId, (uint32_t)table_attrs.size(), table_attrs.data()); if (status == SAI_STATUS_SUCCESS) @@ -2454,12 +2494,12 @@ void AclOrch::doTask(Consumer &consumer) string table_name = consumer.getTableName(); - if (table_name == CFG_ACL_TABLE_TABLE_NAME) + if (table_name == CFG_ACL_TABLE_TABLE_NAME || table_name == APP_ACL_TABLE_TABLE_NAME) { unique_lock lock(m_countersMutex); doAclTableTask(consumer); } - else if (table_name == CFG_ACL_RULE_TABLE_NAME) + else if (table_name == CFG_ACL_RULE_TABLE_NAME || table_name == APP_ACL_RULE_TABLE_NAME) { unique_lock lock(m_countersMutex); doAclRuleTask(consumer); diff --git a/orchagent/aclorch.h b/orchagent/aclorch.h index 1df91cea59..202d369c87 100644 --- a/orchagent/aclorch.h +++ b/orchagent/aclorch.h @@ -33,6 +33,7 @@ #define TABLE_TYPE_CTRLPLANE "CTRLPLANE" #define TABLE_TYPE_DTEL_FLOW_WATCHLIST "DTEL_FLOW_WATCHLIST" #define TABLE_TYPE_DTEL_DROP_WATCHLIST "DTEL_DROP_WATCHLIST" +#define TABLE_TYPE_MCLAG "MCLAG" #define RULE_PRIORITY "PRIORITY" #define MATCH_IN_PORTS "IN_PORTS" @@ -111,7 +112,8 @@ typedef enum ACL_TABLE_PFCWD, ACL_TABLE_CTRLPLANE, ACL_TABLE_DTEL_FLOW_WATCHLIST, - ACL_TABLE_DTEL_DROP_WATCHLIST + ACL_TABLE_DTEL_DROP_WATCHLIST, + ACL_TABLE_MCLAG } acl_table_type_t; typedef map acl_table_type_lookup_t; @@ -317,6 +319,14 @@ class AclRuleDTelDropWatchListEntry: public AclRule DTelOrch *m_pDTelOrch; }; +class AclRuleMclag: public AclRuleL3 +{ +public: + AclRuleMclag(AclOrch *m_pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter = false); + bool validateAddMatch(string attr_name, string attr_value); + bool validate(); +}; + class AclTable { sai_object_id_t m_oid; AclOrch *m_pAclOrch; diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 6782a16087..26ff1f0301 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -176,10 +176,14 @@ bool OrchDaemon::init() TableConnector confDbAclTable(m_configDb, CFG_ACL_TABLE_TABLE_NAME); TableConnector confDbAclRuleTable(m_configDb, CFG_ACL_RULE_TABLE_NAME); + TableConnector appDbAclTable(m_applDb, APP_ACL_TABLE_TABLE_NAME); + TableConnector appDbAclRuleTable(m_applDb, APP_ACL_RULE_TABLE_NAME); vector acl_table_connectors = { confDbAclTable, - confDbAclRuleTable + confDbAclRuleTable, + appDbAclTable, + appDbAclRuleTable }; vector dtel_tables = { diff --git a/tests/test_acl_mclag.py b/tests/test_acl_mclag.py new file mode 100644 index 0000000000..59bca1e1a1 --- /dev/null +++ b/tests/test_acl_mclag.py @@ -0,0 +1,199 @@ +from swsscommon import swsscommon +import time +import re +import json + +class TestMclagAcl(object): + def setup_db(self, dvs): + self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) + self.adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) + + def create_entry(self, tbl, key, pairs): + fvs = swsscommon.FieldValuePairs(pairs) + tbl.set(key, fvs) + time.sleep(1) + + def remove_entry(self, tbl, key): + tbl._del(key) + time.sleep(1) + + def create_entry_tbl(self, db, table, key, pairs): + tbl = swsscommon.Table(db, table) + self.create_entry(tbl, key, pairs) + + def remove_entry_tbl(self, db, table, key): + tbl = swsscommon.Table(db, table) + self.remove_entry(tbl, key) + + def create_entry_pst(self, db, table, key, pairs): + tbl = swsscommon.ProducerStateTable(db, table) + self.create_entry(tbl, key, pairs) + + def remove_entry_pst(self, db, table, key): + tbl = swsscommon.ProducerStateTable(db, table) + self.remove_entry(tbl, key) + + def get_acl_table_id(self, dvs): + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_TABLE") + keys = tbl.getKeys() + + for k in dvs.asicdb.default_acl_tables: + assert k in keys + + acl_tables = [k for k in keys if k not in dvs.asicdb.default_acl_tables] + if len(acl_tables) == 1: + return acl_tables[0] + else: + return None + + def verify_acl_group_num(self, expt): + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_TABLE_GROUP") + acl_table_groups = atbl.getKeys() + assert len(acl_table_groups) == expt + + for k in acl_table_groups: + (status, fvs) = atbl.get(k) + assert status == True + for fv in fvs: + if fv[0] == "SAI_ACL_TABLE_GROUP_ATTR_ACL_STAGE": + assert fv[1] == "SAI_ACL_STAGE_INGRESS" + elif fv[0] == "SAI_ACL_TABLE_GROUP_ATTR_ACL_BIND_POINT_TYPE_LIST": + assert fv[1] == "1:SAI_ACL_BIND_POINT_TYPE_PORT" + elif fv[0] == "SAI_ACL_TABLE_GROUP_ATTR_TYPE": + assert fv[1] == "SAI_ACL_TABLE_GROUP_TYPE_PARALLEL" + else: + assert False + + def verify_acl_group_member(self, acl_group_ids, acl_table_id): + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER") + keys = atbl.getKeys() + + member_groups = [] + for k in keys: + (status, fvs) = atbl.get(k) + assert status == True + assert len(fvs) == 3 + for fv in fvs: + if fv[0] == "SAI_ACL_TABLE_GROUP_MEMBER_ATTR_ACL_TABLE_GROUP_ID": + assert fv[1] in acl_group_ids + member_groups.append(fv[1]) + elif fv[0] == "SAI_ACL_TABLE_GROUP_MEMBER_ATTR_ACL_TABLE_ID": + assert fv[1] == acl_table_id + elif fv[0] == "SAI_ACL_TABLE_GROUP_MEMBER_ATTR_PRIORITY": + assert True + else: + assert False + + assert set(member_groups) == set(acl_group_ids) + + def verify_acl_port_binding(self, dvs, bind_ports): + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_TABLE_GROUP") + acl_table_groups = atbl.getKeys() + assert len(acl_table_groups) == len(bind_ports) + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") + port_groups = [] + for p in [dvs.asicdb.portnamemap[portname] for portname in bind_ports]: + (status, fvs) = atbl.get(p) + for fv in fvs: + if fv[0] == "SAI_PORT_ATTR_INGRESS_ACL": + assert fv[1] in acl_table_groups + port_groups.append(fv[1]) + + assert len(port_groups) == len(bind_ports) + assert set(port_groups) == set(acl_table_groups) + + def test_AclTableCreation(self, dvs, testlog): + """ + hmset ACL_TABLE_TABLE:mclag policy_desc "Mclag egress port isolate acl" type MCLAG ports Ethernet0,Ethernet4 + """ + self.setup_db(dvs) + + # create ACL_TABLE_TABLE in app db + bind_ports = ["Ethernet0", "Ethernet4"] + self.create_entry_pst( + self.pdb, + "ACL_TABLE_TABLE", "mclag", + [ + ("policy_desc", "Mclag egress port isolate acl"), + ("type", "MCLAG"), + ("ports", ",".join(bind_ports)), + ] + ) + + # check acl table in asic db + acl_table_id = self.get_acl_table_id(dvs) + assert acl_table_id is not None + + # check acl table group in asic db + self.verify_acl_group_num(2) + + # get acl table group ids and verify the id numbers + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_TABLE_GROUP") + acl_group_ids = atbl.getKeys() + assert len(acl_group_ids) == 2 + + # check acl table group member + self.verify_acl_group_member(acl_group_ids, acl_table_id) + + # check port binding + self.verify_acl_port_binding(dvs, bind_ports) + + def test_AclRuleOutPorts(self, dvs, testlog): + """ + hmset ACL_RULE_TABLE:mclag:mclag IP_TYPE ANY PACKET_ACTION DROP OUT_PORTS Ethernet8,Ethernet12 + """ + self.setup_db(dvs) + + # create acl rule + bind_ports = ["Ethernet8", "Ethernet12"] + self.create_entry_pst( + self.pdb, + "ACL_RULE_TABLE", "mclag:mclag", + [ + ("IP_TYPE", "ANY"), + ("PACKET_ACTION", "DROP"), + ("OUT_PORTS", ",".join(bind_ports)), + ] + ) + + # check acl rule table in asic db + acl_table_id = self.get_acl_table_id(dvs) + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_ENTRY") + keys = atbl.getKeys() + + acl_entry = [k for k in keys if k not in dvs.asicdb.default_acl_entries] + assert len(acl_entry) == 1 + + (status, fvs) = atbl.get(acl_entry[0]) + assert status == True + + value = dict(fvs) + assert value["SAI_ACL_ENTRY_ATTR_TABLE_ID"] == acl_table_id + assert value["SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION"] == "SAI_PACKET_ACTION_DROP" + assert value["SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE"] == "SAI_ACL_IP_TYPE_ANY&mask:0xffffffffffffffff" + out_ports = value["SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS"] + assert out_ports.startswith("2:") + assert dvs.asicdb.portnamemap["Ethernet8"] in out_ports + assert dvs.asicdb.portnamemap["Ethernet12"] in out_ports + + # remove acl rule + self.remove_entry_pst( + self.pdb, + "ACL_RULE_TABLE", "mclag:mclag" + ) + + # check acl rule in asic db + (status, fvs) = atbl.get(acl_entry[0]) + assert status == False + + # remove acl + self.remove_entry_pst( + self.pdb, + "ACL_TABLE_TABLE", "mclag:mclag" + ) + + # check acl in asic db + acl_table_id = self.get_acl_table_id(dvs) + assert acl_table_id is None From cbe18118a63e6106105794dcb86235c474021580 Mon Sep 17 00:00:00 2001 From: zhenggen-xu Date: Tue, 28 Jan 2020 21:46:44 -0800 Subject: [PATCH 58/63] Fix the stack-overflow issue in AclOrch::getTableById() (#1073) * Fix the stack-overflow issue in AclOrch::getTableById() In case the table_id and m_mirrorTableId/m_mirrorTableId are empty The function would be called recursively without ending. ``` If we have some rule that does not have table defined, it will trigger this issue. (gdb) bt #0 0x00007f77ac5af3a9 in swss::Logger::write (this=0x7f77ac801ec0 , prio=swss::Logger::SWSS_DEBUG, fmt=0x7f77ac5f1690 ":> %s: enter") at logger.cpp:209 #1 0x00000000004a5792 in AclOrch::getTableById (this=this@entry=0x1c722a0, table_id="") at aclorch.cpp:2617 #2 0x00000000004a59d1 in AclOrch::getTableById (this=this@entry=0x1c722a0, table_id="") at aclorch.cpp:2634 #3 0x00000000004a59d1 in AclOrch::getTableById (this=this@entry=0x1c722a0, table_id="") at aclorch.cpp:2634 ... #20944 0x00000000004a59d1 in AclOrch::getTableById (this=this@entry=0x1c722a0, table_id="") at aclorch.cpp:2634 #20945 0x00000000004ad3ce in AclOrch::doAclRuleTask (this=this@entry=0x1c722a0, consumer=...) at aclorch.cpp:2437 #20946 0x00000000004b04cd in AclOrch::doTask (this=0x1c722a0, consumer=...) at aclorch.cpp:2141 #20947 0x00000000004231b2 in Orch::doTask (this=0x1c722a0) at orch.cpp:369 #20948 0x000000000041c4e9 in OrchDaemon::start (this=this@entry=0x1c19960) at orchdaemon.cpp:376 #20949 0x0000000000409ffc in main (argc=, argv=0x7ffe2e392d68) at main.cpp:295 (gdb) p table_id $1 = "" (gdb) p m_mirrorTableId $2 = "" (gdb) p m_mirrorV6TableId $3 = "" ``` Signed-off-by: Zhenggen Xu --- orchagent/aclorch.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index 09a5778c91..52855c87fd 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -2836,6 +2836,13 @@ void AclOrch::doAclRuleTask(Consumer &consumer) string op = kfvOp(t); SWSS_LOG_INFO("OP: %s, TABLE_ID: %s, RULE_ID: %s", op.c_str(), table_id.c_str(), rule_id.c_str()); + + if (table_id.empty()) + { + SWSS_LOG_WARN("ACL rule with RULE_ID: %s is not valid as TABLE_ID is empty", rule_id.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } if (op == SET_COMMAND) { @@ -3016,6 +3023,12 @@ sai_object_id_t AclOrch::getTableById(string table_id) { SWSS_LOG_ENTER(); + if (table_id.empty()) + { + SWSS_LOG_WARN("table_id is empty"); + return SAI_NULL_OBJECT_ID; + } + for (auto it : m_AclTables) { if (it.second.id == table_id) From 49ad38ff5813c1b9eb75251503b8e1a821d4cba8 Mon Sep 17 00:00:00 2001 From: rajkumar38 <54936542+rajkumar38@users.noreply.github.com> Date: Thu, 30 Jan 2020 00:42:39 +0530 Subject: [PATCH 59/63] =?UTF-8?q?[orchagent/copp]=20:=20copp=20trap=20prio?= =?UTF-8?q?rity=20not=20supported=20for=20marvell=20platf=E2=80=A6=20(#116?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [orchagent/copp] : copp trap priority not supported for marvell platform. * Added MRVL platform macro * Do not set trap priority for marvell platform Signed-off-by: RAJKUMAR PENNADAM RAMAMOORTHY rpennadamram@marvell.com --- orchagent/copporch.cpp | 6 ++++-- orchagent/orch.h | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/orchagent/copporch.cpp b/orchagent/copporch.cpp index d6cbfaa736..21cd62f097 100644 --- a/orchagent/copporch.cpp +++ b/orchagent/copporch.cpp @@ -148,8 +148,9 @@ void CoppOrch::initDefaultTrapIds() trap_id_attrs.push_back(attr); /* Mellanox platform doesn't support trap priority setting */ + /* Marvell platform doesn't support trap priority. */ char *platform = getenv("platform"); - if (!platform || !strstr(platform, MLNX_PLATFORM_SUBSTRING)) + if (!platform || (!strstr(platform, MLNX_PLATFORM_SUBSTRING) && (!strstr(platform, MRVL_PLATFORM_SUBSTRING)))) { attr.id = SAI_HOSTIF_TRAP_ATTR_TRAP_PRIORITY; attr.value.u32 = 0; @@ -504,8 +505,9 @@ task_process_status CoppOrch::processCoppRule(Consumer& consumer) else if (fvField(*i) == copp_trap_priority_field) { /* Mellanox platform doesn't support trap priority setting */ + /* Marvell platform doesn't support trap priority. */ char *platform = getenv("platform"); - if (!platform || !strstr(platform, MLNX_PLATFORM_SUBSTRING)) + if (!platform || (!strstr(platform, MLNX_PLATFORM_SUBSTRING) && (!strstr(platform, MRVL_PLATFORM_SUBSTRING)))) { attr.id = SAI_HOSTIF_TRAP_ATTR_TRAP_PRIORITY, attr.value.u32 = (uint32_t)stoul(fvValue(*i)); diff --git a/orchagent/orch.h b/orchagent/orch.h index 1ea75b19d3..b00716e174 100644 --- a/orchagent/orch.h +++ b/orchagent/orch.h @@ -35,6 +35,7 @@ const char state_db_key_delimiter = '|'; #define BFN_PLATFORM_SUBSTRING "barefoot" #define VS_PLATFORM_SUBSTRING "vs" #define NPS_PLATFORM_SUBSTRING "nephos" +#define MRVL_PLATFORM_SUBSTRING "marvell" #define CONFIGDB_KEY_SEPARATOR "|" #define DEFAULT_KEY_SEPARATOR ":" From dc695fb2b81940a3da37eace5505167e2f916641 Mon Sep 17 00:00:00 2001 From: zhenggen-xu Date: Wed, 29 Jan 2020 12:15:39 -0800 Subject: [PATCH 60/63] [orch] change Consumer class to support multiple values for the same key (#1184) Description The Consumer class is used by the orch objects to deal with the redis consumer tables' popped items. It has m_toSync map to save the tasks. During the operations (more items than the orch objects can handle), the tasks in the map is merged for optimization. However, since it is a map, we would only have one value for each key. This potentially eliminate the necessary actions from Redis, e,g, we have a DEL action and SET action coming out from Redis for some key, we would overwrite the DEL action today in the code and thus not able to delete some objects as we intended. The PR changed the m_toSync from map to multi-map to get multiple values for one key. In this design, we keep maximun two values per key, DEL or SET or DEL+SET. We need strictly keep the order of DEL and SET. It is possible to use map of vectors to fulfill this, we chose multi-map because: 1, It will have less/no changes to different orch classes to iterate the m_toSync 2, The order can be guaranteed. The order of the key-value pairs whose keys compare equivalent is the order of insertion and does not change. (since C++11). See https://en.cppreference.com/w/cpp/container/multimap The PR also refactors the consumer class so vlanmgr.cpp and routeorch.cpp will leverage the Consumer functions instead of operating on the members. It also refactors the UT code (aclorch_ut.cpp) so it removes the redundant code and uses the same code. Google UT tests were added for Consumer Class especially for different cases for addToSync() function. What I did Change the m_toSync in Consumer class to multimap so it could support both DEL and SET Reload Consumer addToSync() and refactor vlanmgr/route-orch and ut code to use it Add google ut for consumer class Why I did it See description. How I verified it Unit tests: Running main() from gtest_main.cc ``` [==========] Running 19 tests from 5 test cases. [----------] Global test environment set-up. [----------] 1 test from AclTest [ RUN ] AclTest.Create_L3_Acl_Table [ OK ] AclTest.Create_L3_Acl_Table (1 ms) [----------] 1 test from AclTest (1 ms total) [----------] 3 tests from AclOrchTest [ RUN ] AclOrchTest.ACL_Creation_and_Destorying [ OK ] AclOrchTest.ACL_Creation_and_Destorying (1000 ms) [ RUN ] AclOrchTest.L3Acl_Matches_Actions [ OK ] AclOrchTest.L3Acl_Matches_Actions (1001 ms) [ RUN ] AclOrchTest.L3V6Acl_Matches_Actions [ OK ] AclOrchTest.L3V6Acl_Matches_Actions (1000 ms) [----------] 3 tests from AclOrchTest (3003 ms total) [----------] 2 tests from PortsOrchTest [ RUN ] PortsOrchTest.PortReadinessColdBoot [ OK ] PortsOrchTest.PortReadinessColdBoot (21 ms) [ RUN ] PortsOrchTest.PortReadinessWarmBoot [ OK ] PortsOrchTest.PortReadinessWarmBoot (13 ms) [----------] 2 tests from PortsOrchTest (34 ms total) [----------] 4 tests from SaiSpy [ RUN ] SaiSpy.CURD [ OK ] SaiSpy.CURD (0 ms) [ RUN ] SaiSpy.Same_Function_Signature_In_Same_API_Table [ OK ] SaiSpy.Same_Function_Signature_In_Same_API_Table (0 ms) [ RUN ] SaiSpy.Same_Function_Signature_In_Different_API_Table [ OK ] SaiSpy.Same_Function_Signature_In_Different_API_Table (0 ms) [ RUN ] SaiSpy.create_switch_and_acl_table [ OK ] SaiSpy.create_switch_and_acl_table (0 ms) [----------] 4 tests from SaiSpy (0 ms total) [----------] 9 tests from ConsumerTest [ RUN ] ConsumerTest.ConsumerAddToSync_Set [ OK ] ConsumerTest.ConsumerAddToSync_Set (1 ms) [ RUN ] ConsumerTest.ConsumerAddToSync_Del [ OK ] ConsumerTest.ConsumerAddToSync_Del (0 ms) [ RUN ] ConsumerTest.ConsumerAddToSync_Set_Del [ OK ] ConsumerTest.ConsumerAddToSync_Set_Del (0 ms) [ RUN ] ConsumerTest.ConsumerAddToSync_Del_Set [ OK ] ConsumerTest.ConsumerAddToSync_Del_Set (130 ms) [ RUN ] ConsumerTest.ConsumerAddToSync_Set_Del_Set_Multi [ OK ] ConsumerTest.ConsumerAddToSync_Set_Del_Set_Multi (204 ms) [ RUN ] ConsumerTest.ConsumerAddToSync_Set_Del_Set_Multi_In_Q [ OK ] ConsumerTest.ConsumerAddToSync_Set_Del_Set_Multi_In_Q (4 ms) [ RUN ] ConsumerTest.ConsumerAddToSync_Del_Set_Setnew [ OK ] ConsumerTest.ConsumerAddToSync_Del_Set_Setnew (0 ms) [ RUN ] ConsumerTest.ConsumerAddToSync_Del_Set_Setnew1 [ OK ] ConsumerTest.ConsumerAddToSync_Del_Set_Setnew1 (0 ms) [ RUN ] ConsumerTest.ConsumerAddToSync_Ind_Set_Del [ OK ] ConsumerTest.ConsumerAddToSync_Ind_Set_Del (0 ms) [----------] 9 tests from ConsumerTest (340 ms total) [----------] Global test environment tear-down [==========] 19 tests from 5 test cases ran. (4344 ms total) [ PASSED ] 19 tests. ``` Signed-off-by: Zhenggen Xu --- cfgmgr/vlanmgr.cpp | 5 +- orchagent/orch.cpp | 82 ++++++-- orchagent/orch.h | 17 +- orchagent/routeorch.cpp | 4 +- tests/mock_tests/Makefile.am | 1 + tests/mock_tests/aclorch_ut.cpp | 61 +----- tests/mock_tests/consumer_ut.cpp | 328 +++++++++++++++++++++++++++++++ 7 files changed, 412 insertions(+), 86 deletions(-) create mode 100644 tests/mock_tests/consumer_ut.cpp diff --git a/cfgmgr/vlanmgr.cpp b/cfgmgr/vlanmgr.cpp index 888ced509a..03b0fdc6d4 100644 --- a/cfgmgr/vlanmgr.cpp +++ b/cfgmgr/vlanmgr.cpp @@ -447,8 +447,9 @@ void VlanMgr::processUntaggedVlanMembers(string vlan, const string &members) vector fvVector; FieldValueTuple t("tagging_mode", "untagged"); fvVector.push_back(t); - consumer.m_toSync[member_key] = make_tuple(member_key, SET_COMMAND, fvVector); - SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, consumer.m_toSync[member_key])).c_str()); + KeyOpFieldsValuesTuple tuple = make_tuple(member_key, SET_COMMAND, fvVector); + consumer.addToSync(tuple); + SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, tuple)).c_str()); } /* * There is pending task from consumer pipe, in this case just skip it. diff --git a/orchagent/orch.cpp b/orchagent/orch.cpp index a6dce7efbb..6dcbc6c3d6 100644 --- a/orchagent/orch.cpp +++ b/orchagent/orch.cpp @@ -67,36 +67,66 @@ vector Orch::getSelectables() return selectables; } -size_t Consumer::addToSync(std::deque &entries) +void Consumer::addToSync(const KeyOpFieldsValuesTuple &entry) { SWSS_LOG_ENTER(); - /* Nothing popped */ - if (entries.empty()) + + string key = kfvKey(entry); + string op = kfvOp(entry); + + /* Record incoming tasks */ + if (gSwssRecord) { - return 0; + Orch::recordTuple(*this, entry); } - for (auto& entry: entries) + /* + * m_toSync is a multimap which will allow one key with multiple values, + * Also, the order of the key-value pairs whose keys compare equivalent + * is the order of insertion and does not change. (since C++11) + */ + + /* If a new task comes we directly put it into getConsumerTable().m_toSync map */ + if (m_toSync.find(key) == m_toSync.end()) { - string key = kfvKey(entry); - string op = kfvOp(entry); + m_toSync.emplace(key, entry); + } - /* Record incoming tasks */ - if (gSwssRecord) + /* if a DEL task comes, we overwrite the old key */ + else if (op == DEL_COMMAND) + { + m_toSync.erase(key); + m_toSync.emplace(key, entry); + } + else + { + /* + * Now we are trying to add the key-value with SET. + * We maintain maximun two values per key. + * In case there is one key-value, it should be DEL or SET + * In case there are two key-value pairs, it should be DEL then SET + * The code logic is following: + * We iterate the values with the key, we skip the value with DEL and then + * check if that was the only one (I,E, the iter pointer now points to end or next key), + * in such case, we insert the key-value with SET. + * If there was a SET already (I,E, the pointer still points to the same key), we combine the kfv. + */ + auto ret = m_toSync.equal_range(key); + auto iter = ret.first; + for (; iter != ret.second; ++iter) { - Orch::recordTuple(*this, entry); + auto old_op = kfvOp(iter->second); + if (old_op == SET_COMMAND) + break; } - - /* If a new task comes or if a DEL task comes, we directly put it into getConsumerTable().m_toSync map */ - if (m_toSync.find(key) == m_toSync.end() || op == DEL_COMMAND) + if (iter == ret.second) { - m_toSync[key] = entry; + m_toSync.emplace(key, entry); } - /* If an old task is still there, we combine the old task with new task */ else { - KeyOpFieldsValuesTuple existing_data = m_toSync[key]; + KeyOpFieldsValuesTuple existing_data = iter->second; auto new_values = kfvFieldsValues(entry); auto existing_values = kfvFieldsValues(existing_data); @@ -118,9 +148,21 @@ size_t Consumer::addToSync(std::deque &entries) } existing_values.push_back(FieldValueTuple(field, value)); } - m_toSync[key] = KeyOpFieldsValuesTuple(key, op, existing_values); + iter->second = KeyOpFieldsValuesTuple(key, op, existing_values); } } + +} + +size_t Consumer::addToSync(const std::deque &entries) +{ + SWSS_LOG_ENTER(); + + for (auto& entry: entries) + { + addToSync(entry); + } + return entries.size(); } @@ -186,7 +228,7 @@ void Consumer::drain() m_orch->doTask(*this); } -string Consumer::dumpTuple(KeyOpFieldsValuesTuple &tuple) +string Consumer::dumpTuple(const KeyOpFieldsValuesTuple &tuple) { string s = getTableName() + getConsumerTable()->getTableNameSeparator() + kfvKey(tuple) + "|" + kfvOp(tuple); @@ -412,7 +454,7 @@ void Orch::logfileReopen() } } -void Orch::recordTuple(Consumer &consumer, KeyOpFieldsValuesTuple &tuple) +void Orch::recordTuple(Consumer &consumer, const KeyOpFieldsValuesTuple &tuple) { string s = consumer.dumpTuple(tuple); @@ -426,7 +468,7 @@ void Orch::recordTuple(Consumer &consumer, KeyOpFieldsValuesTuple &tuple) } } -string Orch::dumpTuple(Consumer &consumer, KeyOpFieldsValuesTuple &tuple) +string Orch::dumpTuple(Consumer &consumer, const KeyOpFieldsValuesTuple &tuple) { string s = consumer.dumpTuple(tuple); return s; diff --git a/orchagent/orch.h b/orchagent/orch.h index b00716e174..3e25e96afe 100644 --- a/orchagent/orch.h +++ b/orchagent/orch.h @@ -57,7 +57,11 @@ typedef std::pair object_map_pair; typedef std::map type_map; typedef std::pair type_map_pair; -typedef std::map SyncMap; + +// Use multimap to support multiple OpFieldsValues for the same key (e,g, DEL and SET) +// The order of the key-value pairs whose keys compare equivalent is the order of +// insertion and does not change. (since C++11) +typedef std::multimap SyncMap; typedef std::pair table_name_with_pri_t; @@ -132,7 +136,7 @@ class Consumer : public Executor { return getConsumerTable()->getDbId(); } - std::string dumpTuple(swss::KeyOpFieldsValuesTuple &tuple); + std::string dumpTuple(const swss::KeyOpFieldsValuesTuple &tuple); void dumpPendingTasks(std::vector &ts); size_t refillToSync(); @@ -144,9 +148,10 @@ class Consumer : public Executor { // TODO: hide? SyncMap m_toSync; -protected: + void addToSync(const swss::KeyOpFieldsValuesTuple &entry); + // Returns: the number of entries added to m_toSync - size_t addToSync(std::deque &entries); + size_t addToSync(const std::deque &entries); }; typedef std::map> ConsumerMap; @@ -194,14 +199,14 @@ class Orch virtual void doTask(swss::SelectableTimer &timer) { } /* TODO: refactor recording */ - static void recordTuple(Consumer &consumer, swss::KeyOpFieldsValuesTuple &tuple); + static void recordTuple(Consumer &consumer, const swss::KeyOpFieldsValuesTuple &tuple); void dumpPendingTasks(std::vector &ts); protected: ConsumerMap m_consumerMap; static void logfileReopen(); - std::string dumpTuple(Consumer &consumer, swss::KeyOpFieldsValuesTuple &tuple); + std::string dumpTuple(Consumer &consumer, const swss::KeyOpFieldsValuesTuple &tuple); ref_resolve_status resolveFieldRefValue(type_map&, const std::string&, swss::KeyOpFieldsValuesTuple&, sai_object_id_t&); bool parseIndexRange(const std::string &input, sai_uint32_t &range_low, sai_uint32_t &range_high); bool parseReference(type_map &type_maps, std::string &ref, std::string &table_name, std::string &object_name); diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index 8aabef4a9e..27338ef35c 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -134,7 +134,7 @@ std::string RouteOrch::getLinkLocalEui64Addr(void) uint8_t eui64_interface_id[EUI64_INTF_ID_LEN]; char ipv6_ll_addr[INET6_ADDRSTRLEN] = {0}; - + /* Link-local IPv6 address autogenerated by kernel with eui64 interface-id * derived from the MAC address of the host interface. */ @@ -406,7 +406,7 @@ void RouteOrch::doTask(Consumer& consumer) vector v; key = vrf + i.first.to_string(); auto x = KeyOpFieldsValuesTuple(key, DEL_COMMAND, v); - consumer.m_toSync[key] = x; + consumer.addToSync(x); } } m_resync = true; diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 4c0b5582ce..a7f89475a6 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -23,6 +23,7 @@ LDADD_GTEST = -L/usr/src/gtest tests_SOURCES = aclorch_ut.cpp \ portsorch_ut.cpp \ saispy_ut.cpp \ + consumer_ut.cpp \ ut_saihelper.cpp \ mock_orchagent_main.cpp \ mock_dbconnector.cpp \ diff --git a/tests/mock_tests/aclorch_ut.cpp b/tests/mock_tests/aclorch_ut.cpp index 66b0090d23..dc784c75ae 100644 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -23,54 +23,6 @@ namespace aclorch_test { using namespace std; - size_t consumerAddToSync(Consumer *consumer, const deque &entries) - { - /* Nothing popped */ - if (entries.empty()) - { - return 0; - } - - for (auto &entry : entries) - { - string key = kfvKey(entry); - string op = kfvOp(entry); - - /* If a new task comes or if a DEL task comes, we directly put it into getConsumerTable().m_toSync map */ - if (consumer->m_toSync.find(key) == consumer->m_toSync.end() || op == DEL_COMMAND) - { - consumer->m_toSync[key] = entry; - } - /* If an old task is still there, we combine the old task with new task */ - else - { - KeyOpFieldsValuesTuple existing_data = consumer->m_toSync[key]; - - auto new_values = kfvFieldsValues(entry); - auto existing_values = kfvFieldsValues(existing_data); - - for (auto it : new_values) - { - string field = fvField(it); - string value = fvValue(it); - - auto iu = existing_values.begin(); - while (iu != existing_values.end()) - { - string ofield = fvField(*iu); - if (field == ofield) - iu = existing_values.erase(iu); - else - iu++; - } - existing_values.push_back(FieldValueTuple(field, value)); - } - consumer->m_toSync[key] = KeyOpFieldsValuesTuple(key, op, existing_values); - } - } - return entries.size(); - } - struct AclTestBase : public ::testing::Test { vector m_s32list_pool; @@ -199,8 +151,7 @@ namespace aclorch_test auto consumer = unique_ptr(new Consumer( new swss::ConsumerStateTable(config_db, CFG_ACL_TABLE_TABLE_NAME, 1, 1), m_aclOrch, CFG_ACL_TABLE_TABLE_NAME)); - consumerAddToSync(consumer.get(), entries); - + consumer->addToSync(entries); static_cast(m_aclOrch)->doTask(*consumer); } @@ -209,8 +160,7 @@ namespace aclorch_test auto consumer = unique_ptr(new Consumer( new swss::ConsumerStateTable(config_db, CFG_ACL_RULE_TABLE_NAME, 1, 1), m_aclOrch, CFG_ACL_RULE_TABLE_NAME)); - consumerAddToSync(consumer.get(), entries); - + consumer->addToSync(entries); static_cast(m_aclOrch)->doTask(*consumer); } @@ -381,8 +331,7 @@ namespace aclorch_test auto consumer = unique_ptr(new Consumer( new swss::ConsumerStateTable(m_app_db.get(), APP_PORT_TABLE_NAME, 1, 1), gPortsOrch, APP_PORT_TABLE_NAME)); - consumerAddToSync(consumer.get(), { { "PortInitDone", EMPTY_PREFIX, { { "", "" } } } }); - + consumer->addToSync({ { "PortInitDone", EMPTY_PREFIX, { { "", "" } } } }); static_cast(gPortsOrch)->doTask(*consumer.get()); } @@ -628,7 +577,7 @@ namespace aclorch_test // consistency validation with CRM bool validateResourceCountWithCrm(const AclOrch *aclOrch, CrmOrch *crmOrch) { - // Verify ACL Tables + // Verify ACL Tables auto const &resourceMap = Portal::CrmOrchInternal::getResourceMap(crmOrch); uint32_t crm_acl_table_cnt = 0; for (auto const &kv : resourceMap.at(CrmResourceType::CRM_ACL_TABLE).countersMap) @@ -642,7 +591,7 @@ namespace aclorch_test << ") and AclOrch " << Portal::AclOrchInternal::getAclTables(aclOrch).size(); return false; } - + // Verify ACL Rules // diff --git a/tests/mock_tests/consumer_ut.cpp b/tests/mock_tests/consumer_ut.cpp new file mode 100644 index 0000000000..0facd15269 --- /dev/null +++ b/tests/mock_tests/consumer_ut.cpp @@ -0,0 +1,328 @@ +#include "ut_helper.h" +#include "mock_orchagent_main.h" +#include "mock_table.h" + +#include + +extern PortsOrch *gPortsOrch; + +namespace consumer_test +{ + using namespace std; + + struct ConsumerTest : public ::testing::Test + { + shared_ptr m_app_db; + shared_ptr m_config_db; + shared_ptr m_state_db; + + string key = "key"; + string f1 = "field1"; + string v1a = "value1_a"; + string v1b = "value1_b"; + string f2 = "field2"; + string v2a = "value2_a"; + string v2b = "value2_b"; + string f3 = "field3"; + string v3a = "value3_a"; + KeyOpFieldsValuesTuple exp_kofv; + + unique_ptr consumer; + deque kofv_q; + + ConsumerTest() + { + // FIXME: move out from constructor + m_app_db = make_shared( + APPL_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + m_config_db = make_shared( + CONFIG_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + m_state_db = make_shared( + STATE_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + consumer = unique_ptr(new Consumer( + new swss::ConsumerStateTable(m_config_db.get(), "CFG_TEST_TABLE", 1, 1), gPortsOrch, "CFG_TEST_TABLE")); + } + + virtual void SetUp() override + { + ::testing_db::reset(); + } + + virtual void TearDown() override + { + ::testing_db::reset(); + } + + void validate_syncmap(SyncMap &sync, uint16_t exp_sz, std::string exp_key, KeyOpFieldsValuesTuple exp_kofv) + { + // verify the content in syncMap + ASSERT_EQ(sync.size(), exp_sz); + auto it = sync.begin(); + while (it != sync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + + string itkey = kfvKey(t); + if (itkey == exp_key) { + ASSERT_EQ(t, exp_kofv); + it = sync.erase(it); + break; + } else { + it++; + } + } + ASSERT_EQ(sync.size(), exp_sz-1); + } + }; + + TEST_F(ConsumerTest, ConsumerAddToSync_Set) + { + + // Test case, one set_command + auto entry = KeyOpFieldsValuesTuple( + { key, + SET_COMMAND, + { { f1, v1a }, + { f2, v2a } } }); + + kofv_q.push_back(entry); + consumer->addToSync(kofv_q); + exp_kofv = entry; + validate_syncmap(consumer->m_toSync, 1, key, exp_kofv); + } + + TEST_F(ConsumerTest, ConsumerAddToSync_Del) + { + // Test case, one with del_command + auto entry = KeyOpFieldsValuesTuple( + { key, + DEL_COMMAND, + { { } } }); + + kofv_q.push_back(entry); + consumer->addToSync(kofv_q); + + exp_kofv = entry; + validate_syncmap(consumer->m_toSync, 1, key, exp_kofv); + + } + + TEST_F(ConsumerTest, ConsumerAddToSync_Set_Del) + { + // Test case, add SET then DEL + auto entrya = KeyOpFieldsValuesTuple( + { key, + SET_COMMAND, + { { f1, v1a }, + { f2, v2a } } }); + + auto entryb = KeyOpFieldsValuesTuple( + { key, + DEL_COMMAND, + { { } } }); + + kofv_q.push_back(entrya); + kofv_q.push_back(entryb); + consumer->addToSync(kofv_q); + + // expect only DEL + exp_kofv = entryb; + validate_syncmap(consumer->m_toSync, 1, key, exp_kofv); + } + + TEST_F(ConsumerTest, ConsumerAddToSync_Del_Set) + { + auto entrya = KeyOpFieldsValuesTuple( + { key, + DEL_COMMAND, + { { } } }); + + auto entryb = KeyOpFieldsValuesTuple( + { key, + SET_COMMAND, + { { f1, v1a }, + { f2, v2a } } }); + + // Test case, add DEL then SET, re-try 100 times, order should be kept + for (auto x = 0; x < 100; x++) + { + kofv_q.push_back(entrya); + kofv_q.push_back(entryb); + consumer->addToSync(kofv_q); + + // expect DEL then SET + exp_kofv = entrya; + validate_syncmap(consumer->m_toSync, 2, key, exp_kofv); + + exp_kofv = entryb; + validate_syncmap(consumer->m_toSync, 1, key, exp_kofv); + } + } + + TEST_F(ConsumerTest, ConsumerAddToSync_Set_Del_Set_Multi) + { + // Test5, add SET, DEL then SET, re-try 100 times , order should be kept + auto entrya = KeyOpFieldsValuesTuple( + { key, + SET_COMMAND, + { { f1, v1a }, + { f2, v2a } } }); + + auto entryb = KeyOpFieldsValuesTuple( + { key, + DEL_COMMAND, + { { } } }); + + auto entryc = KeyOpFieldsValuesTuple( + { key, + SET_COMMAND, + { { f1, v1a }, + { f2, v2a } } }); + + for (auto x = 0; x < 100; x++) + { + kofv_q.push_back(entrya); + kofv_q.push_back(entryb); + kofv_q.push_back(entryc); + consumer->addToSync(kofv_q); + + // expect DEL then SET + exp_kofv = entryb; + validate_syncmap(consumer->m_toSync, 2, key, exp_kofv); + + exp_kofv = entryc; + validate_syncmap(consumer->m_toSync, 1, key, exp_kofv); + } + } + + TEST_F(ConsumerTest, ConsumerAddToSync_Set_Del_Set_Multi_In_Q) + { + // Test5, add SET, DEL then SET, repeat 100 times in queue, final result and order should be kept + auto entrya = KeyOpFieldsValuesTuple( + { key, + SET_COMMAND, + { { f1, v1a }, + { f2, v2a } } }); + + auto entryb = KeyOpFieldsValuesTuple( + { key, + DEL_COMMAND, + { { } } }); + + auto entryc = KeyOpFieldsValuesTuple( + { key, + SET_COMMAND, + { { f1, v1a }, + { f2, v2a } } }); + + for (auto x = 0; x < 100; x++) + { + kofv_q.push_back(entrya); + kofv_q.push_back(entryb); + kofv_q.push_back(entryc); + } + consumer->addToSync(kofv_q); + + // expect DEL then SET + exp_kofv = entryb; + validate_syncmap(consumer->m_toSync, 2, key, exp_kofv); + + exp_kofv = entryc; + validate_syncmap(consumer->m_toSync, 1, key, exp_kofv); + } + + TEST_F(ConsumerTest, ConsumerAddToSync_Del_Set_Setnew) + { + // Test case, DEL, SET, then SET with different value + auto entrya = KeyOpFieldsValuesTuple( + { key, + DEL_COMMAND, + { { } } }); + + auto entryb = KeyOpFieldsValuesTuple( + { key, + SET_COMMAND, + { { f1, v1a }, + { f2, v2a } } }); + + auto entryc = KeyOpFieldsValuesTuple( + { key, + SET_COMMAND, + { { f1, v1b }, + { f2, v2b } } }); + + kofv_q.push_back(entrya); + kofv_q.push_back(entryb); + kofv_q.push_back(entryc); + consumer->addToSync(kofv_q); + + // expect DEL then SET with new values + exp_kofv = entrya; + validate_syncmap(consumer->m_toSync, 2, key, exp_kofv); + + exp_kofv = entryc; + validate_syncmap(consumer->m_toSync, 1, key, exp_kofv); + } + + TEST_F(ConsumerTest, ConsumerAddToSync_Del_Set_Setnew1) + { + // Test case, DEL, SET, then SET with new values and new fields + auto entrya = KeyOpFieldsValuesTuple( + { key, + DEL_COMMAND, + { { } } }); + + auto entryb = KeyOpFieldsValuesTuple( + { key, + SET_COMMAND, + { { f1, v1a }, + { f2, v2a } } }); + + auto entryc = KeyOpFieldsValuesTuple( + { key, + SET_COMMAND, + { { f1, v1b }, + { f3, v3a } } }); + + kofv_q.push_back(entrya); + kofv_q.push_back(entryb); + kofv_q.push_back(entryc); + consumer->addToSync(kofv_q); + + // expect DEL then SET with new values and new fields + exp_kofv = entrya; + validate_syncmap(consumer->m_toSync, 2, key, exp_kofv); + + exp_kofv = KeyOpFieldsValuesTuple( + { key, + SET_COMMAND, + { { f2, v2a }, + { f1, v1b }, + { f3, v3a } } }); + + validate_syncmap(consumer->m_toSync, 1, key, exp_kofv); + } + + TEST_F(ConsumerTest, ConsumerAddToSync_Ind_Set_Del) + { + // Test case, Add individuals by addToSync, SET then DEL + auto entrya = KeyOpFieldsValuesTuple( + { key, + SET_COMMAND, + { { f1, v1a }, + { f2, v2a } } }); + + auto entryb = KeyOpFieldsValuesTuple( + { key, + DEL_COMMAND, + { { } } }); + + consumer->addToSync(entrya); + consumer->addToSync(entryb); + + // expect only DEL + exp_kofv = entryb; + validate_syncmap(consumer->m_toSync, 1, key, exp_kofv); + + } +} From 1883c0a022ef44e8cf6278d62a894eec5722bc0f Mon Sep 17 00:00:00 2001 From: Danny Allen Date: Wed, 29 Jan 2020 17:26:05 -0800 Subject: [PATCH 61/63] [vs tests] Remove class-level flaky fixtures (#1189) Signed-off-by: Danny Allen --- tests/test_acl.py | 3 --- tests/test_acl_ctrl.py | 2 -- tests/test_acl_egress_table.py | 2 -- tests/test_acl_portchannel.py | 2 -- tests/test_admin_status.py | 2 -- tests/test_crm.py | 2 -- tests/test_dirbcast.py | 2 -- tests/test_drop_counters.py | 2 -- tests/test_dtel.py | 2 -- tests/test_fdb.py | 2 -- tests/test_fdb_update.py | 2 -- tests/test_interface.py | 2 -- tests/test_mirror.py | 2 -- tests/test_mirror_ipv6_combined.py | 2 -- tests/test_mirror_ipv6_separate.py | 2 -- tests/test_mirror_policer.py | 2 -- tests/test_nat.py | 1 - tests/test_neighbor.py | 2 -- tests/test_nhg.py | 2 -- tests/test_pfc.py | 2 -- tests/test_policer.py | 2 -- tests/test_port.py | 2 -- tests/test_port_an.py | 2 -- tests/test_port_buffer_rel.py | 2 -- tests/test_port_config.py | 2 -- tests/test_port_mac_learn.py | 2 -- tests/test_portchannel.py | 2 -- tests/test_qos_map.py | 2 -- tests/test_route.py | 2 -- tests/test_setro.py | 2 -- tests/test_sflow.py | 2 -- tests/test_speed.py | 2 -- tests/test_sub_port_intf.py | 2 -- tests/test_switch.py | 2 -- tests/test_tunnel.py | 2 -- tests/test_vlan.py | 2 -- tests/test_vnet.py | 2 -- tests/test_vnet_bitmap.py | 2 -- tests/test_vrf.py | 2 -- tests/test_vxlan_tunnel.py | 2 -- tests/test_warm_reboot.py | 2 -- tests/test_watermark.py | 2 -- 42 files changed, 84 deletions(-) diff --git a/tests/test_acl.py b/tests/test_acl.py index 9fb1d3df17..86ebb3f36e 100644 --- a/tests/test_acl.py +++ b/tests/test_acl.py @@ -4,7 +4,6 @@ import pytest from swsscommon import swsscommon -from flaky import flaky class BaseTestAcl(object): @@ -188,7 +187,6 @@ def verify_acl_rule(self, dvs, field, value): assert False -@pytest.mark.flaky class TestAcl(BaseTestAcl): def test_AclTableCreation(self, dvs, testlog): self.setup_db(dvs) @@ -1375,7 +1373,6 @@ def test_AclRuleRedirectToNexthop(self, dvs, testlog): dvs.set_interface_status("Ethernet4", "down") -@pytest.mark.flaky class TestAclRuleValidation(BaseTestAcl): """ Test class for cases that check if orchagent corectly validates ACL rules input diff --git a/tests/test_acl_ctrl.py b/tests/test_acl_ctrl.py index 304066313a..e3998b106e 100644 --- a/tests/test_acl_ctrl.py +++ b/tests/test_acl_ctrl.py @@ -2,10 +2,8 @@ import pytest from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestPortChannelAcl(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_acl_egress_table.py b/tests/test_acl_egress_table.py index d4ed9a7094..6ea36f66f8 100644 --- a/tests/test_acl_egress_table.py +++ b/tests/test_acl_egress_table.py @@ -2,10 +2,8 @@ import pytest from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestEgressAclTable(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_acl_portchannel.py b/tests/test_acl_portchannel.py index 435ea32745..63827656c4 100644 --- a/tests/test_acl_portchannel.py +++ b/tests/test_acl_portchannel.py @@ -2,10 +2,8 @@ import pytest from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestPortChannelAcl(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_admin_status.py b/tests/test_admin_status.py index 0c9eef1bc2..5dd1eff071 100644 --- a/tests/test_admin_status.py +++ b/tests/test_admin_status.py @@ -2,10 +2,8 @@ import pytest from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestAdminStatus(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_crm.py b/tests/test_crm.py index bbe3b7880e..f99c3cf69f 100644 --- a/tests/test_crm.py +++ b/tests/test_crm.py @@ -6,7 +6,6 @@ import pytest from swsscommon import swsscommon -from flaky import flaky def getCrmCounterValue(dvs, key, counter): @@ -65,7 +64,6 @@ def check_syslog(dvs, marker, err_log, expected_cnt): assert num.strip() >= str(expected_cnt) -@pytest.mark.flaky class TestCrm(object): def test_CrmFdbEntry(self, dvs, testlog): diff --git a/tests/test_dirbcast.py b/tests/test_dirbcast.py index 0c98bd567a..745e6ee022 100644 --- a/tests/test_dirbcast.py +++ b/tests/test_dirbcast.py @@ -4,10 +4,8 @@ import pytest from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestDirectedBroadcast(object): def test_DirectedBroadcast(self, dvs, testlog): diff --git a/tests/test_drop_counters.py b/tests/test_drop_counters.py index d120e75f1a..af4ef5648c 100644 --- a/tests/test_drop_counters.py +++ b/tests/test_drop_counters.py @@ -2,7 +2,6 @@ import pytest from swsscommon import swsscommon -from flaky import flaky # Supported drop counters PORT_INGRESS_DROPS = 'PORT_INGRESS_DROPS' @@ -59,7 +58,6 @@ # FIXME: It is really annoying to have to re-run tests due to inconsistent timing, should # implement some sort of polling interface for checking ASIC/flex counter tables after # applying changes to config DB -@pytest.mark.flaky class TestDropCounters(object): def setup_db(self, dvs): self.asic_db = swsscommon.DBConnector(1, dvs.redis_sock, 0) diff --git a/tests/test_dtel.py b/tests/test_dtel.py index e1be7f449b..99fd82b9b1 100644 --- a/tests/test_dtel.py +++ b/tests/test_dtel.py @@ -4,10 +4,8 @@ import pytest from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestDtel(object): def test_DtelGlobalAttribs(self, dvs, testlog): diff --git a/tests/test_fdb.py b/tests/test_fdb.py index aa03e79476..f883acb1bd 100644 --- a/tests/test_fdb.py +++ b/tests/test_fdb.py @@ -5,7 +5,6 @@ import pytest from swsscommon import swsscommon -from flaky import flaky from distutils.version import StrictVersion def create_entry(tbl, key, pairs): @@ -28,7 +27,6 @@ def how_many_entries_exist(db, table): return len(tbl.getKeys()) -@pytest.mark.flaky class TestFdb(object): def test_FdbWarmRestartNotifications(self, dvs, testlog): dvs.setup_db() diff --git a/tests/test_fdb_update.py b/tests/test_fdb_update.py index 001b278d1e..7d6fdc153c 100644 --- a/tests/test_fdb_update.py +++ b/tests/test_fdb_update.py @@ -5,11 +5,9 @@ import pytest from swsscommon import swsscommon -from flaky import flaky from distutils.version import StrictVersion -@pytest.mark.flaky class TestFdbUpdate(object): def create_entry(self, tbl, key, pairs): fvs = swsscommon.FieldValuePairs(pairs) diff --git a/tests/test_interface.py b/tests/test_interface.py index bf66f66322..09c487eacf 100644 --- a/tests/test_interface.py +++ b/tests/test_interface.py @@ -3,10 +3,8 @@ import pytest from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestRouterInterface(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_mirror.py b/tests/test_mirror.py index cc41b3c882..687315d55d 100644 --- a/tests/test_mirror.py +++ b/tests/test_mirror.py @@ -4,11 +4,9 @@ import time from swsscommon import swsscommon -from flaky import flaky from distutils.version import StrictVersion -@pytest.mark.flaky class TestMirror(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_mirror_ipv6_combined.py b/tests/test_mirror_ipv6_combined.py index 8ad5813600..90602069b7 100644 --- a/tests/test_mirror_ipv6_combined.py +++ b/tests/test_mirror_ipv6_combined.py @@ -4,13 +4,11 @@ import time from swsscommon import swsscommon -from flaky import flaky from distutils.version import StrictVersion DVS_FAKE_PLATFORM = "broadcom" -@pytest.mark.flaky class TestMirror(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_mirror_ipv6_separate.py b/tests/test_mirror_ipv6_separate.py index 195094c03c..5fdd4211c2 100644 --- a/tests/test_mirror_ipv6_separate.py +++ b/tests/test_mirror_ipv6_separate.py @@ -5,12 +5,10 @@ from distutils.version import StrictVersion from swsscommon import swsscommon -from flaky import flaky DVS_FAKE_PLATFORM = "mellanox" -@pytest.mark.flaky class TestMirror(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_mirror_policer.py b/tests/test_mirror_policer.py index 1434ae2a62..78bc4c6e1c 100644 --- a/tests/test_mirror_policer.py +++ b/tests/test_mirror_policer.py @@ -4,11 +4,9 @@ import time from swsscommon import swsscommon -from flaky import flaky from distutils.version import StrictVersion -@pytest.mark.flaky class TestMirror(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_nat.py b/tests/test_nat.py index 058cd7f251..d13b59e57d 100644 --- a/tests/test_nat.py +++ b/tests/test_nat.py @@ -6,7 +6,6 @@ import os from swsscommon import swsscommon -from flaky import flaky # FIXME: These tests depend on changes in sonic-buildimage, we need to reenable diff --git a/tests/test_neighbor.py b/tests/test_neighbor.py index d083dcf7c8..6425227702 100644 --- a/tests/test_neighbor.py +++ b/tests/test_neighbor.py @@ -3,10 +3,8 @@ import pytest from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestNeighbor(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_nhg.py b/tests/test_nhg.py index 7d1ba2b66b..56f3bdb0b6 100644 --- a/tests/test_nhg.py +++ b/tests/test_nhg.py @@ -5,10 +5,8 @@ import pytest from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestNextHopGroup(object): def test_route_nhg(self, dvs, testlog): config_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) diff --git a/tests/test_pfc.py b/tests/test_pfc.py index 569724c420..6ef1853fc1 100644 --- a/tests/test_pfc.py +++ b/tests/test_pfc.py @@ -2,7 +2,6 @@ import pytest from swsscommon import swsscommon -from flaky import flaky def getBitMaskStr(bits): @@ -60,7 +59,6 @@ def getPortAttr(dvs, port_oid, port_attr): return '' -@pytest.mark.flaky class TestPfc(object): def test_PfcAsymmetric(self, dvs, testlog): diff --git a/tests/test_policer.py b/tests/test_policer.py index 53fdc37930..d88571cce5 100644 --- a/tests/test_policer.py +++ b/tests/test_policer.py @@ -3,10 +3,8 @@ import time from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestPolicer(object): def test_PolicerBasic(self, dvs, testlog): dvs.setup_db() diff --git a/tests/test_port.py b/tests/test_port.py index cd0e416ed3..d57031077f 100644 --- a/tests/test_port.py +++ b/tests/test_port.py @@ -3,10 +3,8 @@ import pytest from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestPort(object): def test_PortMtu(self, dvs, testlog): pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_port_an.py b/tests/test_port_an.py index b9081df85b..7c2b41fdb8 100644 --- a/tests/test_port_an.py +++ b/tests/test_port_an.py @@ -3,10 +3,8 @@ import pytest from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestPortAutoNeg(object): def test_PortAutoNegCold(self, dvs, testlog): diff --git a/tests/test_port_buffer_rel.py b/tests/test_port_buffer_rel.py index 411923071a..4f7a0ae8fd 100644 --- a/tests/test_port_buffer_rel.py +++ b/tests/test_port_buffer_rel.py @@ -2,11 +2,9 @@ import pytest from swsscommon import swsscommon -from flaky import flaky # The test check that the ports will be up, when the admin state is UP by conf db. -@pytest.mark.flaky class TestPortBuffer(object): def test_PortsAreUpAfterBuffers(self, dvs, testlog): num_ports = 32 diff --git a/tests/test_port_config.py b/tests/test_port_config.py index 7f7c69c1a0..da07abd73c 100644 --- a/tests/test_port_config.py +++ b/tests/test_port_config.py @@ -4,7 +4,6 @@ import pytest from swsscommon import swsscommon -from flaky import flaky @pytest.yield_fixture @@ -15,7 +14,6 @@ def port_config(request, dvs): dvs.runcmd("mv %s.bak %s" % (file_name, file_name)) -@pytest.mark.flaky class TestPortConfig(object): def getPortName(self, dvs, port_vid): diff --git a/tests/test_port_mac_learn.py b/tests/test_port_mac_learn.py index b9258b0f36..b2f1dccceb 100644 --- a/tests/test_port_mac_learn.py +++ b/tests/test_port_mac_learn.py @@ -3,10 +3,8 @@ import pytest from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestPortMacLearn(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(swsscommon.APPL_DB, dvs.redis_sock, 0) diff --git a/tests/test_portchannel.py b/tests/test_portchannel.py index c048c4ba59..ae28641a4e 100644 --- a/tests/test_portchannel.py +++ b/tests/test_portchannel.py @@ -4,10 +4,8 @@ import pytest from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestPortchannel(object): def test_Portchannel(self, dvs, testlog): diff --git a/tests/test_qos_map.py b/tests/test_qos_map.py index 4dd3d3f577..f384434363 100644 --- a/tests/test_qos_map.py +++ b/tests/test_qos_map.py @@ -4,7 +4,6 @@ import time from swsscommon import swsscommon -from flaky import flaky CFG_DOT1P_TO_TC_MAP_TABLE_NAME = "DOT1P_TO_TC_MAP" CFG_DOT1P_TO_TC_MAP_KEY = "AZURE" @@ -24,7 +23,6 @@ CFG_PORT_TABLE_NAME = "PORT" -@pytest.mark.flaky class TestDot1p(object): def connect_dbs(self, dvs): self.asic_db = swsscommon.DBConnector(1, dvs.redis_sock, 0) diff --git a/tests/test_route.py b/tests/test_route.py index f016e798c0..0082df7453 100644 --- a/tests/test_route.py +++ b/tests/test_route.py @@ -5,10 +5,8 @@ import pytest from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestRoute(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_setro.py b/tests/test_setro.py index 5fbb77f605..896d0c1fac 100644 --- a/tests/test_setro.py +++ b/tests/test_setro.py @@ -5,10 +5,8 @@ from pprint import pprint from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestSetRo(object): def test_SetReadOnlyAttribute(self, dvs, testlog): diff --git a/tests/test_sflow.py b/tests/test_sflow.py index 37fbd56f03..cbff6d362d 100644 --- a/tests/test_sflow.py +++ b/tests/test_sflow.py @@ -3,10 +3,8 @@ import pytest from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestSflow(object): speed_rate_table = { "400000":"40000", diff --git a/tests/test_speed.py b/tests/test_speed.py index 82952df81f..3ed70ae4d7 100644 --- a/tests/test_speed.py +++ b/tests/test_speed.py @@ -9,10 +9,8 @@ import pytest from swsscommon import swsscommon -from flaky import flaky -@pytest.mark.flaky class TestSpeedSet(object): num_ports = 32 def test_SpeedAndBufferSet(self, dvs, testlog): diff --git a/tests/test_sub_port_intf.py b/tests/test_sub_port_intf.py index 5ea2198c48..1a4f6cef54 100644 --- a/tests/test_sub_port_intf.py +++ b/tests/test_sub_port_intf.py @@ -3,7 +3,6 @@ import json from swsscommon import swsscommon -from flaky import flaky CFG_VLAN_SUB_INTF_TABLE_NAME = "VLAN_SUB_INTERFACE" CFG_PORT_TABLE_NAME = "PORT" @@ -19,7 +18,6 @@ ADMIN_STATUS = "admin_status" -@pytest.mark.flaky class TestSubPortIntf(object): PHYSICAL_PORT_UNDER_TEST = "Ethernet64" SUB_PORT_INTERFACE_UNDER_TEST = "Ethernet64.10" diff --git a/tests/test_switch.py b/tests/test_switch.py index 13430e9402..beef4c411a 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -2,7 +2,6 @@ import pytest from swsscommon import swsscommon -from flaky import flaky def create_entry(tbl, key, pairs): @@ -62,7 +61,6 @@ def vxlan_switch_test(dvs, oid, port, mac): ) -@pytest.mark.flaky class TestSwitch(object): ''' Test- Check switch attributes diff --git a/tests/test_tunnel.py b/tests/test_tunnel.py index 5880a57b03..bd8273e703 100644 --- a/tests/test_tunnel.py +++ b/tests/test_tunnel.py @@ -2,14 +2,12 @@ import pytest from swsscommon import swsscommon -from flaky import flaky def create_fvs(**kwargs): return swsscommon.FieldValuePairs(kwargs.items()) -@pytest.mark.flaky class TestTunnelBase(object): APP_TUNNEL_DECAP_TABLE_NAME = "TUNNEL_DECAP_TABLE" ASIC_TUNNEL_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL" diff --git a/tests/test_vlan.py b/tests/test_vlan.py index 9b16acd797..f7551ff66f 100644 --- a/tests/test_vlan.py +++ b/tests/test_vlan.py @@ -5,11 +5,9 @@ import platform from swsscommon import swsscommon -from flaky import flaky from distutils.version import StrictVersion -@pytest.mark.flaky class TestVlan(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_vnet.py b/tests/test_vnet.py index 9574c32226..14909c8b5e 100644 --- a/tests/test_vnet.py +++ b/tests/test_vnet.py @@ -5,7 +5,6 @@ import pytest from swsscommon import swsscommon -from flaky import flaky from pprint import pprint @@ -952,7 +951,6 @@ def check_del_vnet_routes(self, dvs, name): self.vnet_bitmap_route_ids.remove(old_bitmap_route[0]) -@pytest.mark.flaky class TestVnetOrch(object): def get_vnet_obj(self): diff --git a/tests/test_vnet_bitmap.py b/tests/test_vnet_bitmap.py index ac456b279a..e7f1d4c52f 100644 --- a/tests/test_vnet_bitmap.py +++ b/tests/test_vnet_bitmap.py @@ -4,7 +4,6 @@ import test_vnet as vnet from swsscommon import swsscommon -from flaky import flaky # Define fake platform for "DVS" fixture, so it will set "platform" environment variable for "orchagent". @@ -18,7 +17,6 @@ Difference between these two implementations is in set SAI attributes, so different values should be checked in ASIC_DB. This class should override "get_vnet_obj()" method in order to return object with appropriate implementation of "check" APIs. ''' -@pytest.mark.flaky class TestVnetBitmapOrch(vnet.TestVnetOrch): ''' diff --git a/tests/test_vrf.py b/tests/test_vrf.py index ff2c2841a8..6b04ce63f2 100644 --- a/tests/test_vrf.py +++ b/tests/test_vrf.py @@ -4,11 +4,9 @@ import pytest from swsscommon import swsscommon -from flaky import flaky from pprint import pprint -@pytest.mark.flaky class TestVrf(object): def setup_db(self, dvs): self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) diff --git a/tests/test_vxlan_tunnel.py b/tests/test_vxlan_tunnel.py index ff533b74c7..48826952d4 100644 --- a/tests/test_vxlan_tunnel.py +++ b/tests/test_vxlan_tunnel.py @@ -5,7 +5,6 @@ import pytest from swsscommon import swsscommon -from flaky import flaky from pprint import pprint @@ -244,7 +243,6 @@ def get_lo(dvs): return lo_id -@pytest.mark.flaky class TestVxlan(object): def test_vxlan_term_orch(self, dvs, testlog): tunnel_map_ids = set() diff --git a/tests/test_warm_reboot.py b/tests/test_warm_reboot.py index 5f94654fbd..6427453a1f 100644 --- a/tests/test_warm_reboot.py +++ b/tests/test_warm_reboot.py @@ -5,7 +5,6 @@ import pytest from swsscommon import swsscommon -from flaky import flaky # macros for number of interfaces and number of neighbors @@ -236,7 +235,6 @@ def ping_new_ips(dvs): dvs.runcmd(['sh', '-c', "ping6 -c 1 -W 0 -q {}00::{} > /dev/null 2>&1".format(i*4,j+NUM_NEIGH_PER_INTF+2)]) -@pytest.mark.flaky class TestWarmReboot(object): def test_PortSyncdWarmRestart(self, dvs, testlog): diff --git a/tests/test_watermark.py b/tests/test_watermark.py index fd79dd8dfd..d3d6ebedba 100644 --- a/tests/test_watermark.py +++ b/tests/test_watermark.py @@ -6,7 +6,6 @@ import redis from swsscommon import swsscommon -from flaky import flaky class SaiWmStats: queue_shared = "SAI_QUEUE_STAT_SHARED_WATERMARK_BYTES" @@ -20,7 +19,6 @@ class WmTables: user = "USER_WATERMARKS" -@pytest.mark.flaky class TestWatermark(object): DEFAULT_TELEMETRY_INTERVAL = 120 From 73ab143ac622243bef4ed63f7bf03ed48d6666fc Mon Sep 17 00:00:00 2001 From: lguohan Date: Wed, 29 Jan 2020 17:26:32 -0800 Subject: [PATCH 62/63] [pytest]: print out stdout/stderr message when cmd fails (#1188) --- tests/conftest.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 16c0ad10b7..65d954a9a7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,9 +14,9 @@ from swsscommon import swsscommon def ensure_system(cmd): - rc = os.WEXITSTATUS(os.system(cmd)) + (rc, output) = commands.getstatusoutput(cmd) if rc: - raise RuntimeError('Failed to run command: %s' % cmd) + raise RuntimeError('Failed to run command: %s. rc=%d. output: %s' % (cmd, rc, output)) def pytest_addoption(parser): parser.addoption("--dvsname", action="store", default=None, @@ -214,7 +214,7 @@ def __init__(self, name=None, imgname=None, keeptb=False, fakeplatform=None): # mount redis to base to unique directory self.mount = "/var/run/redis-vs/{}".format(self.ctn_sw.name) - os.system("mkdir -p {}".format(self.mount)) + ensure_system("mkdir -p {}".format(self.mount)) self.environment = ["fake_platform={}".format(fakeplatform)] if fakeplatform else [] @@ -385,7 +385,7 @@ def get_logs(self, modname=None): else: log_dir = "log/{}".format(modname) os.system("rm -rf {}".format(log_dir)) - os.system("mkdir -p {}".format(log_dir)) + ensure_system("mkdir -p {}".format(log_dir)) p = subprocess.Popen(["tar", "--no-same-owner", "-C", "./{}".format(log_dir), "-x"], stdin=subprocess.PIPE) for x in stream: p.stdin.write(x) @@ -393,7 +393,7 @@ def get_logs(self, modname=None): p.wait() if p.returncode: raise RuntimeError("Failed to unpack the archive.") - os.system("chmod a+r -R log") + ensure_system("chmod a+r -R log") def add_log_marker(self, file=None): marker = "=== start marker {} ===".format(datetime.now().isoformat()) From 750982e4501676ad2abec45d31049fdb8073b06e Mon Sep 17 00:00:00 2001 From: B S Rama krishna <54837361+banagiri@users.noreply.github.com> Date: Thu, 30 Jan 2020 16:13:06 +0530 Subject: [PATCH 63/63] adding mirrorv6 support for mrvl platform (#1190) --- orchagent/aclorch.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index 52855c87fd..9c7845bc13 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -2113,6 +2113,7 @@ void AclOrch::init(vector& connectors, PortsOrch *portOrch, Mirr if (platform == BRCM_PLATFORM_SUBSTRING || platform == MLNX_PLATFORM_SUBSTRING || platform == BFN_PLATFORM_SUBSTRING || + platform == MRVL_PLATFORM_SUBSTRING || platform == NPS_PLATFORM_SUBSTRING) { m_mirrorTableCapabilities = @@ -2144,6 +2145,7 @@ void AclOrch::init(vector& connectors, PortsOrch *portOrch, Mirr // In Mellanox platform, V4 and V6 rules are stored in different tables if (platform == MLNX_PLATFORM_SUBSTRING || + platform == MRVL_PLATFORM_SUBSTRING || platform == BFN_PLATFORM_SUBSTRING) { m_isCombinedMirrorV6Table = false; }