diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index f94e6d3bfa..ea46da3618 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -107,6 +107,8 @@ orchagent_SOURCES = \ response_publisher.cpp \ nvgreorch.cpp \ zmqorch.cpp \ + dash/dashenifwdorch.cpp \ + dash/dashenifwdinfo.cpp \ dash/dashorch.cpp \ dash/dashrouteorch.cpp \ dash/dashvnetorch.cpp \ diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index 60ab40dca6..9f0af9b51c 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -51,6 +51,8 @@ extern Directory gDirectory; const int TCP_PROTOCOL_NUM = 6; // TCP protocol number +#define MAC_EXACT_MATCH "ff:ff:ff:ff:ff:ff" + acl_rule_attr_lookup_t aclMatchLookup = { { MATCH_IN_PORTS, SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS }, @@ -79,6 +81,8 @@ acl_rule_attr_lookup_t aclMatchLookup = { MATCH_TUNNEL_VNI, SAI_ACL_ENTRY_ATTR_FIELD_TUNNEL_VNI }, { MATCH_INNER_ETHER_TYPE, SAI_ACL_ENTRY_ATTR_FIELD_INNER_ETHER_TYPE }, { MATCH_INNER_IP_PROTOCOL, SAI_ACL_ENTRY_ATTR_FIELD_INNER_IP_PROTOCOL }, + { MATCH_INNER_SRC_MAC, SAI_ACL_ENTRY_ATTR_FIELD_INNER_SRC_MAC }, + { MATCH_INNER_DST_MAC, SAI_ACL_ENTRY_ATTR_FIELD_INNER_DST_MAC }, { MATCH_INNER_L4_SRC_PORT, SAI_ACL_ENTRY_ATTR_FIELD_INNER_L4_SRC_PORT }, { MATCH_INNER_L4_DST_PORT, SAI_ACL_ENTRY_ATTR_FIELD_INNER_L4_DST_PORT }, { MATCH_BTH_OPCODE, SAI_ACL_ENTRY_ATTR_FIELD_BTH_OPCODE}, @@ -619,6 +623,7 @@ bool AclTableRangeMatch::validateAclRuleMatch(const AclRule& rule) const return true; } + void AclRule::TunnelNH::load(const std::string& target) { parse(target); @@ -648,6 +653,7 @@ void AclRule::TunnelNH::clear() vxlan_orch->removeNextHopTunnel(tunnel_name, endpoint_ip, mac, vni); } + string AclTableType::getName() const { return m_name; @@ -908,6 +914,13 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) { matchData.data.booldata = (attr_name == "true"); } + else if (attr_name == MATCH_INNER_DST_MAC || attr_name == MATCH_INNER_SRC_MAC) + { + swss::MacAddress mac(attr_value); + swss::MacAddress mask(MAC_EXACT_MATCH); + memcpy(matchData.data.mac, mac.getMac(), sizeof(sai_mac_t)); + memcpy(matchData.mask.mac, mask.getMac(), sizeof(sai_mac_t)); + } else if (attr_name == MATCH_IN_PORTS) { auto ports = tokenize(attr_value, ','); diff --git a/orchagent/aclorch.h b/orchagent/aclorch.h index beb389260e..5cf3241e42 100644 --- a/orchagent/aclorch.h +++ b/orchagent/aclorch.h @@ -51,6 +51,8 @@ #define MATCH_INNER_IP_PROTOCOL "INNER_IP_PROTOCOL" #define MATCH_INNER_L4_SRC_PORT "INNER_L4_SRC_PORT" #define MATCH_INNER_L4_DST_PORT "INNER_L4_DST_PORT" +#define MATCH_INNER_SRC_MAC "INNER_SRC_MAC" +#define MATCH_INNER_DST_MAC "INNER_DST_MAC" #define MATCH_BTH_OPCODE "BTH_OPCODE" #define MATCH_AETH_SYNDROME "AETH_SYNDROME" #define MATCH_TUNNEL_TERM "TUNNEL_TERM" diff --git a/orchagent/dash/dashenifwdinfo.cpp b/orchagent/dash/dashenifwdinfo.cpp new file mode 100644 index 0000000000..b830d324ef --- /dev/null +++ b/orchagent/dash/dashenifwdinfo.cpp @@ -0,0 +1,461 @@ +#include "dashenifwdorch.h" + +using namespace swss; +using namespace std; + +const int EniAclRule::BASE_PRIORITY = 9996; +const vector EniAclRule::RULE_NAMES = { + "IN", + "OUT", + "IN_TERM", + "OUT_TERM" +}; + +unique_ptr EniNH::createNextHop(dpu_type_t type, const IpAddress& ip) +{ + if (type == dpu_type_t::LOCAL) + { + return unique_ptr(new LocalEniNH(ip)); + } + return unique_ptr(new RemoteEniNH(ip)); +} + + +void LocalEniNH::resolve(EniInfo& eni) +{ + auto& ctx = eni.getCtx(); + auto alias = ctx->getNbrAlias(endpoint_); + + NextHopKey nh(endpoint_, alias); + if (ctx->isNeighborResolved(nh)) + { + setStatus(endpoint_status_t::RESOLVED); + return ; + } + + ctx->resolveNeighbor(nh); + setStatus(endpoint_status_t::UNRESOLVED); +} + +string LocalEniNH::getRedirectVal() +{ + return endpoint_.to_string(); +} + + +void RemoteEniNH::resolve(EniInfo& eni) +{ + auto& ctx = eni.getCtx(); + auto vnet = eni.getVnet(); + + if (!ctx->findVnetTunnel(vnet, tunnel_name_)) + { + SWSS_LOG_ERROR("Couldn't find tunnel name for Vnet %s", vnet.c_str()); + setStatus(endpoint_status_t::UNRESOLVED); + return ; + } + + if (ctx->handleTunnelNH(tunnel_name_, endpoint_, true)) + { + setStatus(endpoint_status_t::RESOLVED); + } + else + { + setStatus(endpoint_status_t::UNRESOLVED); + } +} + +void RemoteEniNH::destroy(EniInfo& eni) +{ + auto& ctx = eni.getCtx(); + ctx->handleTunnelNH(tunnel_name_, endpoint_, false); +} + +string RemoteEniNH::getRedirectVal() +{ + return endpoint_.to_string() + "@" + tunnel_name_; +} + +void EniAclRule::setKey(EniInfo& eni) +{ + name_ = string(ENI_REDIRECT_TABLE) + ":" + eni.toKey() + "_" + EniAclRule::RULE_NAMES[type_]; +} + +update_type_t EniAclRule::processUpdate(EniInfo& eni) +{ + SWSS_LOG_ENTER(); + auto& ctx = eni.getCtx(); + IpAddress primary_endp; + dpu_type_t primary_type = LOCAL; + update_type_t update_type = PRIMARY_UPDATE; + uint64_t primary_id; + + if (type_ == rule_type_t::INBOUND_TERM || type_ == rule_type_t::OUTBOUND_TERM) + { + /* Tunnel term entries always use local endpoint regardless of primary id */ + if (!eni.findLocalEp(primary_id)) + { + SWSS_LOG_ERROR("No Local endpoint was found for Rule: %s", getKey().c_str()); + return update_type_t::INVALID; + } + } + else + { + primary_id = eni.getPrimaryId(); + } + + if (!ctx->dpu_info.getType(primary_id, primary_type)) + { + SWSS_LOG_ERROR("No primaryId in DPU Table %" PRIu64 "", primary_id); + return update_type_t::INVALID; + } + + if (primary_type == LOCAL) + { + ctx->dpu_info.getPaV4(primary_id, primary_endp); + } + else + { + ctx->dpu_info.getNpuV4(primary_id, primary_endp); + } + + if (nh_ == nullptr) + { + /* Create Request */ + update_type = update_type_t::CREATE; + } + else if (nh_->getType() != primary_type || nh_->getEp() != primary_endp) + { + /* primary endpoint is switched */ + update_type = update_type_t::PRIMARY_UPDATE; + SWSS_LOG_NOTICE("Endpoint IP for Rule %s updated from %s -> %s", getKey().c_str(), + nh_->getEp().to_string().c_str(), primary_endp.to_string().c_str()); + } + else if(nh_->getStatus() == RESOLVED) + { + /* No primary update and nexthop resolved, no update + Neigh Down on a existing local endpoint needs special handling */ + return update_type_t::IDEMPOTENT; + } + + if (update_type == update_type_t::PRIMARY_UPDATE || update_type == update_type_t::CREATE) + { + if (nh_ != nullptr) + { + nh_->destroy(eni); + } + nh_.reset(); + nh_ = EniNH::createNextHop(primary_type, primary_endp); + } + + /* Try to resolve the neighbor */ + nh_->resolve(eni); + return update_type; +} + +void EniAclRule::fire(EniInfo& eni) +{ + SWSS_LOG_ENTER(); + + auto update_type = processUpdate(eni); + + if (update_type == update_type_t::INVALID || update_type == update_type_t::IDEMPOTENT) + { + if (update_type == update_type_t::INVALID) + { + setState(rule_state_t::FAILED); + } + return ; + } + + auto& ctx = eni.getCtx(); + auto key = getKey(); + + if (state_ == rule_state_t::INSTALLED && update_type == update_type_t::PRIMARY_UPDATE) + { + /* + Delete the complete rule before updating it, + ACLOrch Doesn't support incremental updates + */ + ctx->rule_table->del(key); + setState(rule_state_t::UNINSTALLED); + SWSS_LOG_NOTICE("EniFwd ACL Rule %s deleted", key.c_str()); + } + + if (nh_->getStatus() != endpoint_status_t::RESOLVED) + { + /* Wait until the endpoint is resolved */ + setState(rule_state_t::PENDING); + return ; + } + + vector fv_ = { + { RULE_PRIORITY, to_string(BASE_PRIORITY + static_cast(type_)) }, + { MATCH_DST_IP, ctx->getVip().to_string() }, + { getMacMatchDirection(eni), eni.getMac().to_string() }, + { ACTION_REDIRECT_ACTION, nh_->getRedirectVal() } + }; + + if (type_ == rule_type_t::INBOUND_TERM || type_ == rule_type_t::OUTBOUND_TERM) + { + fv_.push_back({MATCH_TUNNEL_TERM, "true"}); + } + + if (type_ == rule_type_t::OUTBOUND || type_ == rule_type_t::OUTBOUND_TERM) + { + fv_.push_back({MATCH_TUNNEL_VNI, to_string(eni.getOutVni())}); + } + + ctx->rule_table->set(key, fv_); + setState(INSTALLED); + SWSS_LOG_NOTICE("EniFwd ACL Rule %s installed", key.c_str()); +} + +string EniAclRule::getMacMatchDirection(EniInfo& eni) +{ + if (type_ == OUTBOUND || type_ == OUTBOUND_TERM) + { + return eni.getOutMacLookup(); + } + return MATCH_INNER_DST_MAC; +} + +void EniAclRule::destroy(EniInfo& eni) +{ + if (state_ == rule_state_t::INSTALLED) + { + auto key = getKey(); + auto& ctx = eni.getCtx(); + ctx->rule_table->del(key); + if (nh_ != nullptr) + { + nh_->destroy(eni); + } + nh_.reset(); + setState(rule_state_t::UNINSTALLED); + } +} + +void EniAclRule::setState(rule_state_t state) +{ + SWSS_LOG_ENTER(); + SWSS_LOG_INFO("EniFwd ACL Rule: %s State Change %d -> %d", getKey().c_str(), state_, state); + state_ = state; +} + + +EniInfo::EniInfo(const string& mac_str, const string& vnet, const shared_ptr& ctx) : + mac_(mac_str), vnet_name_(vnet), ctx(ctx) +{ + formatMac(); +} + +string EniInfo::toKey() const +{ + return vnet_name_ + "_" + mac_key_; +} + +void EniInfo::fireRule(rule_type_t rule_type) +{ + auto rule_itr = rule_container_.find(rule_type); + if (rule_itr != rule_container_.end()) + { + rule_itr->second.fire(*this); + } +} + +void EniInfo::fireAllRules() +{ + for (auto& rule_tuple : rule_container_) + { + fireRule(rule_tuple.first); + } +} + +bool EniInfo::destroy(const Request& db_request) +{ + for (auto& rule_tuple : rule_container_) + { + rule_tuple.second.destroy(*this); + } + rule_container_.clear(); + return true; +} + +bool EniInfo::create(const Request& db_request) +{ + SWSS_LOG_ENTER(); + + auto updates = db_request.getAttrFieldNames(); + auto itr_ep_list = updates.find(ENI_FWD_VDPU_IDS); + auto itr_primary_id = updates.find(ENI_FWD_PRIMARY); + auto itr_out_vni = updates.find(ENI_FWD_OUT_VNI); + auto itr_out_mac_dir = updates.find(ENI_FWD_OUT_MAC_LOOKUP); + + /* Validation Checks */ + if (itr_ep_list == updates.end() || itr_primary_id == updates.end()) + { + SWSS_LOG_ERROR("Invalid DASH_ENI_FORWARD_TABLE request: No endpoint/primary"); + return false; + } + + ep_list_ = db_request.getAttrUintList(ENI_FWD_VDPU_IDS); + primary_id_ = db_request.getAttrUint(ENI_FWD_PRIMARY); + + uint64_t local_id; + bool tunn_term_allow = findLocalEp(local_id); + bool outbound_allow = false; + + /* Create Rules */ + rule_container_.emplace(piecewise_construct, + forward_as_tuple(rule_type_t::INBOUND), + forward_as_tuple(rule_type_t::INBOUND, *this)); + rule_container_.emplace(piecewise_construct, + forward_as_tuple(rule_type_t::OUTBOUND), + forward_as_tuple(rule_type_t::OUTBOUND, *this)); + + if (tunn_term_allow) + { + /* Create rules for tunnel termination if required */ + rule_container_.emplace(piecewise_construct, + forward_as_tuple(rule_type_t::INBOUND_TERM), + forward_as_tuple(rule_type_t::INBOUND_TERM, *this)); + rule_container_.emplace(piecewise_construct, + forward_as_tuple(rule_type_t::OUTBOUND_TERM), + forward_as_tuple(rule_type_t::OUTBOUND_TERM, *this)); + } + + /* Infer Direction to check MAC for outbound rules */ + if (itr_out_mac_dir == updates.end()) + { + outbound_mac_lookup_ = MATCH_INNER_SRC_MAC; + } + else + { + auto str = db_request.getAttrString(ENI_FWD_OUT_MAC_LOOKUP); + if (str == OUT_MAC_DIR) + { + outbound_mac_lookup_ = MATCH_INNER_DST_MAC; + } + else + { + outbound_mac_lookup_ = MATCH_INNER_SRC_MAC; + } + } + + /* Infer tunnel_vni for the outbound rules */ + if (itr_out_vni == updates.end()) + { + if (ctx->findVnetVni(vnet_name_, outbound_vni_)) + { + outbound_allow = true; + } + else + { + SWSS_LOG_ERROR("Invalid VNET: No VNI. Cannot install outbound rules: %s", toKey().c_str()); + } + } + else + { + outbound_vni_ = db_request.getAttrUint(ENI_FWD_OUT_VNI); + outbound_allow = true; + } + + fireRule(rule_type_t::INBOUND); + + if (tunn_term_allow) + { + fireRule(rule_type_t::INBOUND_TERM); + } + + if (outbound_allow) + { + fireRule(rule_type_t::OUTBOUND); + } + + if (tunn_term_allow && outbound_allow) + { + fireRule(rule_type_t::OUTBOUND_TERM); + } + + return true; +} + +bool EniInfo::update(const NeighborUpdate& nbr_update) +{ + if (nbr_update.add) + { + fireAllRules(); + } + else + { + /* + Neighbor Delete handling not supported yet + When this update comes, ACL rule must be deleted first, followed by the NEIGH object + */ + } + return true; +} + +bool EniInfo::update(const Request& db_request) +{ + SWSS_LOG_ENTER(); + + /* Only primary_id is expected to change after ENI is created */ + auto updates = db_request.getAttrFieldNames(); + auto itr_primary_id = updates.find(ENI_FWD_PRIMARY); + + /* Validation Checks */ + if (itr_primary_id == updates.end()) + { + throw logic_error("Invalid DASH_ENI_FORWARD_TABLE update: No primary idx"); + } + + if (getPrimaryId() == db_request.getAttrUint(ENI_FWD_PRIMARY)) + { + /* No update in the primary id, return true */ + return true; + } + + /* Update local primary id and fire the rules */ + primary_id_ = db_request.getAttrUint(ENI_FWD_PRIMARY); + fireAllRules(); + + return true; +} + +bool EniInfo::findLocalEp(uint64_t& local_endpoint) const +{ + /* Check if atleast one of the endpoints is local */ + bool found = false; + for (auto idx : ep_list_) + { + dpu_type_t val = dpu_type_t::EXTERNAL; + if (ctx->dpu_info.getType(idx, val) && val == dpu_type_t::LOCAL) + { + if (!found) + { + found = true; + local_endpoint = idx; + } + else + { + SWSS_LOG_WARN("Multiple Local Endpoints for the ENI %s found, proceeding with %" PRIu64 "" , + mac_.to_string().c_str(), local_endpoint); + } + } + } + return found; +} + +void EniInfo::formatMac() +{ + /* f4:93:9f:ef:c4:7e -> F4939FEFC47E */ + mac_key_.clear(); + auto mac_orig = mac_.to_string(); + for (char c : mac_orig) { + if (c != ':') { // Skip colons + mac_key_ += static_cast(toupper(c)); + } + } +} diff --git a/orchagent/dash/dashenifwdorch.cpp b/orchagent/dash/dashenifwdorch.cpp new file mode 100644 index 0000000000..2d1bfcded3 --- /dev/null +++ b/orchagent/dash/dashenifwdorch.cpp @@ -0,0 +1,569 @@ +#include +#include +#include "dashenifwdorch.h" +#include "directory.h" + +extern Directory gDirectory; + +using namespace swss; +using namespace std; + +DashEniFwdOrch::DashEniFwdOrch(DBConnector* cfgDb, DBConnector* applDb, const std::string& tableName, NeighOrch* neighOrch) + : Orch2(applDb, tableName, request_), neighorch_(neighOrch) +{ + SWSS_LOG_ENTER(); + acl_table_type_ = make_unique(applDb, APP_ACL_TABLE_TYPE_TABLE_NAME); + acl_table_ = make_unique(applDb, APP_ACL_TABLE_TABLE_NAME); + ctx = make_shared(cfgDb, applDb); + if (neighorch_) + { + /* Listen to Neighbor events */ + neighorch_->attach(this); + } +} + +DashEniFwdOrch::~DashEniFwdOrch() +{ + if (neighorch_) + { + neighorch_->detach(this); + } +} + +void DashEniFwdOrch::update(SubjectType type, void *cntx) +{ + SWSS_LOG_ENTER(); + + switch(type) { + case SUBJECT_TYPE_NEIGH_CHANGE: + { + NeighborUpdate *update = static_cast(cntx); + handleNeighUpdate(*update); + break; + } + default: + // Ignore the update + return; + } +} + +void DashEniFwdOrch::handleNeighUpdate(const NeighborUpdate& update) +{ + SWSS_LOG_ENTER(); + auto ipaddr = update.entry.ip_address; + auto dpu_id_itr = neigh_dpu_map_.find(ipaddr); + if (dpu_id_itr == neigh_dpu_map_.end()) + { + return ; + } + SWSS_LOG_NOTICE("Neighbor Update: %s, add: %d", ipaddr.to_string().c_str(), update.add); + + auto dpu_id = dpu_id_itr->second; + auto itr = dpu_eni_map_.lower_bound(dpu_id); + auto itr_end = dpu_eni_map_.upper_bound(dpu_id); + + while (itr != itr_end) + { + /* Find the eni_itr */ + auto eni_itr = eni_container_.find(itr->second); + if (eni_itr != eni_container_.end()) + { + eni_itr->second.update(update); + } + itr++; + } +} + +void DashEniFwdOrch::initAclTableCfg() +{ + vector match_list = { + MATCH_TUNNEL_VNI, + MATCH_DST_IP, + MATCH_INNER_SRC_MAC, + MATCH_INNER_DST_MAC, + MATCH_TUNNEL_TERM + }; + + auto concat = [](const std::string &a, const std::string &b) { return a + "," + b; }; + + std::string matches = std::accumulate( + std::next(match_list.begin()), match_list.end(), match_list[0], + concat); + + string bpoint_types = string(BIND_POINT_TYPE_PORT) + "," + string(BIND_POINT_TYPE_PORTCHANNEL); + + vector fv_ = { + { ACL_TABLE_TYPE_MATCHES, matches}, + { ACL_TABLE_TYPE_ACTIONS, ACTION_REDIRECT_ACTION }, + { ACL_TABLE_TYPE_BPOINT_TYPES, bpoint_types} + }; + + acl_table_type_->set(ENI_REDIRECT_TABLE_TYPE, fv_); + + auto ports = ctx->getBindPoints(); + std::string ports_str; + + if (!ports.empty()) + { + ports_str = std::accumulate(std::next(ports.begin()), ports.end(), ports[0], concat); + } + + /* Write ACL Table */ + vector table_fv_ = { + { ACL_TABLE_DESCRIPTION, "Contains Rule for DASH ENI Based Forwarding"}, + { ACL_TABLE_TYPE, ENI_REDIRECT_TABLE_TYPE }, + { ACL_TABLE_STAGE, STAGE_INGRESS }, + { ACL_TABLE_PORTS, ports_str } + }; + + acl_table_->set(ENI_REDIRECT_TABLE, table_fv_); +} + +void DashEniFwdOrch::initLocalEndpoints() +{ + auto ids = ctx->dpu_info.getIds(); + dpu_type_t primary_type = CLUSTER; + IpAddress local_endp; + for (auto id : ids) + { + if(ctx->dpu_info.getType(id, primary_type) && primary_type == dpu_type_t::LOCAL) + { + if(ctx->dpu_info.getPaV4(id, local_endp)) + { + neigh_dpu_map_.insert(make_pair(local_endp, id)); + SWSS_LOG_NOTICE("Local DPU endpoint detected %s", local_endp.to_string().c_str()); + + /* Try to resovle the neighbor */ + auto alias = ctx->getNbrAlias(local_endp); + NextHopKey nh(local_endp, alias); + + if (ctx->isNeighborResolved(nh)) + { + SWSS_LOG_WARN("Neighbor already populated.. Not Expected"); + } + ctx->resolveNeighbor(nh); + } + } + } +} + +void DashEniFwdOrch::handleEniDpuMapping(uint64_t id, MacAddress mac, bool add) +{ + /* Make sure id is local */ + dpu_type_t primary_type = CLUSTER; + if(ctx->dpu_info.getType(id, primary_type) && primary_type == dpu_type_t::LOCAL) + { + if (add) + { + dpu_eni_map_.insert(make_pair(id, mac)); + } + else + { + auto range = dpu_eni_map_.equal_range(id); + for (auto it = range.first; it != range.second; ++it) + { + if (it->second == mac) + { + dpu_eni_map_.erase(it); + break; + } + } + } + } +} + +void DashEniFwdOrch::lazyInit() +{ + if (ctx_initialized_) + { + return ; + } + /* + 1. DpuRegistry + 2. Other Orch ptrs + 3. Internal dpu-id mappings + 4. Write ACL Table Cfg + */ + ctx->initialize(); + ctx->populateDpuRegistry(); + initAclTableCfg(); + initLocalEndpoints(); + ctx_initialized_ = true; +} + +bool DashEniFwdOrch::addOperation(const Request& request) +{ + lazyInit(); + + bool new_eni = false; + auto vnet_name = request.getKeyString(0); + auto eni_id = request.getKeyMacAddress(1); + auto eni_itr = eni_container_.find(eni_id); + + if (eni_itr == eni_container_.end()) + { + new_eni = true; + eni_container_.emplace(std::piecewise_construct, + std::forward_as_tuple(eni_id), + std::forward_as_tuple(eni_id.to_string(), vnet_name, ctx)); + + eni_itr = eni_container_.find(eni_id); + } + + if (new_eni) + { + eni_itr->second.create(request); + uint64_t local_ep; + if (eni_itr->second.findLocalEp(local_ep)) + { + /* Add to the local map if the endpoint is found */ + handleEniDpuMapping(local_ep, eni_id, true); + } + } + else + { + eni_itr->second.update(request); + } + return true; +} + +bool DashEniFwdOrch::delOperation(const Request& request) +{ + SWSS_LOG_ENTER(); + auto vnet_name = request.getKeyString(0); + auto eni_id = request.getKeyMacAddress(1); + + auto eni_itr = eni_container_.find(eni_id); + + if (eni_itr == eni_container_.end()) + { + SWSS_LOG_ERROR("Invalid del request %s:%s", vnet_name.c_str(), eni_id.to_string().c_str()); + return true; + } + + bool result = eni_itr->second.destroy(request); + if (result) + { + uint64_t local_ep; + if (eni_itr->second.findLocalEp(local_ep)) + { + /* Add to the local map if the endpoint is found */ + handleEniDpuMapping(local_ep, eni_id, false); + } + } + eni_container_.erase(eni_id); + return true; +} + + +void DpuRegistry::populate(Table* dpuTable) +{ + SWSS_LOG_ENTER(); + std::vector keys; + dpuTable->getKeys(keys); + + for (auto key : keys) + { + try + { + std::vector values; + dpuTable->get(key, values); + + KeyOpFieldsValuesTuple kvo = { + key, SET_COMMAND, values + }; + processDpuTable(kvo); + } + catch(exception& e) + { + SWSS_LOG_ERROR("Failed to parse key:%s in the %s", key.c_str(), CFG_DPU_TABLE); + } + } + SWSS_LOG_INFO("DPU data read. %zu dpus found", dpus_.size()); +} + +void DpuRegistry::processDpuTable(const KeyOpFieldsValuesTuple& kvo) +{ + DpuData data; + + dpu_request_.clear(); + dpu_request_.parse(kvo); + + uint64_t key = dpu_request_.getKeyUint(0); + string type = dpu_request_.getAttrString(DPU_TYPE); + + dpus_ids_.push_back(key); + + if (type == "local") + { + data.type = dpu_type_t::LOCAL; + } + else + { + // External type is not suported + data.type = dpu_type_t::CLUSTER; + } + + data.pa_v4 = dpu_request_.getAttrIP(DPU_PA_V4); + data.npu_v4 = dpu_request_.getAttrIP(DPU_NPU_V4); + dpus_.insert({key, data}); +} + +std::vector DpuRegistry::getIds() +{ + return dpus_ids_; +} + +bool DpuRegistry::getType(uint64_t id, dpu_type_t& val) +{ + auto itr = dpus_.find(id); + if (itr == dpus_.end()) return false; + val = itr->second.type; + return true; +} + +bool DpuRegistry::getPaV4(uint64_t id, swss::IpAddress& val) +{ + auto itr = dpus_.find(id); + if (itr == dpus_.end()) return false; + val = itr->second.pa_v4; + return true; +} + +bool DpuRegistry::getNpuV4(uint64_t id, swss::IpAddress& val) +{ + auto itr = dpus_.find(id); + if (itr == dpus_.end()) return false; + val = itr->second.npu_v4; + return true; +} + +EniFwdCtxBase::EniFwdCtxBase(DBConnector* cfgDb, DBConnector* applDb) +{ + dpu_tbl_ = make_unique(cfgDb, CFG_DPU_TABLE); + port_tbl_ = make_unique
(cfgDb, CFG_PORT_TABLE_NAME); + vip_tbl_ = make_unique
(cfgDb, CFG_VIP_TABLE_TMP); + rule_table = make_unique(applDb, APP_ACL_RULE_TABLE_NAME); + vip_inferred_ = false; +} + +void EniFwdCtxBase::populateDpuRegistry() +{ + dpu_info.populate(dpu_tbl_.get()); +} + +std::set EniFwdCtxBase::findInternalPorts() +{ + std::vector all_ports; + std::set internal_ports; + port_tbl_->getKeys(all_ports); + for (auto& port : all_ports) + { + std::string val; + if (port_tbl_->hget(port, PORT_ROLE, val)) + { + if (val == PORT_ROLE_DPC) + { + internal_ports.insert(port); + } + } + } + return internal_ports; +} + +vector EniFwdCtxBase::getBindPoints() +{ + std::vector bpoints; + auto internal_ports = findInternalPorts(); + auto all_ports = getAllPorts(); + + std::set legitSet; + + /* Add Phy and Lag ports */ + for (auto &it: all_ports) + { + if (it.second.m_type == Port::PHY || it.second.m_type == Port::LAG) + { + legitSet.insert(it.first); + } + } + + /* Remove any Lag Members PHY's */ + for (auto &it: all_ports) + { + Port& port = it.second; + if (port.m_type == Port::LAG) + { + for (auto mem : port.m_members) + { + /* Remove any members that are part of a LAG */ + legitSet.erase(mem); + } + } + } + + /* Filter Internal ports */ + for (auto& port : legitSet) + { + if (internal_ports.find(port) == internal_ports.end()) + { + bpoints.push_back(port); + } + } + + return bpoints; +} + +string EniFwdCtxBase::getNbrAlias(const swss::IpAddress& nh_ip) +{ + auto itr = nh_alias_map_.find(nh_ip); + if (itr != nh_alias_map_.end()) + { + return itr->second; + } + + auto alias = this->getRouterIntfsAlias(nh_ip); + if (!alias.empty()) + { + nh_alias_map_.insert(std::pair(nh_ip, alias)); + } + return alias; +} + +bool EniFwdCtxBase::handleTunnelNH(const std::string& tunnel_name, swss::IpAddress endpoint, bool create) +{ + SWSS_LOG_ENTER(); + + auto nh_key = endpoint.to_string() + "@" + tunnel_name; + auto nh_itr = remote_nh_map_.find(nh_key); + + /* Delete Tunnel NH if ref_count = 0 */ + if (!create) + { + if (nh_itr != remote_nh_map_.end()) + { + if(!--nh_itr->second.first) + { + remote_nh_map_.erase(nh_key); + return removeNextHopTunnel(tunnel_name, endpoint); + } + } + return true; + } + + if (nh_itr == remote_nh_map_.end()) + { + /* Create a tunnel NH */ + auto nh_oid = createNextHopTunnel(tunnel_name, endpoint); + if (nh_oid == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR("Failed to create Tunnel Next Hop, name: %s. endpoint %s", tunnel_name.c_str(), + endpoint.to_string().c_str()); + } + remote_nh_map_.insert(make_pair(nh_key, make_pair(0, nh_oid))); + nh_itr = remote_nh_map_.find(nh_key); + } + nh_itr->second.first++; /* Increase the ref count, indicates number of rules using referencing this */ + return true; +} + +IpPrefix EniFwdCtxBase::getVip() +{ + SWSS_LOG_ENTER(); + + if (!vip_inferred_) + { + std::vector keys; + vip_tbl_->getKeys(keys); + if (keys.empty()) + { + SWSS_LOG_THROW("Invalid Config: VIP info not populated"); + } + + try + { + vip = IpPrefix(keys[0]); + SWSS_LOG_NOTICE("VIP found: %s", vip.to_string().c_str()); + } + catch (std::exception& e) + { + SWSS_LOG_THROW("VIP is not formatted correctly %s", keys[0].c_str()); + } + vip_inferred_ = true; + } + return vip; +} + + +void EniFwdCtx::initialize() +{ + portsorch_ = gDirectory.get(); + neighorch_ = gDirectory.get(); + intfsorch_ = gDirectory.get(); + vnetorch_ = gDirectory.get(); + vxlanorch_ = gDirectory.get(); + assert(portsorch_); + assert(neighorch_); + assert(intfsorch_); + assert(vnetorch_); + assert(vxlanorch_); +} + +bool EniFwdCtx::isNeighborResolved(const NextHopKey& nh) +{ + return neighorch_->isNeighborResolved(nh); +} + +void EniFwdCtx::resolveNeighbor(const NeighborEntry& nh) +{ + /* Neighorch already has the logic to handle the duplicate requests */ + neighorch_->resolveNeighbor(nh); +} + +string EniFwdCtx::getRouterIntfsAlias(const IpAddress &ip, const string &vrf_name) +{ + return intfsorch_->getRouterIntfsAlias(ip, vrf_name); +} + +bool EniFwdCtx::findVnetVni(const string& vnet_name, uint64_t& vni) +{ + if (vnetorch_->isVnetExists(vnet_name)) + { + vni = vnetorch_->getTypePtr(vnet_name)->getVni(); + return true; + } + return false; +} + +bool EniFwdCtx::findVnetTunnel(const string& vnet_name, string& tunnel) +{ + if (vnetorch_->isVnetExists(vnet_name)) + { + tunnel = vnetorch_->getTunnelName(vnet_name); + return true; + } + return false; +} + +std::map& EniFwdCtx::getAllPorts() +{ + return portsorch_->getAllPorts(); +} + +sai_object_id_t EniFwdCtx::createNextHopTunnel(string tunnel_name, IpAddress ip_addr) +{ + return safetyWrapper(vxlanorch_, &VxlanTunnelOrch::createNextHopTunnel, + (sai_object_id_t)SAI_NULL_OBJECT_ID, + (string)tunnel_name, ip_addr, + MacAddress(), + (uint32_t)0); +} + +bool EniFwdCtx::removeNextHopTunnel(string tunnel_name, IpAddress ip_addr) +{ + return safetyWrapper(vxlanorch_, &VxlanTunnelOrch::removeNextHopTunnel, + false, + (std::string)tunnel_name, ip_addr, + MacAddress(), + (uint32_t)0); +} diff --git a/orchagent/dash/dashenifwdorch.h b/orchagent/dash/dashenifwdorch.h new file mode 100644 index 0000000000..835c920bf6 --- /dev/null +++ b/orchagent/dash/dashenifwdorch.h @@ -0,0 +1,396 @@ +#pragma once + +#include +#include +#include "producerstatetable.h" +#include "orch.h" +#include "portsorch.h" +#include "aclorch.h" +#include "neighorch.h" +#include "vnetorch.h" +#include "observer.h" +#include "request_parser.h" +#include +#include + +// TODO: Update in sonic-swss-common schema.h +#define CFG_DPU_TABLE "DPU_TABLE" +#define APP_DASH_ENI_FORWARD_TABLE "DASH_ENI_FORWARD_TABLE" + +// TODO: remove after the schema is finalized and added to swss-common +#define CFG_VIP_TABLE_TMP "VIP_TABLE" + +#define ENI_REDIRECT_TABLE_TYPE "ENI_REDIRECT" +#define ENI_REDIRECT_TABLE "ENI" +#define ENI_FWD_VDPU_IDS "vdpu_ids" +#define ENI_FWD_PRIMARY "primary_vdpu" +#define ENI_FWD_OUT_VNI "outbound_vni" +#define ENI_FWD_OUT_MAC_LOOKUP "outbound_eni_mac_lookup" + +#define DPU_TYPE "type" +#define DPU_STATE "state" +#define DPU_PA_V4 "pa_ipv4" +#define DPU_PA_V6 "pa_ipv6" +#define DPU_NPU_V4 "npu_ipv4" +#define DPU_NPU_V6 "npu_ipv6" + +#define DPU_LOCAL "local" +#define OUT_MAC_DIR "dst" + + + +const request_description_t eni_dash_fwd_desc = { + { REQ_T_STRING, REQ_T_MAC_ADDRESS }, // VNET_NAME, ENI_ID + { + { ENI_FWD_VDPU_IDS, REQ_T_UINT_LIST }, // DPU ID's + { ENI_FWD_PRIMARY, REQ_T_UINT }, + { ENI_FWD_OUT_VNI, REQ_T_UINT }, + { ENI_FWD_OUT_MAC_LOOKUP, REQ_T_STRING }, + }, + { } +}; + +const request_description_t dpu_table_desc = { + { REQ_T_UINT }, // DPU_ID + { + { DPU_TYPE, REQ_T_STRING }, + { DPU_STATE, REQ_T_STRING }, + { DPU_PA_V4, REQ_T_IP }, + { DPU_PA_V6, REQ_T_IP }, + { DPU_NPU_V4, REQ_T_IP }, + { DPU_NPU_V6, REQ_T_IP }, + }, + { DPU_TYPE, DPU_PA_V4, DPU_NPU_V4 } +}; + +typedef enum +{ + LOCAL, + CLUSTER, + EXTERNAL +} dpu_type_t; + +typedef enum +{ + RESOLVED, + UNRESOLVED +} endpoint_status_t; + +typedef enum +{ + FAILED, + PENDING, + INSTALLED, + UNINSTALLED +} rule_state_t; + +typedef enum +{ + INVALID, + IDEMPOTENT, + CREATE, + PRIMARY_UPDATE /* Either NH update or primary endp change */ +} update_type_t; + +typedef enum +{ + INBOUND = 0, + OUTBOUND, + INBOUND_TERM, + OUTBOUND_TERM +} rule_type_t; + + +class DpuRegistry; +class EniNH; +class LocalEniNH; +class RemoteEniNH; +class EniAclRule; +class EniInfo; +class EniFwdCtxBase; +class EniFwdCtx; + + +class DashEniFwdOrch : public Orch2, public Observer +{ +public: + struct EniFwdRequest : public Request + { + EniFwdRequest() : Request(eni_dash_fwd_desc, ':') {} + }; + + DashEniFwdOrch(swss::DBConnector*, swss::DBConnector*, const std::string&, NeighOrch* neigh_orch_); + ~DashEniFwdOrch(); + + /* Refresh the ENIs based on NextHop status */ + void update(SubjectType, void *) override; + +protected: + virtual bool addOperation(const Request& request); + virtual bool delOperation(const Request& request); + EniFwdRequest request_; + +private: + void lazyInit(); + void initAclTableCfg(); + void initLocalEndpoints(); + void handleNeighUpdate(const NeighborUpdate& update); + void handleEniDpuMapping(uint64_t id, MacAddress mac, bool add = true); + + /* multimap because Multiple ENIs can be mapped to the same DPU */ + std::multimap dpu_eni_map_; + /* Local Endpoint -> DPU mapping */ + std::map neigh_dpu_map_; + std::map eni_container_; + + bool ctx_initialized_ = false; + shared_ptr ctx; + unique_ptr acl_table_; + unique_ptr acl_table_type_; + NeighOrch* neighorch_; +}; + + +class DpuRegistry +{ +public: + struct DpuData + { + dpu_type_t type; + swss::IpAddress pa_v4; + swss::IpAddress npu_v4; + }; + + struct DpuRequest : public Request + { + DpuRequest() : Request(dpu_table_desc, '|' ) { } + }; + + void populate(Table*); + std::vector getIds(); + bool getType(uint64_t id, dpu_type_t& val); + bool getPaV4(uint64_t id, swss::IpAddress& val); + bool getNpuV4(uint64_t id, swss::IpAddress& val); + +private: + void processDpuTable(const KeyOpFieldsValuesTuple& ); + + std::vector dpus_ids_; + DpuRequest dpu_request_; + map dpus_; +}; + + +class EniNH +{ +public: + static std::unique_ptr createNextHop(dpu_type_t, const swss::IpAddress&); + + EniNH(const swss::IpAddress& ip) : endpoint_(ip) {} + void setStatus(endpoint_status_t status) {status_ = status;} + void setType(dpu_type_t type) {type_ = type;} + endpoint_status_t getStatus() {return status_;} + dpu_type_t getType() {return type_;} + swss::IpAddress getEp() {return endpoint_;} + + virtual void resolve(EniInfo& eni) = 0; + virtual void destroy(EniInfo& eni) = 0; + virtual string getRedirectVal() = 0; + +protected: + endpoint_status_t status_; + dpu_type_t type_; + swss::IpAddress endpoint_; +}; + + +class LocalEniNH : public EniNH +{ +public: + LocalEniNH(const swss::IpAddress& ip) : EniNH(ip) + { + setStatus(endpoint_status_t::UNRESOLVED); + setType(dpu_type_t::LOCAL); + } + void resolve(EniInfo& eni) override; + void destroy(EniInfo& eni) {} + string getRedirectVal() override; +}; + + +class RemoteEniNH : public EniNH +{ +public: + RemoteEniNH(const swss::IpAddress& ip) : EniNH(ip) + { + /* No BFD monitoring for Remote NH yet */ + setStatus(endpoint_status_t::UNRESOLVED); + setType(dpu_type_t::CLUSTER); + } + void resolve(EniInfo& eni) override; + void destroy(EniInfo& eni) override; + string getRedirectVal() override; + +private: + string tunnel_name_; +}; + + +class EniAclRule +{ +public: + static const int BASE_PRIORITY; + static const std::vector RULE_NAMES; + + EniAclRule(rule_type_t type, EniInfo& eni) : + type_(type), + state_(rule_state_t::PENDING) { setKey(eni); } + + void destroy(EniInfo&); + void fire(EniInfo&); + + update_type_t processUpdate(EniInfo& eni); + std::string getKey() {return name_; } + string getMacMatchDirection(EniInfo& eni); + void setState(rule_state_t state); + +private: + void setKey(EniInfo&); + std::unique_ptr nh_ = nullptr; + std::string name_; + rule_type_t type_; + rule_state_t state_; +}; + + +class EniInfo +{ +public: + friend class DashEniFwdOrch; /* Only orch is expected to call create/update/fire */ + + EniInfo(const std::string&, const std::string&, const shared_ptr&); + EniInfo(const EniInfo&) = delete; + EniInfo& operator=(const EniInfo&) = delete; + EniInfo(EniInfo&&) = delete; + EniInfo& operator=(EniInfo&&) = delete; + + string toKey() const; + std::shared_ptr& getCtx() {return ctx;} + bool findLocalEp(uint64_t&) const; + swss::MacAddress getMac() const { return mac_; } // Can only be set during object creation + std::vector getEpList() { return ep_list_; } + uint64_t getPrimaryId() const { return primary_id_; } + uint64_t getOutVni() const { return outbound_vni_; } + std::string getOutMacLookup() const { return outbound_mac_lookup_; } + std::string getVnet() const { return vnet_name_; } + +protected: + void formatMac(); + void fireRule(rule_type_t); + void fireAllRules(); + bool create(const Request&); + bool destroy(const Request&); + bool update(const Request& ); + bool update(const NeighborUpdate&); + + std::shared_ptr ctx; + std::map rule_container_; + std::vector ep_list_; + uint64_t primary_id_; + uint64_t outbound_vni_; + std::string outbound_mac_lookup_; + std::string vnet_name_; + swss::MacAddress mac_; + std::string mac_key_; // Formatted MAC key +}; + + +/* + Collection of API's used across DashEniFwdOrch +*/ +class EniFwdCtxBase +{ +public: + EniFwdCtxBase(DBConnector* cfgDb, DBConnector* applDb); + void populateDpuRegistry(); + std::vector getBindPoints(); + std::string getNbrAlias(const swss::IpAddress& ip); + bool handleTunnelNH(const std::string&, swss::IpAddress, bool); + swss::IpPrefix getVip(); + + virtual void initialize() = 0; + /* API's that call other orchagents */ + virtual std::map& getAllPorts() = 0; + virtual bool isNeighborResolved(const NextHopKey&) = 0; + virtual void resolveNeighbor(const NeighborEntry &) = 0; + virtual string getRouterIntfsAlias(const IpAddress &, const string & = "") = 0; + virtual bool findVnetVni(const std::string&, uint64_t& ) = 0; + virtual bool findVnetTunnel(const std::string&, string&) = 0; + virtual sai_object_id_t createNextHopTunnel(string, IpAddress) = 0; + virtual bool removeNextHopTunnel(string, IpAddress) = 0; + + DpuRegistry dpu_info; + unique_ptr rule_table; +protected: + std::set findInternalPorts(); + + /* RemoteNh Key -> {ref_count, sai oid} */ + std::map> remote_nh_map_; + /* Mapping between DPU Nbr and Alias */ + std::map nh_alias_map_; + + unique_ptr port_tbl_; + unique_ptr vip_tbl_; + unique_ptr dpu_tbl_; + + /* Only one vip is expected per T1 */ + swss::IpPrefix vip; + bool vip_inferred_; +}; + + +/* + Wrapper on API's that are throwable +*/ +template +R safetyWrapper(C* ptr, R (C::*func)(Args...), R defaultValue, Args&&... args) +{ + SWSS_LOG_ENTER(); + try + { + return (ptr->*func)(std::forward(args)...); + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Exception thrown.. %s", e.what()); + return defaultValue; + } +} + + +/* + Implements API's to access other orchagents +*/ +class EniFwdCtx : public EniFwdCtxBase +{ +public: + using EniFwdCtxBase::EniFwdCtxBase; + + /* Setup pointers to other orchagents */ + void initialize() override; + bool isNeighborResolved(const NextHopKey&) override; + void resolveNeighbor(const NeighborEntry&) override; + std::string getRouterIntfsAlias(const IpAddress &, const string & = "") override; + bool findVnetVni(const std::string&, uint64_t&) override; + bool findVnetTunnel(const std::string&, string&) override; + std::map& getAllPorts() override; + virtual sai_object_id_t createNextHopTunnel(string, IpAddress) override; + virtual bool removeNextHopTunnel(string, IpAddress) override; + +private: + PortsOrch* portsorch_; + NeighOrch* neighorch_; + IntfsOrch* intfsorch_; + VNetOrch* vnetorch_; + VxlanTunnelOrch* vxlanorch_; +}; diff --git a/orchagent/main.cpp b/orchagent/main.cpp index d50508f6ce..e3f6a35481 100644 --- a/orchagent/main.cpp +++ b/orchagent/main.cpp @@ -63,6 +63,7 @@ extern bool gIsNatSupported; #define RESPONSE_PUBLISHER_RECORD_ENABLE (0x1 << 2) string gMySwitchType = ""; +string gMySwitchSubType = ""; int32_t gVoqMySwitchId = -1; int32_t gVoqMaxCores = 0; uint32_t gCfgSystemPorts = 0; @@ -160,7 +161,7 @@ void init_gearbox_phys(DBConnector *applDb) delete tmpGearboxTable; } -void getCfgSwitchType(DBConnector *cfgDb, string &switch_type) +void getCfgSwitchType(DBConnector *cfgDb, string &switch_type, string &switch_sub_type) { Table cfgDeviceMetaDataTable(cfgDb, CFG_DEVICE_METADATA_TABLE_NAME); @@ -184,6 +185,16 @@ void getCfgSwitchType(DBConnector *cfgDb, string &switch_type) //If configured switch type is none of the supported, assume regular switch switch_type = "switch"; } + + try + { + cfgDeviceMetaDataTable.hget("localhost", "subtype", switch_sub_type); + } + catch(const std::system_error& e) + { + SWSS_LOG_ERROR("System error in parsing switch subtype: %s", e.what()); + } + } bool getSystemPortConfigList(DBConnector *cfgDb, DBConnector *appDb, vector &sysportcfglist) @@ -503,7 +514,7 @@ int main(int argc, char **argv) } // Get switch_type - getCfgSwitchType(&config_db, gMySwitchType); + getCfgSwitchType(&config_db, gMySwitchType, gMySwitchSubType); sai_attribute_t attr; vector attrs; diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 13ef89c487..6872afaa8b 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -29,6 +29,7 @@ using namespace swss; extern sai_switch_api_t* sai_switch_api; extern sai_object_id_t gSwitchId; extern string gMySwitchType; +extern string gMySwitchSubType; extern void syncd_apply_view(); /* @@ -207,7 +208,9 @@ bool OrchDaemon::init() gDirectory.set(chassis_frontend_orch); gIntfsOrch = new IntfsOrch(m_applDb, APP_INTF_TABLE_NAME, vrf_orch, m_chassisAppDb); + gDirectory.set(gIntfsOrch); gNeighOrch = new NeighOrch(m_applDb, APP_NEIGH_TABLE_NAME, gIntfsOrch, gFdbOrch, gPortsOrch, m_chassisAppDb); + gDirectory.set(gNeighOrch); const int fgnhgorch_pri = 15; @@ -548,6 +551,13 @@ bool OrchDaemon::init() m_orchList.push_back(gFabricPortsOrch); } + if (gMySwitchSubType == "SmartSwitch") + { + DashEniFwdOrch *dash_eni_fwd_orch = new DashEniFwdOrch(m_configDb, m_applDb, APP_DASH_ENI_FORWARD_TABLE, gNeighOrch); + gDirectory.set(dash_eni_fwd_orch); + m_orchList.push_back(dash_eni_fwd_orch); + } + vector flex_counter_tables = { CFG_FLEX_COUNTER_TABLE_NAME }; diff --git a/orchagent/orchdaemon.h b/orchagent/orchdaemon.h index 6a1c0b999a..9fc13a7fec 100644 --- a/orchagent/orchdaemon.h +++ b/orchagent/orchdaemon.h @@ -48,6 +48,7 @@ #include "nvgreorch.h" #include "twamporch.h" #include "stporch.h" +#include "dash/dashenifwdorch.h" #include "dash/dashaclorch.h" #include "dash/dashorch.h" #include "dash/dashrouteorch.h" diff --git a/orchagent/request_parser.cpp b/orchagent/request_parser.cpp index 70b4351119..8a40e135bd 100644 --- a/orchagent/request_parser.cpp +++ b/orchagent/request_parser.cpp @@ -71,10 +71,10 @@ void Request::parseKey(const KeyOpFieldsValuesTuple& request) key_items.push_back(full_key_.substr(key_item_start, full_key_.length())); /* - * Attempt to parse an IPv6 address only if the following conditions are met: + * Attempt to parse an IPv6/MAC address only if the following conditions are met: * - The key separator is ":" * - The above logic will already correctly parse IPv6 addresses using other key separators - * - Special consideration is only needed for ":" key separators since IPv6 addresses also use ":" as the field separator + * - Special consideration is only needed for ":" key separators since IPv6/MAC addresses also use ":" as the field separator * - The number of parsed key items exceeds the number of expected key items * - If we have too many key items and the last key item is supposed to be an IP or prefix, there is a chance that it was an * IPv6 address that got segmented during parsing @@ -85,7 +85,8 @@ void Request::parseKey(const KeyOpFieldsValuesTuple& request) */ if (key_separator_ == ':' and key_items.size() > number_of_key_items_ and - (request_description_.key_item_types.back() == REQ_T_IP or request_description_.key_item_types.back() == REQ_T_IP_PREFIX)) + (request_description_.key_item_types.back() == REQ_T_IP or request_description_.key_item_types.back() == REQ_T_IP_PREFIX + or request_description_.key_item_types.back() == REQ_T_MAC_ADDRESS)) { // Remove key_items so that key_items.size() is correct, then assemble the removed items into an IPv6 address std::vector ip_addr_groups(--key_items.begin() + number_of_key_items_, key_items.end()); diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 1abe482c4c..c485e091e2 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -62,6 +62,7 @@ tests_SOURCES = aclorch_ut.cpp \ switchorch_ut.cpp \ warmrestarthelper_ut.cpp \ neighorch_ut.cpp \ + dashenifwdorch_ut.cpp \ dashorch_ut.cpp \ twamporch_ut.cpp \ stporch_ut.cpp \ @@ -131,6 +132,8 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/cfgmgr/portmgr.cpp \ $(top_srcdir)/cfgmgr/sflowmgr.cpp \ $(top_srcdir)/orchagent/zmqorch.cpp \ + $(top_srcdir)/orchagent/dash/dashenifwdorch.cpp \ + $(top_srcdir)/orchagent/dash/dashenifwdinfo.cpp \ $(top_srcdir)/orchagent/dash/dashaclorch.cpp \ $(top_srcdir)/orchagent/dash/dashorch.cpp \ $(top_srcdir)/orchagent/dash/dashaclgroupmgr.cpp \ diff --git a/tests/mock_tests/aclorch_ut.cpp b/tests/mock_tests/aclorch_ut.cpp index 575f56e5d5..556a12f240 100644 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -962,6 +962,30 @@ namespace aclorch_test return false; } } + else if (attr_name == MATCH_INNER_SRC_MAC || attr_name == MATCH_INNER_DST_MAC) + { + + auto it_field = rule_matches.find(attr_name == MATCH_INNER_SRC_MAC ? SAI_ACL_ENTRY_ATTR_FIELD_INNER_SRC_MAC : + SAI_ACL_ENTRY_ATTR_FIELD_INNER_DST_MAC); + if (it_field == rule_matches.end()) + { + return false; + } + + if (attr_value != sai_serialize_mac(it_field->second.getSaiAttr().value.aclfield.data.mac)) + { + std::cerr << "MAC didn't match, Expected:" << attr_value << "\n" \ + << "Recieved: " << sai_serialize_mac(it_field->second.getSaiAttr().value.aclfield.data.mac) << "\n" ; + return false; + } + + if ("FF:FF:FF:FF:FF:FF" != sai_serialize_mac(it_field->second.getSaiAttr().value.aclfield.mask.mac)) + { + std::cerr << "MAC Mask didn't match, Expected: FF:FF:FF:FF:FF:FF\n" \ + << "Recieved: " << sai_serialize_mac(it_field->second.getSaiAttr().value.aclfield.data.mac) << "\n" ; + return false; + } + } else { // unknown attr_name @@ -985,13 +1009,18 @@ namespace aclorch_test return false; } } - else if (attr_name == MATCH_SRC_IP || attr_name == MATCH_DST_IP || attr_name == MATCH_SRC_IPV6) + else if (attr_name == MATCH_SRC_IP || attr_name == MATCH_DST_IP || attr_name == MATCH_SRC_IPV6 + || attr_name == MATCH_INNER_DST_MAC || attr_name == MATCH_INNER_SRC_MAC) { if (!validateAclRuleMatch(acl_rule, attr_name, attr_value)) { return false; } } + else if (attr_name == RULE_PRIORITY) + { + continue; + } else { // unknown attr_name @@ -1955,4 +1984,71 @@ namespace aclorch_test // Restore sai_switch_api. sai_switch_api = old_sai_switch_api; } + + TEST_F(AclOrchTest, Match_Inner_Mac) + { + string aclTableTypeName = "MAC_MATCH_TABLE_TYPE"; + string aclTableName = "MAC_MATCH_TABLE"; + string aclRuleName = "MAC_MATCH_RULE0"; + + auto orch = createAclOrch(); + + auto matches = string(MATCH_INNER_DST_MAC) + comma + string(MATCH_INNER_SRC_MAC); + orch->doAclTableTypeTask( + deque( + { + { + aclTableTypeName, + SET_COMMAND, + { + { ACL_TABLE_TYPE_MATCHES, matches}, + { ACL_TABLE_TYPE_ACTIONS, ACTION_PACKET_ACTION } + } + } + }) + ); + + orch->doAclTableTask( + deque( + { + { + aclTableName, + SET_COMMAND, + { + { ACL_TABLE_TYPE, aclTableTypeName }, + { ACL_TABLE_STAGE, STAGE_INGRESS } + } + } + }) + ); + + ASSERT_TRUE(orch->getAclTable(aclTableName)); + + auto tableOid = orch->getTableById(aclTableName); + ASSERT_NE(tableOid, SAI_NULL_OBJECT_ID); + const auto &aclTables = orch->getAclTables(); + auto it_table = aclTables.find(tableOid); + ASSERT_NE(it_table, aclTables.end()); + + const auto &aclTableObject = it_table->second; + + auto kvfAclRule = deque({ + { + aclTableName + "|" + aclRuleName, + SET_COMMAND, + { + { RULE_PRIORITY, "9999" }, + { MATCH_INNER_DST_MAC, "FF:EE:DD:CC:BB:AA" }, + { MATCH_INNER_SRC_MAC, "11:22:33:44:55:66" }, + { ACTION_PACKET_ACTION, PACKET_ACTION_DROP } + } + } + }); + orch->doAclRuleTask(kvfAclRule); + + auto it_rule = aclTableObject.rules.find(aclRuleName); + ASSERT_NE(it_rule, aclTableObject.rules.end()); + ASSERT_TRUE(validateAclRuleByConfOp(*it_rule->second, kfvFieldsValues(kvfAclRule.front()))); +} + } // namespace nsAclOrchTest diff --git a/tests/mock_tests/dashenifwdorch_ut.cpp b/tests/mock_tests/dashenifwdorch_ut.cpp new file mode 100644 index 0000000000..f3a157f87d --- /dev/null +++ b/tests/mock_tests/dashenifwdorch_ut.cpp @@ -0,0 +1,664 @@ +#include "mock_orch_test.h" +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "mock_table.h" +#define protected public +#define private public +#include "dash/dashenifwdorch.h" +#undef public +#undef protected + +using namespace ::testing; + +namespace dashenifwdorch_ut +{ + /* Mock API Calls to other orchagents */ + class MockEniFwdCtx : public EniFwdCtxBase { + public: + using EniFwdCtxBase::EniFwdCtxBase; + + void initialize() override {} + MOCK_METHOD(std::string, getRouterIntfsAlias, (const swss::IpAddress&, const string& vrf), (override)); + MOCK_METHOD(bool, isNeighborResolved, (const NextHopKey&), (override)); + MOCK_METHOD(void, resolveNeighbor, (const NextHopKey&), (override)); + MOCK_METHOD(bool, findVnetVni, (const std::string&, uint64_t&), (override)); + MOCK_METHOD(bool, findVnetTunnel, (const std::string&, std::string&), (override)); + MOCK_METHOD(sai_object_id_t, createNextHopTunnel, (std::string, IpAddress), (override)); + MOCK_METHOD(bool, removeNextHopTunnel, (std::string, IpAddress), (override)); + MOCK_METHOD((std::map&), getAllPorts, (), (override)); + }; + + class DashEniFwdOrchTest : public Test + { + public: + unique_ptr cfgDb; + unique_ptr applDb; + unique_ptr chassisApplDb; + unique_ptr
dpuTable; + unique_ptr
eniFwdTable; + unique_ptr
aclRuleTable; + unique_ptr eniOrch; + shared_ptr ctx; + + /* Test values */ + string alias_dpu = "Vlan1000"; + string test_vip = "10.2.0.1/32"; + string vnet_name = "Vnet_1000"; + string tunnel_name = "mock_tunnel"; + string test_mac = "aa:bb:cc:dd:ee:ff"; + string test_mac2 = "ff:ee:dd:cc:bb:aa"; + string test_mac_key = "AABBCCDDEEFF"; + string test_mac2_key = "FFEEDDCCBBAA"; + string local_pav4 = "10.0.0.1"; + string remote_pav4 = "10.0.0.2"; + string remote_2_pav4 = "10.0.0.3"; + string local_npuv4 = "20.0.0.1"; + string remote_npuv4 = "20.0.0.2"; + string remote_2_npuv4 = "20.0.0.3"; + + uint64_t test_vni = 1234; + uint64_t test_vni2 = 5678; + int BASE_PRIORITY = 9996; + + void populateDpuTable() + { + /* Add 1 local and 1 cluster DPU */ + dpuTable->set("1", + { + { DPU_TYPE, "local" }, + { DPU_PA_V4, local_pav4 }, + { DPU_NPU_V4, local_npuv4 }, + }, SET_COMMAND); + + dpuTable->set("2", + { + { DPU_TYPE, "cluster" }, + { DPU_PA_V4, remote_pav4 }, + { DPU_NPU_V4, remote_npuv4 }, + }, SET_COMMAND); + + dpuTable->set("3", + { + { DPU_TYPE, "cluster" }, + { DPU_PA_V4, remote_2_pav4 }, + { DPU_NPU_V4, remote_2_npuv4 }, + }, SET_COMMAND); + } + + void populateVip() + { + Table vipTable(cfgDb.get(), CFG_VIP_TABLE_TMP); + vipTable.set(test_vip, {{}}); + } + + void doDashEniFwdTableTask(DBConnector* applDb, const deque &entries) + { + auto consumer = unique_ptr(new Consumer( + new swss::ConsumerStateTable(applDb, APP_DASH_ENI_FORWARD_TABLE, 1, 1), + eniOrch.get(), APP_DASH_ENI_FORWARD_TABLE)); + + consumer->addToSync(entries); + eniOrch->doTask(*consumer); + } + + void checkKFV(Table* m_table, const std::string& key, const std::vector& expectedValues) { + std::string val; + for (const auto& fv : expectedValues) { + const std::string& field = fvField(fv); + const std::string& expectedVal = fvValue(fv); + EXPECT_TRUE(m_table->hget(key, field, val)) + << "Failed to retrieve field " << field << " from key " << key; + EXPECT_EQ(val, expectedVal) + << "Mismatch for field " << field << " for key " << key + << ": expected " << expectedVal << ", got " << val; + } + } + + void checkRuleUninstalled(string key) + { + std::string val; + EXPECT_FALSE(aclRuleTable->hget(key, MATCH_DST_IP, val)) + << key << ": Still Exist"; + } + + void SetUp() override { + testing_db::reset(); + cfgDb = make_unique("CONFIG_DB", 0); + applDb = make_unique("APPL_DB", 0); + chassisApplDb = make_unique("CHASSIS_APP_DB", 0); + /* Initialize tables */ + dpuTable = make_unique
(cfgDb.get(), CFG_DPU_TABLE); + eniFwdTable = make_unique
(applDb.get(), APP_DASH_ENI_FORWARD_TABLE); + aclRuleTable = make_unique
(applDb.get(), APP_ACL_RULE_TABLE_NAME); + /* Populate DPU Configuration */ + populateDpuTable(); + populateVip(); + eniOrch = make_unique(cfgDb.get(), applDb.get(), APP_DASH_ENI_FORWARD_TABLE, nullptr); + + /* Clear the default context and Patch with the Mock */ + ctx = make_shared(cfgDb.get(), applDb.get()); + eniOrch->ctx.reset(); + eniOrch->ctx = ctx; + eniOrch->ctx->populateDpuRegistry(); + eniOrch->ctx_initialized_ = true; + } + }; + + /* + Test getting the PA, NPU address of a DPU and dpuType + */ + TEST_F(DashEniFwdOrchTest, TestDpuRegistry) + { + dpu_type_t type = dpu_type_t::EXTERNAL; + swss::IpAddress pa_v4; + swss::IpAddress npu_v4; + + EniFwdCtx ctx(cfgDb.get(), applDb.get()); + ctx.populateDpuRegistry(); + + EXPECT_TRUE(ctx.dpu_info.getType(1, type)); + EXPECT_EQ(type, dpu_type_t::LOCAL); + EXPECT_TRUE(ctx.dpu_info.getType(2, type)); + EXPECT_EQ(type, dpu_type_t::CLUSTER); + + EXPECT_TRUE(ctx.dpu_info.getPaV4(1, pa_v4)); + EXPECT_EQ(pa_v4.to_string(), local_pav4); + EXPECT_TRUE(ctx.dpu_info.getPaV4(2, pa_v4)); + EXPECT_EQ(pa_v4.to_string(), remote_pav4); + + EXPECT_TRUE(ctx.dpu_info.getNpuV4(1, npu_v4)); + EXPECT_EQ(npu_v4.to_string(), local_npuv4); + EXPECT_TRUE(ctx.dpu_info.getNpuV4(2, npu_v4)); + EXPECT_EQ(npu_v4.to_string(), remote_npuv4); + + vector ids = {1, 2, 3}; + EXPECT_EQ(ctx.dpu_info.getIds(), ids); + } + + /* + VNI is provided by HaMgrd, Resolve Neighbor + */ + TEST_F(DashEniFwdOrchTest, LocalNeighbor) + { + auto nh_ip = swss::IpAddress(local_pav4); + NextHopKey nh = {nh_ip, alias_dpu}; + /* Mock calls to intfsOrch and neighOrch + If neighbor is already resolved, resolveNeighbor is not called */ + EXPECT_CALL(*ctx, getRouterIntfsAlias(nh_ip, _)).WillOnce(Return(alias_dpu)); /* Once per local endpoint */ + EXPECT_CALL(*ctx, isNeighborResolved(nh)).Times(4).WillRepeatedly(Return(true)); + EXPECT_CALL(*ctx, resolveNeighbor(nh)).Times(0); + + doDashEniFwdTableTask(applDb.get(), + deque( + { + { + vnet_name + ":" + test_mac, + SET_COMMAND, + { + { ENI_FWD_VDPU_IDS, "1,2" }, + { ENI_FWD_PRIMARY, "1" }, // Local endpoint is the primary + { ENI_FWD_OUT_VNI, to_string(test_vni) } + } + } + } + ) + ); + + /* Check ACL Rules */ + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac_key+ "_IN", { + { ACTION_REDIRECT_ACTION , local_pav4 }, { MATCH_DST_IP, test_vip }, + { RULE_PRIORITY, to_string(BASE_PRIORITY + INBOUND) }, + { MATCH_INNER_DST_MAC, test_mac } + }); + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac_key+ "_OUT", { + { ACTION_REDIRECT_ACTION, local_pav4 }, { MATCH_DST_IP, test_vip }, + { RULE_PRIORITY, to_string(BASE_PRIORITY + OUTBOUND) }, + { MATCH_INNER_SRC_MAC, test_mac }, { MATCH_TUNNEL_VNI, to_string(test_vni) } + }); + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac_key+ "_IN_TERM", { + { ACTION_REDIRECT_ACTION, local_pav4 }, { MATCH_DST_IP, test_vip }, + { RULE_PRIORITY, to_string(BASE_PRIORITY + INBOUND_TERM) }, + { MATCH_INNER_DST_MAC, test_mac }, + { MATCH_TUNNEL_TERM, "true"} + }); + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac_key+ "_OUT_TERM", { + { ACTION_REDIRECT_ACTION, local_pav4 }, { MATCH_DST_IP, test_vip }, + { RULE_PRIORITY, to_string(BASE_PRIORITY + OUTBOUND_TERM) }, + { MATCH_INNER_SRC_MAC, test_mac }, { MATCH_TUNNEL_VNI, to_string(test_vni) }, + { MATCH_TUNNEL_TERM, "true"} + }); + } + + /* + Infer VNI by reading from the VnetOrch, resolved Neighbor + */ + TEST_F(DashEniFwdOrchTest, LocalNeighbor_NoVNI) + { + auto nh_ip = swss::IpAddress(local_pav4); + NextHopKey nh = {nh_ip, alias_dpu}; + + EXPECT_CALL(*ctx, findVnetVni(vnet_name, _)).Times(1) // Called once per ENI + .WillRepeatedly(DoAll( + SetArgReferee<1>(test_vni2), + Return(true) + )); + + EXPECT_CALL(*ctx, getRouterIntfsAlias(nh_ip, _)).WillOnce(Return(alias_dpu)); + EXPECT_CALL(*ctx, isNeighborResolved(nh)).Times(4).WillRepeatedly(Return(true)); + + doDashEniFwdTableTask(applDb.get(), + deque( + { + { + vnet_name + ":" + test_mac2, + SET_COMMAND, + { + { ENI_FWD_VDPU_IDS, "1,2" }, + { ENI_FWD_PRIMARY, "1" }, // Local endpoint is the primary + // No Explicit VNI from the DB, should be inferred from the VNetOrch + } + } + } + ) + ); + + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac2_key + "_OUT", { + { ACTION_REDIRECT_ACTION, local_pav4 }, { MATCH_DST_IP, test_vip }, + { RULE_PRIORITY, to_string(BASE_PRIORITY + OUTBOUND) }, + { MATCH_INNER_SRC_MAC, test_mac2 }, { MATCH_TUNNEL_VNI, to_string(test_vni2) } + }); + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac2_key + "_OUT_TERM", { + { ACTION_REDIRECT_ACTION, local_pav4 }, { MATCH_DST_IP, test_vip }, + { RULE_PRIORITY, to_string(BASE_PRIORITY + OUTBOUND_TERM) }, + { MATCH_INNER_SRC_MAC, test_mac2 }, { MATCH_TUNNEL_VNI, to_string(test_vni2) }, + { MATCH_TUNNEL_TERM, "true"} + }); + } + + /* + Verify MAC direction + */ + TEST_F(DashEniFwdOrchTest, LocalNeighbor_MacDirection) + { + auto nh_ip = swss::IpAddress(local_pav4); + NextHopKey nh = {nh_ip, alias_dpu}; + + EXPECT_CALL(*ctx, getRouterIntfsAlias(nh_ip, _)).WillOnce(Return(alias_dpu)); + EXPECT_CALL(*ctx, isNeighborResolved(nh)).Times(4).WillRepeatedly(Return(true)); + + doDashEniFwdTableTask(applDb.get(), + deque( + { + { + vnet_name + ":" + test_mac2, + SET_COMMAND, + { + { ENI_FWD_VDPU_IDS, "1,2" }, + { ENI_FWD_PRIMARY, "1" }, // Local endpoint is the primary + { ENI_FWD_OUT_VNI, to_string(test_vni2) }, + { ENI_FWD_OUT_MAC_LOOKUP, "dst" } + } + } + } + ) + ); + + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac2_key + "_OUT", { + { MATCH_INNER_DST_MAC, test_mac2 }, + }); + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac2_key + "_OUT_TERM", { + { MATCH_INNER_DST_MAC, test_mac2 }, { MATCH_TUNNEL_TERM, "true"} + }); + } + + + /* + VNI is provided by HaMgrd, UnResolved Neighbor + */ + TEST_F(DashEniFwdOrchTest, LocalNeighbor_Unresolved) + { + auto nh_ip = swss::IpAddress(local_pav4); + NextHopKey nh = {nh_ip, alias_dpu}; + /* 1 for initLocalEndpoints */ + EXPECT_CALL(*ctx, getRouterIntfsAlias(nh_ip, _)).WillOnce(Return(alias_dpu)); + + /* Neighbor is not resolved, 1 per rule + 1 for initLocalEndpoints */ + EXPECT_CALL(*ctx, isNeighborResolved(nh)).Times(9).WillRepeatedly(Return(false)); + /* resolveNeighbor is called because the neigh is not resolved */ + EXPECT_CALL(*ctx, resolveNeighbor(nh)).Times(9); /* 1 per rule + 1 for initLocalEndpoints */ + + eniOrch->initLocalEndpoints(); + + /* Populate 2 ENI's */ + doDashEniFwdTableTask(applDb.get(), + deque( + { + { + vnet_name + ":" + test_mac, + SET_COMMAND, + { + { ENI_FWD_VDPU_IDS, "1,2" }, + { ENI_FWD_PRIMARY, "1" }, // Local endpoint is the primary + { ENI_FWD_OUT_VNI, to_string(test_vni) } + } + }, + { + vnet_name + ":" + test_mac2, + SET_COMMAND, + { + { ENI_FWD_VDPU_IDS, "1,2" }, + { ENI_FWD_PRIMARY, "1" }, // Local endpoint is the primary + { ENI_FWD_OUT_VNI, to_string(test_vni2) } + } + } + } + ) + ); + + checkRuleUninstalled("ENI:" + vnet_name + "_" + test_mac_key+ "_IN"); + checkRuleUninstalled("ENI:" + vnet_name + "_" + test_mac_key+ "_IN_TERM"); + checkRuleUninstalled("ENI:" + vnet_name + "_" + test_mac2_key + "_OUT"); + checkRuleUninstalled("ENI:" + vnet_name + "_" + test_mac2_key + "_OUT_TERM"); + + /* Neighbor is resolved, Trigger a nexthop update (1 for Neigh Update) * 4 for Types of Rules */ + EXPECT_CALL(*ctx, isNeighborResolved(nh)).Times(8).WillRepeatedly(Return(true)); + + NeighborEntry temp_entry = nh; + NeighborUpdate update = { temp_entry, MacAddress(), true }; + eniOrch->update(SUBJECT_TYPE_NEIGH_CHANGE, static_cast(&update)); + + /* Check ACL Rules */ + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac_key+ "_IN", { + { ACTION_REDIRECT_ACTION, local_pav4 } + }); + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac_key+ "_IN_TERM", { + { ACTION_REDIRECT_ACTION, local_pav4 }, { MATCH_TUNNEL_TERM, "true"} + }); + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac2_key + "_OUT", { + { ACTION_REDIRECT_ACTION, local_pav4 }, + }); + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac2_key + "_OUT_TERM", { + { ACTION_REDIRECT_ACTION, local_pav4 }, { MATCH_TUNNEL_TERM, "true"}, + { MATCH_INNER_SRC_MAC, test_mac2 }, + }); + } + + /* + Remote Endpoint + */ + TEST_F(DashEniFwdOrchTest, RemoteNeighbor) + { + IpAddress remote_npuv4_ip = IpAddress(remote_npuv4); + EXPECT_CALL(*ctx, getRouterIntfsAlias(_, _)).WillOnce(Return(alias_dpu)); + /* calls to neighOrch expected for tunn termination entries */ + EXPECT_CALL(*ctx, isNeighborResolved(_)).Times(4).WillRepeatedly(Return(true)); + + EXPECT_CALL(*ctx, findVnetTunnel(vnet_name, _)).Times(4) // Once per non-tunnel term rules + .WillRepeatedly(DoAll( + SetArgReferee<1>(tunnel_name), + Return(true) + )); + + EXPECT_CALL(*ctx, createNextHopTunnel(tunnel_name, remote_npuv4_ip)).Times(1) + .WillRepeatedly(Return(0x400000000064d)); // Only once since same NH object will be re-used + + doDashEniFwdTableTask(applDb.get(), + deque( + { + { + vnet_name + ":" + test_mac, + SET_COMMAND, + { + { ENI_FWD_VDPU_IDS, "1,2" }, + { ENI_FWD_PRIMARY, "2" }, // Remote endpoint is the primary + { ENI_FWD_OUT_VNI, to_string(test_vni) } + } + }, + { + vnet_name + ":" + test_mac2, + SET_COMMAND, + { + { ENI_FWD_VDPU_IDS, "1,2" }, + { ENI_FWD_PRIMARY, "2" }, // Remote endpoint is the primary + { ENI_FWD_OUT_VNI, to_string(test_vni2) } + } + } + } + ) + ); + + /* Check ACL Rules */ + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac_key+ "_IN", { + { ACTION_REDIRECT_ACTION, remote_npuv4 + "@" + tunnel_name } + }); + /* Tunnel termiantion rule should be local endpoint */ + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac_key+ "_OUT_TERM", { + { ACTION_REDIRECT_ACTION, local_pav4 } + }); + + /* Rules for second ENI */ + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac2_key + "_OUT", { + { ACTION_REDIRECT_ACTION, remote_npuv4 + "@" + tunnel_name } + }); + + /* Check Ref count */ + ASSERT_TRUE(ctx->remote_nh_map_.find(remote_npuv4 + "@" + tunnel_name) != ctx->remote_nh_map_.end()); + EXPECT_EQ(ctx->remote_nh_map_.find(remote_npuv4 + "@" + tunnel_name)->second.first, 4); /* 4 rules using this NH */ + EXPECT_EQ(ctx->remote_nh_map_.find(remote_npuv4 + "@" + tunnel_name)->second.second, 0x400000000064d); + + EXPECT_CALL(*ctx, removeNextHopTunnel(tunnel_name, remote_npuv4_ip)).WillOnce(Return(true)); + + /* Delete all ENI's */ + doDashEniFwdTableTask(applDb.get(), + deque( + { + { + vnet_name + ":" + test_mac2, + DEL_COMMAND, + { } + }, + { + vnet_name + ":" + test_mac, + DEL_COMMAND, + { } + } + } + ) + ); + checkRuleUninstalled("ENI:" + vnet_name + "_" + test_mac2_key + "_IN"); + checkRuleUninstalled("ENI:" + vnet_name + "_" + test_mac2_key + "_IN_TERM"); + checkRuleUninstalled("ENI:" + vnet_name + "_" + test_mac2_key + "_OUT"); + checkRuleUninstalled("ENI:" + vnet_name + "_" + test_mac2_key + "_OUT_TERM"); + checkRuleUninstalled("ENI:" + vnet_name + "_" + test_mac_key+ "_IN"); + checkRuleUninstalled("ENI:" + vnet_name + "_" + test_mac_key+ "_IN_TERM"); + checkRuleUninstalled("ENI:" + vnet_name + "_" + test_mac_key+ "_OUT"); + checkRuleUninstalled("ENI:" + vnet_name + "_" + test_mac_key+ "_OUT_TERM"); + /* Check the tunnel is removed */ + ASSERT_TRUE(ctx->remote_nh_map_.find(remote_npuv4 + "@" + tunnel_name) == ctx->remote_nh_map_.end()); + } + + /* + Remote Endpoint with an update to switch to Local Endpoint + */ + TEST_F(DashEniFwdOrchTest, RemoteNeighbor_SwitchToLocal) + { + IpAddress remote_npuv4_ip = IpAddress(remote_npuv4); + EXPECT_CALL(*ctx, getRouterIntfsAlias(_, _)).WillOnce(Return(alias_dpu)); + /* 2 calls made for tunnel termination rules */ + EXPECT_CALL(*ctx, isNeighborResolved(_)).Times(2).WillRepeatedly(Return(true)); + EXPECT_CALL(*ctx, findVnetTunnel(vnet_name, _)).Times(2) // Once per non-tunnel term rules + .WillRepeatedly(DoAll( + SetArgReferee<1>(tunnel_name), + Return(true) + )); + EXPECT_CALL(*ctx, createNextHopTunnel(tunnel_name, remote_npuv4_ip)).Times(1) + .WillRepeatedly(Return(0x400000000064d)); // Only once since same NH object will be re-used + + doDashEniFwdTableTask(applDb.get(), + deque( + { + { + vnet_name + ":" + test_mac, + SET_COMMAND, + { + { ENI_FWD_VDPU_IDS, "1,2" }, + { ENI_FWD_PRIMARY, "2" }, // Remote endpoint is the primary + { ENI_FWD_OUT_VNI, to_string(test_vni) } + } + } + } + ) + ); + + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac_key+ "_IN", { + { ACTION_REDIRECT_ACTION, remote_npuv4 + "@" + tunnel_name } + }); + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac_key+ "_OUT", { + { ACTION_REDIRECT_ACTION, remote_npuv4 + "@" + tunnel_name } + }); + ASSERT_TRUE(ctx->remote_nh_map_.find(remote_npuv4 + "@" + tunnel_name) != ctx->remote_nh_map_.end()); + EXPECT_EQ(ctx->remote_nh_map_.find(remote_npuv4 + "@" + tunnel_name)->second.first, 2); /* 4 rules using this NH */ + EXPECT_EQ(ctx->remote_nh_map_.find(remote_npuv4 + "@" + tunnel_name)->second.second, 0x400000000064d); + + /* 2 calls will be made for non tunnel termination rules after primary switch */ + EXPECT_CALL(*ctx, isNeighborResolved(_)).Times(2).WillRepeatedly(Return(true)); + EXPECT_CALL(*ctx, removeNextHopTunnel(tunnel_name, remote_npuv4_ip)).WillOnce(Return(true)); + + doDashEniFwdTableTask(applDb.get(), + deque( + { + { + vnet_name + ":" + test_mac, + SET_COMMAND, + { + { ENI_FWD_PRIMARY, "1" }, // Primary is Local now + } + } + } + ) + ); + + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac_key+ "_OUT", { + { ACTION_REDIRECT_ACTION, local_pav4 } + }); + /* Check ACL Rules */ + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac_key+ "_OUT_TERM", { + { ACTION_REDIRECT_ACTION, local_pav4 } + }); + /* Check the tunnel is removed */ + ASSERT_TRUE(ctx->remote_nh_map_.find(remote_npuv4 + "@" + tunnel_name) == ctx->remote_nh_map_.end()); + } + + /* + T1 doesn't host the ENI, Both the enndpoints are Remote. + No Tunnel Termination Rules expected + */ + TEST_F(DashEniFwdOrchTest, RemoteNeighbor_NoTunnelTerm) + { + IpAddress remote_npuv4_ip = IpAddress(remote_2_npuv4); + EXPECT_CALL(*ctx, findVnetTunnel(vnet_name, _)).Times(2) // Only two rules are created + .WillRepeatedly(DoAll( + SetArgReferee<1>(tunnel_name), + Return(true) + )); + + EXPECT_CALL(*ctx, createNextHopTunnel(tunnel_name, remote_npuv4_ip)).Times(1) + .WillRepeatedly(Return(0x400000000064d)); // Only once since same NH object will be re-used + + doDashEniFwdTableTask(applDb.get(), + deque( + { + { + vnet_name + ":" + test_mac, + SET_COMMAND, + { + { ENI_FWD_VDPU_IDS, "2,3" }, + { ENI_FWD_PRIMARY, "3" }, // Remote endpoint is the primary + { ENI_FWD_OUT_VNI, to_string(test_vni) } + } + } + } + ) + ); + + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac_key+ "_IN", { + { ACTION_REDIRECT_ACTION, remote_2_npuv4 + "@" + tunnel_name } + }); + checkKFV(aclRuleTable.get(), "ENI:" + vnet_name + "_" + test_mac_key+ "_OUT", { + { ACTION_REDIRECT_ACTION, remote_2_npuv4 + "@" + tunnel_name } + }); + + /* Tunnel termination rules are not installed */ + checkRuleUninstalled("ENI:" + vnet_name + "_" + test_mac_key+ "_IN_TERM"); + checkRuleUninstalled("ENI:" + vnet_name + "_" + test_mac_key+ "_OUT_TERM"); + } + + /* + Test ACL Table and Table Type config + */ + TEST_F(DashEniFwdOrchTest, TestAclTableConfig) + { + /* Create a set of ports */ + std::map allPorts; + allPorts["Ethernet0"] = Port("Ethernet0", Port::PHY); + allPorts["Ethernet4"] = Port("Ethernet4", Port::PHY); + allPorts["Ethernet8"] = Port("Ethernet8", Port::PHY); + allPorts["Ethernet16"] = Port("Ethernet16", Port::PHY); + allPorts["PortChannel1011"] = Port("PortChannel1012", Port::LAG); + allPorts["PortChannel1012"] = Port("Ethernet16", Port::LAG); + allPorts["PortChannel1011"].m_members.insert("Ethernet8"); + allPorts["PortChannel1012"].m_members.insert("Ethernet16"); + EXPECT_CALL(*ctx, getAllPorts()).WillOnce(ReturnRef(allPorts)); + + Table aclTableType(applDb.get(), APP_ACL_TABLE_TYPE_TABLE_NAME); + Table aclTable(applDb.get(), APP_ACL_TABLE_TABLE_NAME); + Table portTable(cfgDb.get(), CFG_PORT_TABLE_NAME); + + portTable.set("Ethernet0", + { + { "lanes", "0,1,2,3" } + }, SET_COMMAND); + + portTable.set("Ethernet4", + { + { "lanes", "4,5,6,7" }, + { PORT_ROLE, PORT_ROLE_DPC } + }, SET_COMMAND); + + eniOrch->initAclTableCfg(); + + checkKFV(&aclTableType, "ENI_REDIRECT", { + { ACL_TABLE_TYPE_MATCHES, "TUNNEL_VNI,DST_IP,INNER_SRC_MAC,INNER_DST_MAC,TUNNEL_TERM" }, + { ACL_TABLE_TYPE_ACTIONS, "REDIRECT_ACTION" }, + { ACL_TABLE_TYPE_BPOINT_TYPES, "PORT,PORTCHANNEL" } + }); + + checkKFV(&aclTable, "ENI", { + { ACL_TABLE_TYPE, "ENI_REDIRECT" }, + { ACL_TABLE_STAGE, STAGE_INGRESS }, + { ACL_TABLE_PORTS, "Ethernet0,PortChannel1011,PortChannel1012" } + }); + } +} + +namespace mock_orch_test +{ + TEST_F(MockOrchTest, EniFwdCtx) + { + EniFwdCtx ctx(m_config_db.get(), m_app_db.get()); + ASSERT_NO_THROW(ctx.initialize()); + + NextHopKey nh(IpAddress("10.0.0.1"), "Ethernet0"); + ASSERT_NO_THROW(ctx.isNeighborResolved(nh)); + ASSERT_NO_THROW(ctx.resolveNeighbor(nh)); + ASSERT_NO_THROW(ctx.getRouterIntfsAlias(IpAddress("10.0.0.1"))); + + uint64_t vni; + ASSERT_NO_THROW(ctx.findVnetVni("Vnet_1000", vni)); + string tunnel; + ASSERT_NO_THROW(ctx.findVnetTunnel("Vnet_1000", tunnel)); + ASSERT_NO_THROW(ctx.getAllPorts()); + ASSERT_NO_THROW(ctx.createNextHopTunnel("tunnel0", IpAddress("10.0.0.1"))); + ASSERT_NO_THROW(ctx.removeNextHopTunnel("tunnel0", IpAddress("10.0.0.1"))); + } +} diff --git a/tests/mock_tests/mock_orch_test.cpp b/tests/mock_tests/mock_orch_test.cpp index c6898188b4..370aec45fb 100644 --- a/tests/mock_tests/mock_orch_test.cpp +++ b/tests/mock_tests/mock_orch_test.cpp @@ -258,6 +258,10 @@ void MockOrchTest::SetUp() gDirectory.set(m_VxlanTunnelOrch); ut_orch_list.push_back((Orch **)&m_VxlanTunnelOrch); + m_vnetOrch = new VNetOrch(m_app_db.get(), APP_VNET_TABLE_NAME); + gDirectory.set(m_vnetOrch); + ut_orch_list.push_back((Orch **)&m_vnetOrch); + ApplyInitialConfigs(); PostSetUp(); } diff --git a/tests/mock_tests/mock_orch_test.h b/tests/mock_tests/mock_orch_test.h index 86c5a36655..fa6e5faed8 100644 --- a/tests/mock_tests/mock_orch_test.h +++ b/tests/mock_tests/mock_orch_test.h @@ -50,6 +50,7 @@ namespace mock_orch_test MuxStateOrch *m_MuxStateOrch; FlexCounterOrch *m_FlexCounterOrch; VxlanTunnelOrch *m_VxlanTunnelOrch; + VNetOrch *m_vnetOrch; DashOrch *m_DashOrch; void PrepareSai(); diff --git a/tests/mock_tests/mock_orchagent_main.cpp b/tests/mock_tests/mock_orchagent_main.cpp index 96d86018fa..5f011eb6b8 100644 --- a/tests/mock_tests/mock_orchagent_main.cpp +++ b/tests/mock_tests/mock_orchagent_main.cpp @@ -13,6 +13,7 @@ MacAddress gMacAddress; MacAddress gVxlanMacAddress; string gMySwitchType = "switch"; +string gMySwitchSubType = "SmartSwitch"; int32_t gVoqMySwitchId = 0; string gMyHostName = "Linecard1"; string gMyAsicName = "Asic0"; diff --git a/tests/request_parser_ut.cpp b/tests/request_parser_ut.cpp index 8409dc5b66..7ac44b12b7 100644 --- a/tests/request_parser_ut.cpp +++ b/tests/request_parser_ut.cpp @@ -1761,3 +1761,47 @@ TEST(request_parser, multipleValuesInvalidMac) FAIL() << "Expected std::invalid_argument, not other exception"; } } + +/* +Check MAC key +*/ +const request_description_t test_mac_key = { + { REQ_T_STRING, REQ_T_MAC_ADDRESS }, + { + { "f1", REQ_T_STRING } + }, + { } +}; + +class TestRequestMacKey: public Request +{ +public: + TestRequestMacKey() : Request(test_mac_key, ':') { } +}; + +TEST(request_parser, mac_key_parse_checker) +{ + KeyOpFieldsValuesTuple t {"Vnet_1000:ab:b1:ca:dd:e1:f2", "SET", + { } + }; + + try + { + TestRequestMacKey request; + + EXPECT_NO_THROW(request.parse(t)); + + EXPECT_STREQ(request.getOperation().c_str(), "SET"); + EXPECT_STREQ(request.getFullKey().c_str(), "Vnet_1000:ab:b1:ca:dd:e1:f2"); + EXPECT_EQ(request.getKeyString(0), "Vnet_1000"); + EXPECT_EQ(request.getKeyMacAddress(1), MacAddress("ab:b1:ca:dd:e1:f2")); + } + catch (const std::exception& e) + { + FAIL() << "Got unexpected exception " << e.what(); + } + catch (...) + { + FAIL() << "Got unexpected exception"; + } +}