diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index e7e0cebd3818..d1240cf9af2d 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -61,7 +61,8 @@ orchagent_SOURCES = \ chassisorch.cpp \ debugcounterorch.cpp \ natorch.cpp \ - muxorch.cpp + muxorch.cpp \ + macsecorch.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/flex_counter/flex_counter_manager.cpp b/orchagent/flex_counter/flex_counter_manager.cpp index 000ead1882fd..299e238d37cc 100644 --- a/orchagent/flex_counter/flex_counter_manager.cpp +++ b/orchagent/flex_counter/flex_counter_manager.cpp @@ -7,6 +7,8 @@ #include "logger.h" #include "sai_serialize.h" +#include + using std::shared_ptr; using std::string; using std::unordered_map; @@ -32,10 +34,11 @@ const unordered_map FlexCounterManager::status_lookup = const unordered_map FlexCounterManager::counter_id_field_lookup = { - { 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 } + { 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 }, + { CounterType::MACSEC_SA_ATTR, MACSEC_SA_ATTR_ID_LIST }, }; FlexCounterManager::FlexCounterManager( diff --git a/orchagent/flex_counter/flex_counter_manager.h b/orchagent/flex_counter/flex_counter_manager.h index 1561236019f5..4df99c90bddd 100644 --- a/orchagent/flex_counter/flex_counter_manager.h +++ b/orchagent/flex_counter/flex_counter_manager.h @@ -22,7 +22,8 @@ enum class CounterType PORT, QUEUE, PORT_DEBUG, - SWITCH_DEBUG + SWITCH_DEBUG, + MACSEC_SA_ATTR, }; // FlexCounterManager allows users to manage a group of flex counters. diff --git a/orchagent/macsecorch.cpp b/orchagent/macsecorch.cpp new file mode 100644 index 000000000000..e43b9504f902 --- /dev/null +++ b/orchagent/macsecorch.cpp @@ -0,0 +1,2213 @@ +#include "macsecorch.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* Global Variables*/ + +#define AVAILABLE_ACL_PRIORITIES_LIMITATION (32) +#define EAPOL_ETHER_TYPE (0x888e) +#define MACSEC_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS (1000) +#define COUNTERS_MACSEC_ATTR_GROUP "COUNTERS_MACSEC_ATTR" + +extern sai_object_id_t gSwitchId; +extern sai_macsec_api_t *sai_macsec_api; +extern sai_acl_api_t *sai_acl_api; +extern sai_port_api_t *sai_port_api; +extern sai_switch_api_t *sai_switch_api; + +constexpr bool DEFAULT_ENABLE_ENCRYPT = true; +constexpr bool DEFAULT_SCI_IN_SECTAG = false; +constexpr sai_macsec_cipher_suite_t DEFAULT_CIPHER_SUITE = SAI_MACSEC_CIPHER_SUITE_GCM_AES_128; + +static const std::vector macsec_egress_sa_attrs = + { + "SAI_MACSEC_SA_ATTR_XPN", +}; + +static const std::vector macsec_ingress_sa_attrs = + { + "SAI_MACSEC_SA_ATTR_MINIMUM_XPN", +}; + +template +static bool extract_variables(const std::string &input, char delimiter, T &output, Args &... args) +{ + const auto tokens = swss::tokenize(input, delimiter); + try + { + swss::lexical_convert(tokens, output, args...); + return true; + } + catch(const std::exception& e) + { + return false; + } +} + +template +static bool get_value( + const MACsecOrch::TaskArgs & ta, + const std::string & field, + T & value) +{ + SWSS_LOG_ENTER(); + + auto value_opt = swss::fvsGetValue(ta, field, true); + if (!value_opt) + { + SWSS_LOG_DEBUG("Cannot find field : %s", field.c_str()); + return false; + } + + try + { + lexical_convert(*value_opt, value); + return true; + } + catch(const std::exception &err) + { + SWSS_LOG_DEBUG("Cannot convert field : %s to type : %s", field.c_str(), typeid(T).name()); + return false; + } +} + +struct MACsecSAK +{ + sai_macsec_sak_t m_sak; + bool m_sak_256_enable; +}; + +static void lexical_convert(const std::string &buffer, MACsecSAK &sak) +{ + SWSS_LOG_ENTER(); + + bool convert_done = false; + memset(&sak, 0, sizeof(sak)); + // One hex indicates 4 bits + size_t bit_count = buffer.length() * 4; + // 128 bits SAK + if (bit_count == 128) + { + // 128-bit SAK uses only Bytes 16..31. + sak.m_sak_256_enable = false; + convert_done = swss::hex_to_binary( + buffer, + &sak.m_sak[16], + 16); + } + // 256 bits SAK + else if (bit_count == 256) + { + sak.m_sak_256_enable = true; + convert_done = swss::hex_to_binary( + buffer, + sak.m_sak, + 32); + } + + if (!convert_done) + { + SWSS_LOG_THROW("Invalid SAK %s", buffer.c_str()); + } +} + +struct MACsecSalt +{ + sai_macsec_salt_t m_salt; +}; + +static void lexical_convert(const std::string &buffer, MACsecSalt &salt) +{ + SWSS_LOG_ENTER(); + + memset(&salt, 0, sizeof(salt)); + if ( + (buffer.length() != sizeof(salt.m_salt) * 2) || (!swss::hex_to_binary(buffer, salt.m_salt, sizeof(salt.m_salt)))) + { + SWSS_LOG_THROW("Invalid SALT %s", buffer.c_str()); + } +} + +struct MACsecAuthKey +{ + sai_macsec_auth_key_t m_auth_key; +}; + +static void lexical_convert(const std::string &buffer, MACsecAuthKey &auth_key) +{ + SWSS_LOG_ENTER(); + + memset(&auth_key, 0, sizeof(auth_key)); + if ( + (buffer.length() != sizeof(auth_key.m_auth_key) * 2) || (!swss::hex_to_binary( + buffer, + auth_key.m_auth_key, + sizeof(auth_key.m_auth_key)))) + { + SWSS_LOG_THROW("Invalid Auth Key %s", buffer.c_str()); + } +} + +/* Recover from a fail action by a serial of pre-defined recover actions */ +class RecoverStack +{ +public: + ~RecoverStack() + { + pop_all(true); + } + void clear() + { + pop_all(); + } + void add_action(std::function action) + { + m_recover_actions.push(action); + } + +private: + void pop_all(bool do_recover = false) + { + while (!m_recover_actions.empty()) + { + if (do_recover) + { + m_recover_actions.top()(); + } + m_recover_actions.pop(); + } + } + std::stack> m_recover_actions; +}; + +/* Get MACsecOrch Context from port_name, sci or an */ + +class MACsecOrchContext +{ +public: + MACsecOrchContext( + MACsecOrch *orch, + const std::string &port_name) : MACsecOrchContext(orch) + { + m_port_name.reset(new std::string(port_name)); + } + MACsecOrchContext( + MACsecOrch *orch, + const std::string &port_name, + sai_macsec_direction_t direction, + sai_uint64_t sci) : MACsecOrchContext(orch, port_name) + { + m_direction = direction; + m_sci.reset(new sai_uint64_t(sci)); + } + MACsecOrchContext( + MACsecOrch *orch, + const std::string &port_name, + sai_macsec_direction_t direction, + sai_uint64_t sci, + macsec_an_t an) : MACsecOrchContext(orch, port_name, direction, sci) + { + m_an.reset(new macsec_an_t(an)); + } + + sai_object_id_t *get_port_id() + { + if(m_port_id == nullptr) + { + auto port = get_port(); + if (port == nullptr) + { + return nullptr; + } + m_port_id = std::make_unique(port->m_port_id); + // TODO: If the MACsec was enabled at the gearbox, should use line port id as the port id. + } + return m_port_id.get(); + } + + sai_object_id_t *get_switch_id() + { + if (m_switch_id == nullptr) + { + if (gSwitchId == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR("Switch ID cannot be found"); + return nullptr; + } + m_switch_id = std::make_unique(gSwitchId); + } + return m_switch_id.get(); + } + + MACsecOrch::MACsecObject *get_macsec_obj() + { + if (m_macsec_obj == nullptr) + { + auto switch_id = get_switch_id(); + if (switch_id == nullptr) + { + return nullptr; + } + auto macsec_port = m_orch->m_macsec_objs.find(*switch_id); + if (macsec_port == m_orch->m_macsec_objs.end()) + { + SWSS_LOG_INFO("Cannot find the MACsec Object at the port %s.", m_port_name->c_str()); + return nullptr; + } + m_macsec_obj = &macsec_port->second; + } + return m_macsec_obj; + } + + MACsecOrch::MACsecPort *get_macsec_port() + { + if (m_macsec_port == nullptr) + { + if (m_orch == nullptr) + { + SWSS_LOG_ERROR("MACsec orch wasn't provided"); + return nullptr; + } + if (m_port_name == nullptr || m_port_name->empty()) + { + SWSS_LOG_ERROR("Port name wasn't provided."); + return nullptr; + } + auto macsec_port = m_orch->m_macsec_ports.find(*m_port_name); + if (macsec_port == m_orch->m_macsec_ports.end()) + { + SWSS_LOG_INFO("Cannot find the MACsec Object at the port %s.", m_port_name->c_str()); + return nullptr; + } + m_macsec_port = macsec_port->second.get(); + } + return m_macsec_port; + } + + MACsecOrch::MACsecACLTable *get_acl_table() + { + if (m_acl_table == nullptr) + { + auto port = get_macsec_port(); + if (port == nullptr) + { + return nullptr; + } + m_acl_table = + (m_direction == SAI_MACSEC_DIRECTION_EGRESS) + ? &port->m_egress_acl_table + : &port->m_ingress_acl_table; + } + return m_acl_table; + } + + MACsecOrch::MACsecSC *get_macsec_sc() + { + if (m_macsec_sc == nullptr) + { + if (m_sci == nullptr) + { + SWSS_LOG_ERROR("SCI wasn't provided"); + return nullptr; + } + auto port = get_macsec_port(); + if (port == nullptr) + { + return nullptr; + } + auto &scs = + (m_direction == SAI_MACSEC_DIRECTION_EGRESS) + ? port->m_egress_scs + : port->m_ingress_scs; + auto sc = scs.find(*m_sci); + if (sc == scs.end()) + { + SWSS_LOG_INFO("Cannot find the MACsec SC 0x%" PRIx64 " at the port %s.", *m_sci, m_port_name->c_str()); + return nullptr; + } + m_macsec_sc = &sc->second; + } + return m_macsec_sc; + } + + sai_object_id_t *get_macsec_sa() + { + if (m_macsec_sa == nullptr) + { + if (m_an == nullptr) + { + SWSS_LOG_ERROR("AN wasn't provided"); + return nullptr; + } + auto sc = get_macsec_sc(); + if (sc == nullptr) + { + return nullptr; + } + auto an = sc->m_sa_ids.find(*m_an); + if (an == sc->m_sa_ids.end()) + { + SWSS_LOG_INFO( + "Cannot find the MACsec SA %u of SC 0x%" PRIx64 " at the port %s.", + *m_an, + *m_sci, + m_port_name->c_str()); + return nullptr; + } + m_macsec_sa = &an->second; + } + return m_macsec_sa; + } + +private: + MACsecOrchContext(MACsecOrch *orch) : m_orch(orch), + m_port_name(nullptr), + m_direction(SAI_MACSEC_DIRECTION_EGRESS), + m_sci(nullptr), + m_an(nullptr), + m_port(nullptr), + m_macsec_obj(nullptr), + m_port_id(nullptr), + m_switch_id(nullptr), + m_macsec_port(nullptr), + m_acl_table(nullptr), + m_macsec_sc(nullptr), + m_macsec_sa(nullptr) + { + } + + const Port *get_port() + { + if (m_port == nullptr) + { + if (m_orch == nullptr) + { + SWSS_LOG_ERROR("MACsec orch wasn't provided"); + return nullptr; + } + if (m_port_name == nullptr || m_port_name->empty()) + { + SWSS_LOG_ERROR("Port name wasn't provided."); + return nullptr; + } + auto port = std::make_unique(); + if (!m_orch->m_port_orch->getPort(*m_port_name, *port)) + { + SWSS_LOG_INFO("Cannot find the port %s.", m_port_name->c_str()); + return nullptr; + } + m_port = std::move(port); + } + return m_port.get(); + } + + MACsecOrch *m_orch; + std::shared_ptr m_port_name; + sai_macsec_direction_t m_direction; + std::unique_ptr m_sci; + std::unique_ptr m_an; + + std::unique_ptr m_port; + MACsecOrch::MACsecObject *m_macsec_obj; + std::unique_ptr m_port_id; + std::unique_ptr m_switch_id; + + MACsecOrch::MACsecPort *m_macsec_port; + MACsecOrch::MACsecACLTable *m_acl_table; + + MACsecOrch::MACsecSC *m_macsec_sc; + sai_object_id_t *m_macsec_sa; +}; + +/* MACsec Orchagent */ + +MACsecOrch::MACsecOrch( + DBConnector *app_db, + DBConnector *state_db, + const std::vector &tables, + PortsOrch *port_orch) : Orch(app_db, tables), + m_port_orch(port_orch), + m_state_macsec_port(state_db, STATE_MACSEC_PORT_TABLE_NAME), + m_state_macsec_egress_sc(state_db, STATE_MACSEC_EGRESS_SC_TABLE_NAME), + m_state_macsec_ingress_sc(state_db, STATE_MACSEC_INGRESS_SC_TABLE_NAME), + m_state_macsec_egress_sa(state_db, STATE_MACSEC_EGRESS_SA_TABLE_NAME), + m_state_macsec_ingress_sa(state_db, STATE_MACSEC_INGRESS_SA_TABLE_NAME), + m_counter_db("COUNTERS_DB", 0), + m_macsec_counters_map(&m_counter_db, COUNTERS_MACSEC_NAME_MAP), + m_macsec_flex_counter_manager( + COUNTERS_MACSEC_ATTR_GROUP, + StatsMode::READ, + MACSEC_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS, true) +{ + SWSS_LOG_ENTER(); +} + +MACsecOrch::~MACsecOrch() +{ + while (!m_macsec_ports.empty()) + { + auto port = m_macsec_ports.begin(); + const MACsecOrch::TaskArgs temp; + taskDisableMACsecPort(port->first, temp); + } +} + +void MACsecOrch::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + using TaskType = std::tuple; + using TaskFunc = task_process_status (MACsecOrch::*)( + const std::string &, + const TaskArgs &); + const static std::map TaskMap = { + {{APP_MACSEC_PORT_TABLE_NAME, SET_COMMAND}, + &MACsecOrch::taskUpdateMACsecPort}, + {{APP_MACSEC_PORT_TABLE_NAME, DEL_COMMAND}, + &MACsecOrch::taskDisableMACsecPort}, + {{APP_MACSEC_EGRESS_SC_TABLE_NAME, SET_COMMAND}, + &MACsecOrch::taskUpdateEgressSC}, + {{APP_MACSEC_EGRESS_SC_TABLE_NAME, DEL_COMMAND}, + &MACsecOrch::taskDeleteEgressSC}, + {{APP_MACSEC_INGRESS_SC_TABLE_NAME, SET_COMMAND}, + &MACsecOrch::taskUpdateIngressSC}, + {{APP_MACSEC_INGRESS_SC_TABLE_NAME, DEL_COMMAND}, + &MACsecOrch::taskDeleteIngressSC}, + {{APP_MACSEC_EGRESS_SA_TABLE_NAME, SET_COMMAND}, + &MACsecOrch::taskUpdateEgressSA}, + {{APP_MACSEC_EGRESS_SA_TABLE_NAME, DEL_COMMAND}, + &MACsecOrch::taskDeleteEgressSA}, + {{APP_MACSEC_INGRESS_SA_TABLE_NAME, SET_COMMAND}, + &MACsecOrch::taskUpdateIngressSA}, + {{APP_MACSEC_INGRESS_SA_TABLE_NAME, DEL_COMMAND}, + &MACsecOrch::taskDeleteIngressSA}, + }; + + const std::string &table_name = consumer.getTableName(); + auto itr = consumer.m_toSync.begin(); + while (itr != consumer.m_toSync.end()) + { + task_process_status task_done = task_failed; + auto &message = itr->second; + const std::string &op = kfvOp(message); + + auto task = TaskMap.find(std::make_tuple(table_name, op)); + if (task != TaskMap.end()) + { + task_done = (this->*task->second)( + kfvKey(message), + kfvFieldsValues(message)); + } + else + { + SWSS_LOG_ERROR( + "Unknown task : %s - %s", + table_name.c_str(), + op.c_str()); + } + + if (task_done == task_need_retry) + { + SWSS_LOG_DEBUG( + "Task %s - %s need retry", + table_name.c_str(), + op.c_str()); + ++itr; + } + else + { + if (task_done != task_success) + { + SWSS_LOG_WARN("Task %s - %s fail", + table_name.c_str(), + op.c_str()); + } + else + { + SWSS_LOG_DEBUG( + "Task %s - %s success", + table_name.c_str(), + op.c_str()); + } + + itr = consumer.m_toSync.erase(itr); + } + } +} + +task_process_status MACsecOrch::taskUpdateMACsecPort( + const std::string &port_name, + const TaskArgs &port_attr) +{ + SWSS_LOG_ENTER(); + + MACsecOrchContext ctx(this, port_name); + RecoverStack recover; + + if (ctx.get_port_id() == nullptr || ctx.get_switch_id() == nullptr) + { + return task_need_retry; + } + if (ctx.get_macsec_obj() == nullptr) + { + if (!initMACsecObject(*ctx.get_switch_id())) + { + SWSS_LOG_WARN("Cannot init MACsec Object at the port %s.", port_name.c_str()); + return task_failed; + } + auto switch_id = *ctx.get_switch_id(); + recover.add_action([this, switch_id]() { this->deinitMACsecObject(switch_id); }); + } + if (ctx.get_macsec_port() == nullptr) + { + auto macsec_port_itr = m_macsec_ports.emplace(port_name, std::make_shared()).first; + recover.add_action([this, macsec_port_itr]() { this->m_macsec_ports.erase(macsec_port_itr); }); + + if (!createMACsecPort( + *macsec_port_itr->second, + port_name, + port_attr, + *ctx.get_macsec_obj(), + *ctx.get_port_id(), + *ctx.get_switch_id())) + { + return task_failed; + } + ctx.get_macsec_obj()->m_macsec_ports[port_name] = macsec_port_itr->second; + recover.add_action([this, &port_name, &ctx, macsec_port_itr]() { + this->deleteMACsecPort( + *macsec_port_itr->second, + port_name, + *ctx.get_macsec_obj(), + *ctx.get_port_id()); + }); + } + if (!updateMACsecPort(*ctx.get_macsec_port(), port_attr)) + { + return task_failed; + } + + recover.clear(); + return task_success; +} + +task_process_status MACsecOrch::taskDisableMACsecPort( + const std::string &port_name, + const TaskArgs &port_attr) +{ + SWSS_LOG_ENTER(); + + MACsecOrchContext ctx(this, port_name); + + if (ctx.get_switch_id() == nullptr || ctx.get_macsec_port() == nullptr) + { + SWSS_LOG_WARN("Cannot find MACsec switch at the port %s.", port_name.c_str()); + return task_failed; + } + if (ctx.get_port_id() == nullptr) + { + SWSS_LOG_WARN("Cannot find the port %s.", port_name.c_str()); + return task_failed; + } + + if (ctx.get_macsec_port() == nullptr) + { + SWSS_LOG_INFO("The MACsec wasn't enabled at the port %s", port_name.c_str()); + return task_success; + } + + auto result = task_success; + if (!deleteMACsecPort( + *ctx.get_macsec_port(), + port_name, + *ctx.get_macsec_obj(), + *ctx.get_port_id())) + { + result = task_failed; + } + m_macsec_ports.erase(port_name); + ctx.get_macsec_obj()->m_macsec_ports.erase(port_name); + // All ports on this macsec object have been deleted. + if (ctx.get_macsec_obj()->m_macsec_ports.empty()) + { + if (!deinitMACsecObject(*ctx.get_switch_id())) + { + SWSS_LOG_WARN("Cannot deinit macsec at the port %s.", port_name.c_str()); + result = task_failed; + } + } + + return result; +} + +task_process_status MACsecOrch::taskUpdateEgressSC( + const std::string &port_sci, + const TaskArgs &sc_attr) +{ + SWSS_LOG_ENTER(); + return updateMACsecSC(port_sci, sc_attr, SAI_MACSEC_DIRECTION_EGRESS); +} + +task_process_status MACsecOrch::taskDeleteEgressSC( + const std::string &port_sci, + const TaskArgs &sc_attr) +{ + SWSS_LOG_ENTER(); + return deleteMACsecSC(port_sci, SAI_MACSEC_DIRECTION_EGRESS); +} + +task_process_status MACsecOrch::taskUpdateIngressSC( + const std::string &port_sci, + const TaskArgs &sc_attr) +{ + SWSS_LOG_ENTER(); + return updateMACsecSC(port_sci, sc_attr, SAI_MACSEC_DIRECTION_INGRESS); +} + +task_process_status MACsecOrch::taskDeleteIngressSC( + const std::string &port_sci, + const TaskArgs &sc_attr) +{ + SWSS_LOG_ENTER(); + return deleteMACsecSC(port_sci, SAI_MACSEC_DIRECTION_INGRESS); +} + +task_process_status MACsecOrch::taskUpdateEgressSA( + const std::string &port_sci_an, + const TaskArgs &sa_attr) +{ + SWSS_LOG_ENTER(); + std::string port_name; + sai_uint64_t sci = 0; + macsec_an_t an = 0; + if (!extract_variables(port_sci_an, ':', port_name, sci, an) || an > MAX_SA_NUMBER) + { + SWSS_LOG_WARN("The key %s isn't correct.", port_sci_an.c_str()); + return task_failed; + } + + MACsecOrchContext ctx(this, port_name, SAI_MACSEC_DIRECTION_EGRESS, sci, an); + if (ctx.get_macsec_sc() == nullptr) + { + SWSS_LOG_INFO("The MACsec SC 0x%" PRIx64 " hasn't been created at the port %s.", sci, port_name.c_str()); + return task_need_retry; + } + if (ctx.get_macsec_sc()->m_encoding_an == an) + { + return createMACsecSA(port_sci_an, sa_attr, SAI_MACSEC_DIRECTION_EGRESS); + } + return task_need_retry; +} + +task_process_status MACsecOrch::taskDeleteEgressSA( + const std::string &port_sci_an, + const TaskArgs &sa_attr) +{ + SWSS_LOG_ENTER(); + return deleteMACsecSA(port_sci_an, SAI_MACSEC_DIRECTION_EGRESS); +} + +task_process_status MACsecOrch::taskUpdateIngressSA( + const std::string &port_sci_an, + const TaskArgs &sa_attr) +{ + SWSS_LOG_ENTER(); + + swss::AlphaBoolean alpha_boolean = false; + get_value(sa_attr, "active", alpha_boolean); + bool active = alpha_boolean.operator bool(); + if (active) + { + return createMACsecSA(port_sci_an, sa_attr, SAI_MACSEC_DIRECTION_INGRESS); + } + else + { + + std::string port_name; + sai_uint64_t sci = 0; + macsec_an_t an = 0; + if (!extract_variables(port_sci_an, ':', port_name, sci, an) || an > MAX_SA_NUMBER) + { + SWSS_LOG_WARN("The key %s isn't correct.", port_sci_an.c_str()); + return task_failed; + } + + MACsecOrchContext ctx(this, port_name, SAI_MACSEC_DIRECTION_INGRESS, sci, an); + + if (ctx.get_macsec_sa() != nullptr) + { + return deleteMACsecSA(port_sci_an, SAI_MACSEC_DIRECTION_INGRESS); + } + else + { + // The MACsec SA hasn't been created. + // Don't need do anything until the active field to be enabled. + // To use "retry" because this message maybe include other initialized fields + // To use other return values will make us lose these initialized fields. + return task_need_retry; + } + } +} + +task_process_status MACsecOrch::taskDeleteIngressSA( + const std::string &port_sci_an, + const TaskArgs &sa_attr) +{ + SWSS_LOG_ENTER(); + return deleteMACsecSA(port_sci_an, SAI_MACSEC_DIRECTION_INGRESS); +} + +bool MACsecOrch::initMACsecObject(sai_object_id_t switch_id) +{ + SWSS_LOG_ENTER(); + + RecoverStack recover; + + auto macsec_obj = m_macsec_objs.emplace(switch_id, MACsecObject()); + if (!macsec_obj.second) + { + SWSS_LOG_INFO("The MACsec has been initialized at the switch 0x%" PRIx64, switch_id); + return true; + } + recover.add_action([&]() { m_macsec_objs.erase(macsec_obj.first); }); + + sai_attribute_t attr; + std::vector attrs; + + attr.id = SAI_MACSEC_ATTR_DIRECTION; + attr.value.s32 = SAI_MACSEC_DIRECTION_EGRESS; + attrs.push_back(attr); + if (sai_macsec_api->create_macsec( + &macsec_obj.first->second.m_egress_id, + switch_id, + static_cast(attrs.size()), + attrs.data()) != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN("Cannot initialize MACsec egress object at the switch 0x%" PRIx64, switch_id); + return false; + } + recover.add_action([&]() { sai_macsec_api->remove_macsec(macsec_obj.first->second.m_egress_id); }); + + attrs.clear(); + attr.id = SAI_MACSEC_ATTR_DIRECTION; + attr.value.s32 = SAI_MACSEC_DIRECTION_INGRESS; + attrs.push_back(attr); + if (sai_macsec_api->create_macsec( + &macsec_obj.first->second.m_ingress_id, + switch_id, + static_cast(attrs.size()), + attrs.data()) != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN("Cannot initialize MACsec ingress object at the switch 0x%" PRIx64, switch_id); + return false; + } + recover.add_action([&]() { sai_macsec_api->remove_macsec(macsec_obj.first->second.m_ingress_id); }); + + attrs.clear(); + attr.id = SAI_MACSEC_ATTR_SCI_IN_INGRESS_MACSEC_ACL; + attrs.push_back(attr); + if (sai_macsec_api->get_macsec_attribute( + macsec_obj.first->second.m_ingress_id, + static_cast(attrs.size()), + attrs.data()) != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN( + "Cannot get MACsec attribution SAI_MACSEC_ATTR_SCI_IN_INGRESS_MACSEC_ACL at the switch 0x%" PRIx64, + switch_id); + return false; + } + macsec_obj.first->second.m_sci_in_ingress_macsec_acl = attrs.front().value.booldata; + + recover.clear(); + return true; +} + +bool MACsecOrch::deinitMACsecObject(sai_object_id_t switch_id) +{ + SWSS_LOG_ENTER(); + + auto macsec_obj = m_macsec_objs.find(switch_id); + if (macsec_obj == m_macsec_objs.end()) + { + SWSS_LOG_INFO("The MACsec wasn't initialized at the switch 0x%" PRIx64, switch_id); + return true; + } + + bool result = true; + + if (sai_macsec_api->remove_macsec( + macsec_obj->second.m_egress_id) != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN("Cannot deinitialize MACsec egress object at the switch 0x%" PRIx64, macsec_obj->first); + result &= false; + } + + if (sai_macsec_api->remove_macsec( + macsec_obj->second.m_ingress_id) != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN("Cannot deinitialize MACsec ingress object at the switch 0x%" PRIx64, macsec_obj->first); + result &= false; + } + + m_macsec_objs.erase(macsec_obj); + return result; +} + +bool MACsecOrch::createMACsecPort( + MACsecPort &macsec_port, + const std::string &port_name, + const TaskArgs &port_attr, + const MACsecObject &macsec_obj, + sai_object_id_t line_port_id, + sai_object_id_t switch_id) +{ + SWSS_LOG_ENTER(); + + RecoverStack recover; + + if (!createMACsecPort( + macsec_port.m_egress_port_id, + line_port_id, + switch_id, + SAI_MACSEC_DIRECTION_EGRESS)) + { + SWSS_LOG_WARN("Cannot create MACsec egress port at the port %s", port_name.c_str()); + return false; + } + recover.add_action([this, &macsec_port]() { + this->deleteMACsecPort(macsec_port.m_egress_port_id); + macsec_port.m_egress_port_id = SAI_NULL_OBJECT_ID; + }); + + if (!createMACsecPort( + macsec_port.m_ingress_port_id, + line_port_id, + switch_id, + SAI_MACSEC_DIRECTION_INGRESS)) + { + SWSS_LOG_WARN("Cannot create MACsec ingress port at the port %s", port_name.c_str()); + return false; + } + recover.add_action([this, &macsec_port]() { + this->deleteMACsecPort(macsec_port.m_ingress_port_id); + macsec_port.m_ingress_port_id = SAI_NULL_OBJECT_ID; + }); + + macsec_port.m_enable_encrypt = DEFAULT_ENABLE_ENCRYPT; + macsec_port.m_sci_in_sectag = DEFAULT_SCI_IN_SECTAG; + macsec_port.m_cipher_suite = DEFAULT_CIPHER_SUITE; + macsec_port.m_enable = false; + + // If hardware matches SCI in ACL, the macsec_flow maps to an IEEE 802.1ae SecY object. + // Multiple SCs can be associated with such a macsec_flow. + // Then a specific value of SCI from the SecTAG in the packet is used to identify a specific SC + // for that macsec_flow. + // False means one flow can be associated with multiple ACL entries and multiple SC + if (!macsec_obj.m_sci_in_ingress_macsec_acl) + { + if (!createMACsecFlow( + macsec_port.m_egress_flow_id, + switch_id, + SAI_MACSEC_DIRECTION_EGRESS)) + { + SWSS_LOG_WARN("Cannot create MACsec egress flow at the port %s.", port_name.c_str()); + return false; + } + recover.add_action([this, &macsec_port]() { + this->deleteMACsecFlow(macsec_port.m_egress_flow_id); + macsec_port.m_egress_flow_id = SAI_NULL_OBJECT_ID; + }); + + if (!createMACsecFlow( + macsec_port.m_ingress_flow_id, + switch_id, + SAI_MACSEC_DIRECTION_INGRESS)) + { + SWSS_LOG_WARN("Cannot create MACsec ingress flow at the port %s.", port_name.c_str()); + return false; + } + recover.add_action([this, &macsec_port]() { + this->deleteMACsecFlow(macsec_port.m_ingress_flow_id); + macsec_port.m_ingress_flow_id = SAI_NULL_OBJECT_ID; + }); + } + + if (!initMACsecACLTable( + macsec_port.m_egress_acl_table, + line_port_id, + switch_id, + SAI_MACSEC_DIRECTION_EGRESS, + macsec_port.m_sci_in_sectag)) + { + SWSS_LOG_WARN("Cannot init the ACL Table at the port %s.", port_name.c_str()); + return false; + } + recover.add_action([this, &macsec_port, line_port_id]() { + this->deinitMACsecACLTable( + macsec_port.m_egress_acl_table, + line_port_id, + SAI_MACSEC_DIRECTION_EGRESS); + }); + + if (!initMACsecACLTable( + macsec_port.m_ingress_acl_table, + line_port_id, + switch_id, + SAI_MACSEC_DIRECTION_INGRESS, + macsec_port.m_sci_in_sectag)) + { + SWSS_LOG_WARN("Cannot init the ACL Table at the port %s.", port_name.c_str()); + return false; + } + recover.add_action([this, &macsec_port, line_port_id]() { + this->deinitMACsecACLTable( + macsec_port.m_ingress_acl_table, + line_port_id, + SAI_MACSEC_DIRECTION_INGRESS); + }); + + SWSS_LOG_NOTICE("MACsec port %s is created.", port_name.c_str()); + + std::vector fvVector; + fvVector.emplace_back("state", "ok"); + m_state_macsec_port.set(port_name, fvVector); + + recover.clear(); + return true; +} + +bool MACsecOrch::createMACsecPort( + sai_object_id_t &macsec_port_id, + sai_object_id_t line_port_id, + sai_object_id_t switch_id, + sai_macsec_direction_t direction) +{ + SWSS_LOG_ENTER(); + + sai_attribute_t attr; + std::vector attrs; + + attr.id = SAI_MACSEC_PORT_ATTR_MACSEC_DIRECTION; + attr.value.s32 = direction; + attrs.push_back(attr); + attr.id = SAI_MACSEC_PORT_ATTR_PORT_ID; + attr.value.oid = line_port_id; + attrs.push_back(attr); + if (sai_macsec_api->create_macsec_port( + &macsec_port_id, + switch_id, + static_cast(attrs.size()), + attrs.data()) != SAI_STATUS_SUCCESS) + { + return false; + } + + return true; +} + +bool MACsecOrch::updateMACsecPort(MACsecPort &macsec_port, const TaskArgs &port_attr) +{ + SWSS_LOG_ENTER(); + + RecoverStack recover; + + swss::AlphaBoolean alpha_boolean; + + if (get_value(port_attr, "enable_encrypt", alpha_boolean)) + { + macsec_port.m_enable_encrypt = alpha_boolean.operator bool(); + } + if (get_value(port_attr, "send_sci", alpha_boolean)) + { + macsec_port.m_sci_in_sectag = alpha_boolean.operator bool(); + } + std::string cipher_suite; + if (get_value(port_attr, "cipher_suite", cipher_suite)) + { + if (cipher_suite == "GCM-AES-128") + { + macsec_port.m_cipher_suite = SAI_MACSEC_CIPHER_SUITE_GCM_AES_128; + } + else if (cipher_suite == "GCM-AES-256") + { + macsec_port.m_cipher_suite = SAI_MACSEC_CIPHER_SUITE_GCM_AES_256; + } + else if (cipher_suite == "GCM-AES-XPN-128") + { + macsec_port.m_cipher_suite = SAI_MACSEC_CIPHER_SUITE_GCM_AES_XPN_128; + } + else if (cipher_suite == "GCM-AES-XPN-256") + { + macsec_port.m_cipher_suite = SAI_MACSEC_CIPHER_SUITE_GCM_AES_XPN_256; + } + else + { + SWSS_LOG_WARN("Unknow Cipher Suite %s", cipher_suite.c_str()); + return false; + } + } + swss::AlphaBoolean enable = false; + if (get_value(port_attr, "enable", enable) && enable.operator bool() != macsec_port.m_enable) + { + std::vector macsec_scs; + macsec_port.m_enable = enable.operator bool(); + for (auto &sc : macsec_port.m_egress_scs) + { + macsec_scs.push_back(&sc.second); + } + for (auto &sc : macsec_port.m_ingress_scs) + { + macsec_scs.push_back(&sc.second); + } + for (auto &macsec_sc : macsec_scs) + { + // Change the ACL entry action from packet action to MACsec flow + if (macsec_port.m_enable) + { + if (!setMACsecFlowActive(macsec_sc->m_entry_id, macsec_sc->m_flow_id, true)) + { + SWSS_LOG_WARN("Cannot change the ACL entry action from packet action to MACsec flow"); + return false; + } + auto an = macsec_sc->m_encoding_an; + auto flow_id = macsec_sc->m_flow_id; + recover.add_action([this, an, flow_id]() { this->setMACsecFlowActive(an, flow_id, false); }); + } + else + { + setMACsecFlowActive(macsec_sc->m_encoding_an, macsec_sc->m_flow_id, false); + } + } + } + + recover.clear(); + return true; +} + +bool MACsecOrch::deleteMACsecPort( + const MACsecPort &macsec_port, + const std::string &port_name, + const MACsecObject &macsec_obj, + sai_object_id_t line_port_id) +{ + SWSS_LOG_ENTER(); + + bool result = true; + + for (auto &sc : macsec_port.m_egress_scs) + { + const std::string port_sci = swss::join(':', port_name, sc.first); + if (deleteMACsecSC(port_sci, SAI_MACSEC_DIRECTION_EGRESS) != task_success) + { + result &= false; + } + } + for (auto &sc : macsec_port.m_ingress_scs) + { + const std::string port_sci = swss::join(':', port_name, sc.first); + if (deleteMACsecSC(port_sci, SAI_MACSEC_DIRECTION_INGRESS) != task_success) + { + result &= false; + } + } + + if (!macsec_obj.m_sci_in_ingress_macsec_acl) + { + if (!this->deleteMACsecFlow(macsec_port.m_egress_flow_id)) + { + SWSS_LOG_WARN("Cannot delete MACsec egress flow at the port %s", port_name.c_str()); + result &= false; + } + + if (!this->deleteMACsecFlow(macsec_port.m_ingress_flow_id)) + { + SWSS_LOG_WARN("Cannot delete MACsec ingress flow at the port %s", port_name.c_str()); + result &= false; + } + } + + if (!deinitMACsecACLTable(macsec_port.m_ingress_acl_table, line_port_id, SAI_MACSEC_DIRECTION_INGRESS)) + { + SWSS_LOG_WARN("Cannot deinit ingress ACL table at the port %s.", port_name.c_str()); + result &= false; + } + + if (!deinitMACsecACLTable(macsec_port.m_egress_acl_table, line_port_id, SAI_MACSEC_DIRECTION_EGRESS)) + { + SWSS_LOG_WARN("Cannot deinit egress ACL table at the port %s.", port_name.c_str()); + result &= false; + } + + if (!deleteMACsecPort(macsec_port.m_egress_port_id)) + { + SWSS_LOG_WARN("Cannot delete MACsec egress port at the port %s", port_name.c_str()); + result &= false; + } + + if (!deleteMACsecPort(macsec_port.m_ingress_port_id)) + { + SWSS_LOG_WARN("Cannot delete MACsec ingress port at the port %s", port_name.c_str()); + result &= false; + } + + m_state_macsec_port.del(port_name); + + return true; +} + +bool MACsecOrch::deleteMACsecPort(sai_object_id_t macsec_port_id) +{ + if (sai_macsec_api->remove_macsec_port( + macsec_port_id) != SAI_STATUS_SUCCESS) + { + return false; + } + return true; +} + +bool MACsecOrch::createMACsecFlow( + sai_object_id_t &flow_id, + sai_object_id_t switch_id, + sai_macsec_direction_t direction) +{ + SWSS_LOG_ENTER(); + + if (flow_id != SAI_NULL_OBJECT_ID) + { + return true; + } + + sai_attribute_t attr; + std::vector attrs; + + attr.id = SAI_MACSEC_FLOW_ATTR_MACSEC_DIRECTION; + attr.value.s32 = direction; + attrs.push_back(attr); + if (sai_macsec_api->create_macsec_flow( + &flow_id, + switch_id, + static_cast(attrs.size()), + attrs.data()) != SAI_STATUS_SUCCESS) + { + return false; + } + return true; +} + +bool MACsecOrch::deleteMACsecFlow(sai_object_id_t flow_id) +{ + if (sai_macsec_api->remove_macsec_flow( + flow_id) != SAI_STATUS_SUCCESS) + { + return false; + } + return true; +} + +task_process_status MACsecOrch::updateMACsecSC( + const std::string &port_sci, + const TaskArgs &sc_attr, + sai_macsec_direction_t direction) +{ + SWSS_LOG_ENTER(); + + std::string port_name; + sai_uint64_t sci = {0}; + if (!extract_variables(port_sci, ':', port_name, sci)) + { + SWSS_LOG_WARN("The key %s isn't correct.", port_sci.c_str()); + return task_failed; + } + + MACsecOrchContext ctx(this, port_name, direction, sci); + + if (ctx.get_macsec_port() == nullptr) + { + return task_need_retry; + } + + if (ctx.get_switch_id() == nullptr || ctx.get_macsec_obj() == nullptr) + { + SWSS_LOG_ERROR("Cannot find switch id at the port %s.", port_name.c_str()); + return task_failed; + } + + if (ctx.get_macsec_sc() == nullptr) + { + if (!createMACsecSC( + *ctx.get_macsec_port(), + port_name, + sc_attr, + *ctx.get_macsec_obj(), + sci, + *ctx.get_switch_id(), + direction)) + { + return task_failed; + } + } + else + { + if (!setEncodingAN(*ctx.get_macsec_sc(), sc_attr, direction)) + { + return task_failed; + } + } + + return task_success; +} + +bool MACsecOrch::setEncodingAN( + MACsecSC &sc, + const TaskArgs &sc_attr, + sai_macsec_direction_t direction) +{ + if (direction == SAI_MACSEC_DIRECTION_EGRESS) + { + if (!get_value(sc_attr, "encoding_an", sc.m_encoding_an)) + { + SWSS_LOG_WARN("Wrong parameter, the encoding AN cannot be found"); + return false; + } + } + else + { + SWSS_LOG_WARN("Cannot set encoding AN for the ingress SC"); + return false; + } + return true; +} + +bool MACsecOrch::createMACsecSC( + MACsecPort &macsec_port, + const std::string &port_name, + const TaskArgs &sc_attr, + const MACsecObject &macsec_obj, + sai_uint64_t sci, + sai_object_id_t switch_id, + sai_macsec_direction_t direction) +{ + SWSS_LOG_ENTER(); + + RecoverStack recover; + + const std::string port_sci = swss::join(':', port_name, sci); + + auto scs = + (direction == SAI_MACSEC_DIRECTION_EGRESS) + ? &macsec_port.m_egress_scs + : &macsec_port.m_ingress_scs; + auto sc_itr = scs->emplace( + std::piecewise_construct, + std::forward_as_tuple(sci), + std::forward_as_tuple()); + if (!sc_itr.second) + { + SWSS_LOG_ERROR("The SC %s has been created.", port_sci.c_str()); + return false; + } + recover.add_action([scs, sc_itr]() { + scs->erase(sc_itr.first->first); + }); + auto sc = &sc_itr.first->second; + + if (direction == SAI_MACSEC_DIRECTION_EGRESS) + { + get_value(sc_attr, "encoding_an", sc->m_encoding_an); + } + + sc->m_flow_id = SAI_NULL_OBJECT_ID; + // If SCI can only be used as ACL field + // Which means one flow can be associated with only one ACL entry and one SC. + if (macsec_obj.m_sci_in_ingress_macsec_acl) + { + if (!createMACsecFlow(sc->m_flow_id, switch_id, direction)) + { + SWSS_LOG_WARN("Cannot create MACsec Flow"); + return false; + } + recover.add_action([this, sc]() { this->deleteMACsecFlow(sc->m_flow_id); }); + } + else + { + sc->m_flow_id = + (direction == SAI_MACSEC_DIRECTION_EGRESS) + ? macsec_port.m_egress_flow_id + : macsec_port.m_ingress_flow_id; + } + + if (!createMACsecSC( + sc->m_sc_id, + switch_id, + direction, + sc->m_flow_id, + sci, + macsec_port.m_enable_encrypt, + macsec_port.m_sci_in_sectag, + macsec_port.m_cipher_suite)) + { + SWSS_LOG_WARN("Create MACsec SC %s fail.", port_sci.c_str()); + return false; + } + recover.add_action([this, sc]() { this->deleteMACsecSC(sc->m_sc_id); }); + + auto table = + (direction == SAI_MACSEC_DIRECTION_EGRESS) + ? &macsec_port.m_egress_acl_table + : &macsec_port.m_ingress_acl_table; + if (table->m_available_acl_priorities.empty()) + { + SWSS_LOG_WARN("Available ACL priorities have been exhausted."); + return false; + } + sc->m_acl_priority = *(table->m_available_acl_priorities.begin()); + table->m_available_acl_priorities.erase(table->m_available_acl_priorities.begin()); + if (!createMACsecACLDataEntry( + sc->m_entry_id, + table->m_table_id, + switch_id, + macsec_port.m_sci_in_sectag, + sci, + sc->m_acl_priority)) + { + SWSS_LOG_WARN("Cannot create ACL Data entry"); + return false; + } + recover.add_action([this, sc, table]() { + deleteMACsecACLEntry(sc->m_entry_id); + table->m_available_acl_priorities.insert(sc->m_acl_priority); + }); + + SWSS_LOG_NOTICE("MACsec SC %s is created.", port_sci.c_str()); + + std::vector fvVector; + fvVector.emplace_back("state", "ok"); + if (direction == SAI_MACSEC_DIRECTION_EGRESS) + { + m_state_macsec_egress_sc.set(swss::join('|', port_name, sci), fvVector); + } + else + { + m_state_macsec_ingress_sc.set(swss::join('|', port_name, sci), fvVector); + } + + recover.clear(); + return true; +} + +bool MACsecOrch::createMACsecSC( + sai_object_id_t &sc_id, + sai_object_id_t switch_id, + sai_macsec_direction_t direction, + sai_object_id_t flow_id, + sai_uint64_t sci, + bool encryption_enable, + bool send_sci, + sai_macsec_cipher_suite_t cipher_suite) +{ + SWSS_LOG_ENTER(); + + sai_attribute_t attr; + std::vector attrs; + + attr.id = SAI_MACSEC_SC_ATTR_MACSEC_DIRECTION; + attr.value.s32 = direction; + attrs.push_back(attr); + attr.id = SAI_MACSEC_SC_ATTR_FLOW_ID; + attr.value.oid = flow_id; + attrs.push_back(attr); + attr.id = SAI_MACSEC_SC_ATTR_MACSEC_SCI; + attr.value.u64 = sci; + attrs.push_back(attr); + attr.id = SAI_MACSEC_SC_ATTR_ENCRYPTION_ENABLE; + attr.value.booldata = encryption_enable; + attrs.push_back(attr); + attr.id = SAI_MACSEC_SC_ATTR_MACSEC_EXPLICIT_SCI_ENABLE; + attr.value.booldata = send_sci; + attrs.push_back(attr); + attr.id = SAI_MACSEC_SC_ATTR_MACSEC_CIPHER_SUITE; + attr.value.s32 = cipher_suite; + attrs.push_back(attr); + + if (sai_macsec_api->create_macsec_sc( + &sc_id, + switch_id, + static_cast(attrs.size()), + attrs.data()) != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN("Cannot create MACsec egress SC 0x%" PRIx64, sci); + return false; + } + return true; +} + +task_process_status MACsecOrch::deleteMACsecSC( + const std::string &port_sci, + sai_macsec_direction_t direction) +{ + SWSS_LOG_ENTER(); + + std::string port_name; + sai_uint64_t sci = 0; + if (!extract_variables(port_sci, ':', port_name, sci)) + { + SWSS_LOG_WARN("The key %s isn't correct.", port_sci.c_str()); + return task_failed; + } + + MACsecOrchContext ctx(this, port_name, direction, sci); + + if (ctx.get_macsec_sc() == nullptr) + { + SWSS_LOG_INFO("The MACsec SC %s wasn't created", port_sci.c_str()); + return task_success; + } + + auto result = task_success; + + for (auto &sa : ctx.get_macsec_sc()->m_sa_ids) + { + const std::string port_sci_an = swss::join(':', port_sci, sa.first); + deleteMACsecSA(port_sci_an, direction); + } + + deleteMACsecACLEntry(ctx.get_macsec_sc()->m_entry_id); + ctx.get_acl_table()->m_available_acl_priorities.insert(ctx.get_macsec_sc()->m_acl_priority); + + if (!deleteMACsecSC(ctx.get_macsec_sc()->m_sc_id)) + { + SWSS_LOG_WARN("The MACsec SC %s cannot be deleted", port_sci.c_str()); + result = task_failed; + } + + if (ctx.get_macsec_obj()->m_sci_in_ingress_macsec_acl) + { + if (!deleteMACsecFlow(ctx.get_macsec_sc()->m_flow_id)) + { + SWSS_LOG_WARN("Cannot delete MACsec flow"); + result = task_failed; + } + } + + auto scs = + (direction == SAI_MACSEC_DIRECTION_EGRESS) + ? &ctx.get_macsec_port()->m_egress_scs + : &ctx.get_macsec_port()->m_ingress_scs; + scs->erase(sci); + + SWSS_LOG_NOTICE("MACsec SC %s is deleted.", port_sci.c_str()); + + if (direction == SAI_MACSEC_DIRECTION_EGRESS) + { + m_state_macsec_egress_sc.del(swss::join('|', port_name, sci)); + } + else + { + m_state_macsec_ingress_sc.del(swss::join('|', port_name, sci)); + } + + return result; +} + +bool MACsecOrch::deleteMACsecSC(sai_object_id_t sc_id) +{ + SWSS_LOG_ENTER(); + + if (sai_macsec_api->remove_macsec_sc( + sc_id) != SAI_STATUS_SUCCESS) + { + return false; + } + return true; +} + +task_process_status MACsecOrch::createMACsecSA( + const std::string &port_sci_an, + const TaskArgs &sa_attr, + sai_macsec_direction_t direction) +{ + SWSS_LOG_ENTER(); + + std::string port_name; + sai_uint64_t sci = 0; + macsec_an_t an = 0; + if (!extract_variables(port_sci_an, ':', port_name, sci, an) || an > MAX_SA_NUMBER) + { + SWSS_LOG_WARN("The key %s isn't correct.", port_sci_an.c_str()); + return task_failed; + } + + MACsecOrchContext ctx(this, port_name, direction, sci, an); + + if (ctx.get_macsec_sa() != nullptr) + { + SWSS_LOG_NOTICE("The MACsec SA %s has been created.", port_sci_an.c_str()); + return task_success; + } + + if (ctx.get_macsec_sc() == nullptr) + { + SWSS_LOG_INFO("The MACsec SC 0x%" PRIx64 " hasn't been created at the port %s.", sci, port_name.c_str()); + return task_need_retry; + } + auto sc = ctx.get_macsec_sc(); + + MACsecSAK sak = {{0}, false}; + MACsecSalt salt = {0}; + sai_uint32_t ssci = 0; + MACsecAuthKey auth_key = {0}; + try + { + if (!get_value(sa_attr, "sak", sak)) + { + SWSS_LOG_WARN("The SAK isn't existed at SA %s", port_sci_an.c_str()); + return task_failed; + } + if (sak.m_sak_256_enable) + { + if (ctx.get_macsec_port()->m_cipher_suite == SAI_MACSEC_CIPHER_SUITE_GCM_AES_128 + && ctx.get_macsec_port()->m_cipher_suite == SAI_MACSEC_CIPHER_SUITE_GCM_AES_XPN_128) + { + SWSS_LOG_WARN("Wrong SAK with 256 bit, expect 128 bit"); + return task_failed; + } + } + else + { + if (ctx.get_macsec_port()->m_cipher_suite == SAI_MACSEC_CIPHER_SUITE_GCM_AES_256 + && ctx.get_macsec_port()->m_cipher_suite == SAI_MACSEC_CIPHER_SUITE_GCM_AES_XPN_256) + { + SWSS_LOG_WARN("Wrong SAK with 128 bit, expect 256 bit"); + return task_failed; + } + } + if (ctx.get_macsec_port()->m_cipher_suite == SAI_MACSEC_CIPHER_SUITE_GCM_AES_XPN_128 + || ctx.get_macsec_port()->m_cipher_suite == SAI_MACSEC_CIPHER_SUITE_GCM_AES_XPN_256) + { + if (!get_value(sa_attr, "salt", salt)) + { + SWSS_LOG_WARN("The salt isn't existed at SA %s", port_sci_an.c_str()); + return task_failed; + } + if (!get_value(sa_attr, "ssci", ssci)) + { + SWSS_LOG_WARN("The ssci isn't existed at SA %s", port_sci_an.c_str()); + return task_failed; + } + } + if (!get_value(sa_attr, "auth_key", auth_key)) + { + SWSS_LOG_WARN("The auth key isn't existed at SA %s", port_sci_an.c_str()); + return task_failed; + } + } + catch (const std::invalid_argument &e) + { + SWSS_LOG_WARN("Invalid argument : %s", e.what()); + return task_failed; + } + sai_uint64_t pn = 1; + if (direction == SAI_MACSEC_DIRECTION_EGRESS) + { + if (!get_value(sa_attr, "next_pn", pn)) + { + SWSS_LOG_WARN("The init pn isn't existed at SA %s", port_sci_an.c_str()); + return task_failed; + } + } + else + { + if (!get_value(sa_attr, "lowest_acceptable_pn", pn)) + { + SWSS_LOG_WARN("The init pn isn't existed at SA %s", port_sci_an.c_str()); + return task_failed; + } + } + + RecoverStack recover; + + // If this SA is the first SA + // change the ACL entry action from packet action to MACsec flow + if (ctx.get_macsec_port()->m_enable && sc->m_sa_ids.empty()) + { + if (!setMACsecFlowActive(sc->m_entry_id, sc->m_flow_id, true)) + { + SWSS_LOG_WARN("Cannot change the ACL entry action from packet action to MACsec flow"); + return task_failed; + } + recover.add_action([this, sc]() { + this->setMACsecFlowActive( + sc->m_encoding_an, + sc->m_flow_id, + false); + }); + } + + if (!createMACsecSA( + sc->m_sa_ids[an], + *ctx.get_switch_id(), + direction, + sc->m_sc_id, + an, + sak.m_sak, + salt.m_salt, + ssci, + auth_key.m_auth_key, + pn)) + { + SWSS_LOG_WARN("Cannot create the SA %s", port_sci_an.c_str()); + return task_failed; + } + recover.add_action([this, sc, an]() { + this->deleteMACsecSA(sc->m_sa_ids[an]); + sc->m_sa_ids.erase(an); + }); + + std::vector fvVector; + fvVector.emplace_back("state", "ok"); + if (direction == SAI_MACSEC_DIRECTION_EGRESS) + { + installCounter(CounterType::MACSEC_SA_ATTR, port_sci_an, sc->m_sa_ids[an], macsec_egress_sa_attrs); + m_state_macsec_egress_sa.set(swss::join('|', port_name, sci, an), fvVector); + } + else + { + installCounter(CounterType::MACSEC_SA_ATTR, port_sci_an, sc->m_sa_ids[an], macsec_ingress_sa_attrs); + m_state_macsec_ingress_sa.set(swss::join('|', port_name, sci, an), fvVector); + } + + SWSS_LOG_NOTICE("MACsec SA %s is created.", port_sci_an.c_str()); + + recover.clear(); + return task_success; +} + +task_process_status MACsecOrch::deleteMACsecSA( + const std::string &port_sci_an, + sai_macsec_direction_t direction) +{ + SWSS_LOG_ENTER(); + + std::string port_name = ""; + sai_uint64_t sci = 0; + macsec_an_t an = 0; + if (!extract_variables(port_sci_an, ':', port_name, sci, an) || an > MAX_SA_NUMBER) + { + SWSS_LOG_WARN("The key %s isn't correct.", port_sci_an.c_str()); + return task_failed; + } + + MACsecOrchContext ctx(this, port_name, direction, sci, an); + + if (ctx.get_macsec_sa() == nullptr) + { + SWSS_LOG_INFO("MACsec SA %s has been deleted.", port_sci_an.c_str()); + return task_success; + } + + auto result = task_success; + + uninstallCounter(port_sci_an, ctx.get_macsec_sc()->m_sa_ids[an]); + if (!deleteMACsecSA(ctx.get_macsec_sc()->m_sa_ids[an])) + { + SWSS_LOG_WARN("Cannot delete the MACsec SA %s.", port_sci_an.c_str()); + result = task_failed; + } + ctx.get_macsec_sc()->m_sa_ids.erase(an); + + // If this SA is the last SA + // change the ACL entry action from MACsec flow to packet action + if (ctx.get_macsec_sc()->m_sa_ids.empty()) + { + if (!setMACsecFlowActive(ctx.get_macsec_sc()->m_entry_id, ctx.get_macsec_sc()->m_flow_id, false)) + { + SWSS_LOG_WARN("Cannot change the ACL entry action from MACsec flow to packet action"); + result = task_failed; + } + } + + + if (direction == SAI_MACSEC_DIRECTION_EGRESS) + { + m_state_macsec_egress_sa.del(swss::join('|', port_name, sci, an)); + } + else + { + m_state_macsec_ingress_sa.del(swss::join('|', port_name, sci, an)); + } + + SWSS_LOG_NOTICE("MACsec SA %s is deleted.", port_sci_an.c_str()); + return result; +} + +bool MACsecOrch::createMACsecSA( + sai_object_id_t &sa_id, + sai_object_id_t switch_id, + sai_macsec_direction_t direction, + sai_object_id_t sc_id, + macsec_an_t an, + sai_macsec_sak_t sak, + sai_macsec_salt_t salt, + sai_uint32_t ssci, + sai_macsec_auth_key_t auth_key, + sai_uint64_t pn) +{ + SWSS_LOG_ENTER(); + + sai_attribute_t attr; + std::vector attrs; + + attr.id = SAI_MACSEC_SA_ATTR_MACSEC_DIRECTION; + attr.value.s32 = direction; + attrs.push_back(attr); + + attr.id = SAI_MACSEC_SA_ATTR_SC_ID; + attr.value.oid = sc_id; + attrs.push_back(attr); + + attr.id = SAI_MACSEC_SA_ATTR_AN; + attr.value.u8 = static_cast(an); + attrs.push_back(attr); + + attr.id = SAI_MACSEC_SA_ATTR_SAK; + std::copy(sak, sak + sizeof(attr.value.macsecsak), attr.value.macsecsak); + attrs.push_back(attr); + + // Valid when SAI_MACSEC_SC_ATTR_MACSEC_XPN64_ENABLE == true. + attr.id = SAI_MACSEC_SA_ATTR_SALT; + std::copy(salt, salt + sizeof(attr.value.macsecsalt), attr.value.macsecsalt); + attrs.push_back(attr); + + // Valid when SAI_MACSEC_SC_ATTR_MACSEC_XPN64_ENABLE == true. + attr.id = SAI_MACSEC_SA_ATTR_MACSEC_SSCI; + attr.value.u32 = ssci; + attrs.push_back(attr); + + attr.id = SAI_MACSEC_SA_ATTR_AUTH_KEY; + std::copy(auth_key, auth_key + sizeof(attr.value.macsecauthkey), attr.value.macsecauthkey); + attrs.push_back(attr); + + if (direction == SAI_MACSEC_DIRECTION_EGRESS) + { + attr.id = SAI_MACSEC_SA_ATTR_XPN; + attr.value.u64 = pn; + attrs.push_back(attr); + } + else + { + attr.id = SAI_MACSEC_SA_ATTR_MINIMUM_XPN; + attr.value.u64 = pn; + attrs.push_back(attr); + } + + if (sai_macsec_api->create_macsec_sa( + &sa_id, + switch_id, + static_cast(attrs.size()), + attrs.data()) != SAI_STATUS_SUCCESS) + { + return false; + } + return true; +} + +bool MACsecOrch::deleteMACsecSA(sai_object_id_t sa_id) +{ + SWSS_LOG_ENTER(); + if (sai_macsec_api->remove_macsec_sa( + sa_id) != SAI_STATUS_SUCCESS) + { + return false; + } + return true; +} + +void MACsecOrch::installCounter( + CounterType counter_type, + const std::string &obj_name, + sai_object_id_t obj_id, + const std::vector &stats) +{ + FieldValueTuple tuple(obj_name, sai_serialize_object_id(obj_id)); + vector fields; + fields.push_back(tuple); + m_macsec_counters_map.set("", fields); + + std::unordered_set counter_stats; + for (const auto &stat : stats) + { + counter_stats.emplace(stat); + } + m_macsec_flex_counter_manager.setCounterIdList(obj_id, counter_type, counter_stats); +} + +void MACsecOrch::uninstallCounter(const std::string &obj_name, sai_object_id_t obj_id) +{ + m_macsec_flex_counter_manager.clearCounterIdList(obj_id); + + m_counter_db.hdel(COUNTERS_MACSEC_NAME_MAP, obj_name); +} + +bool MACsecOrch::initMACsecACLTable( + MACsecACLTable &acl_table, + sai_object_id_t port_id, + sai_object_id_t switch_id, + sai_macsec_direction_t direction, + bool sci_in_sectag) +{ + SWSS_LOG_ENTER(); + + RecoverStack recover; + + if (!createMACsecACLTable(acl_table.m_table_id, switch_id, direction, sci_in_sectag)) + { + SWSS_LOG_WARN("Cannot create ACL table"); + return false; + } + recover.add_action([this, &acl_table]() { + this->deleteMACsecACLTable(acl_table.m_table_id); + acl_table.m_table_id = SAI_NULL_OBJECT_ID; + }); + + if (!createMACsecACLEAPOLEntry( + acl_table.m_eapol_packet_forward_entry_id, + acl_table.m_table_id, + switch_id)) + { + SWSS_LOG_WARN("Cannot create ACL EAPOL entry"); + return false; + } + recover.add_action([this, &acl_table]() { + this->deleteMACsecACLEntry(acl_table.m_eapol_packet_forward_entry_id); + acl_table.m_eapol_packet_forward_entry_id = SAI_NULL_OBJECT_ID; + }); + + if (!bindMACsecACLTabletoPort(acl_table.m_table_id, port_id, direction)) + { + SWSS_LOG_WARN("Cannot bind ACL table"); + return false; + } + recover.add_action([this, port_id, direction]() { this->unbindMACsecACLTable(port_id, direction); }); + + sai_uint32_t minimum_priority = 0; + if (!getAclPriority(switch_id, SAI_SWITCH_ATTR_ACL_ENTRY_MINIMUM_PRIORITY, minimum_priority)) + { + return false; + } + sai_uint32_t maximum_priority = 0; + if (!getAclPriority(switch_id, SAI_SWITCH_ATTR_ACL_ENTRY_MAXIMUM_PRIORITY, maximum_priority)) + { + return false; + } + sai_uint32_t priority = minimum_priority + 1; + while (priority < maximum_priority) + { + if (acl_table.m_available_acl_priorities.size() >= AVAILABLE_ACL_PRIORITIES_LIMITATION) + { + break; + } + acl_table.m_available_acl_priorities.insert(priority); + priority += 1; + } + recover.add_action([&acl_table]() { acl_table.m_available_acl_priorities.clear(); }); + + recover.clear(); + return true; +} + +bool MACsecOrch::deinitMACsecACLTable( + const MACsecACLTable &acl_table, + sai_object_id_t port_id, + sai_macsec_direction_t direction) +{ + bool result = true; + + if (!unbindMACsecACLTable(port_id, direction)) + { + SWSS_LOG_WARN("Cannot unbind ACL table"); + result &= false; + } + if (!deleteMACsecACLEntry(acl_table.m_eapol_packet_forward_entry_id)) + { + SWSS_LOG_WARN("Cannot delete ACL entry"); + result &= false; + } + if (!deleteMACsecACLTable(acl_table.m_table_id)) + { + SWSS_LOG_WARN("Cannot delete ACL table"); + result &= false; + } + + return result; +} + +bool MACsecOrch::createMACsecACLTable( + sai_object_id_t &table_id, + sai_object_id_t switch_id, + sai_macsec_direction_t direction, + bool sci_in_sectag) +{ + sai_attribute_t attr; + std::vector attrs; + + // Create ingress MACsec ACL table for port_id1 + attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; + if (direction == SAI_MACSEC_DIRECTION_EGRESS) + { + attr.value.s32 = SAI_ACL_STAGE_EGRESS_MACSEC; + } + else + { + attr.value.s32 = SAI_ACL_STAGE_INGRESS_MACSEC; + } + attrs.push_back(attr); + + attr.id = SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST; + vector bpoint_list = {SAI_ACL_BIND_POINT_TYPE_PORT}; + attr.value.s32list.count = static_cast(bpoint_list.size()); + attr.value.s32list.list = bpoint_list.data(); + attrs.push_back(attr); + + attr.id = SAI_ACL_TABLE_ATTR_FIELD_DST_MAC; + attr.value.booldata = true; + attrs.push_back(attr); + + attr.id = SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE; + attr.value.booldata = true; + attrs.push_back(attr); + + attr.id = SAI_ACL_TABLE_ATTR_FIELD_MACSEC_SCI; + attr.value.booldata = sci_in_sectag; + attrs.push_back(attr); + + if (sai_acl_api->create_acl_table( + &table_id, + switch_id, + static_cast(attrs.size()), + attrs.data()) != SAI_STATUS_SUCCESS) + { + return false; + } + return true; +} + +bool MACsecOrch::deleteMACsecACLTable(sai_object_id_t table_id) +{ + if (sai_acl_api->remove_acl_table(table_id) != SAI_STATUS_SUCCESS) + { + return false; + } + return true; +} + +bool MACsecOrch::bindMACsecACLTabletoPort( + sai_object_id_t table_id, + sai_object_id_t port_id, + sai_macsec_direction_t direction) +{ + sai_attribute_t attr; + + if (direction == SAI_MACSEC_DIRECTION_EGRESS) + { + attr.id = SAI_PORT_ATTR_EGRESS_MACSEC_ACL; + } + else + { + attr.id = SAI_PORT_ATTR_INGRESS_MACSEC_ACL; + } + attr.value.oid = table_id; + + if (sai_port_api->set_port_attribute( + port_id, + &attr) != SAI_STATUS_SUCCESS) + { + return false; + } + return true; +} + +bool MACsecOrch::unbindMACsecACLTable( + sai_object_id_t port_id, + sai_macsec_direction_t direction) +{ + sai_attribute_t attr; + + if (direction == SAI_MACSEC_DIRECTION_EGRESS) + { + attr.id = SAI_PORT_ATTR_EGRESS_MACSEC_ACL; + } + else + { + attr.id = SAI_PORT_ATTR_INGRESS_MACSEC_ACL; + } + attr.value.oid = SAI_NULL_OBJECT_ID; + + if (sai_port_api->set_port_attribute( + port_id, + &attr) != SAI_STATUS_SUCCESS) + { + return false; + } + return true; +} + +bool MACsecOrch::createMACsecACLEAPOLEntry( + sai_object_id_t &entry_id, + sai_object_id_t table_id, + sai_object_id_t switch_id) +{ + sai_attribute_t attr; + std::vector attrs; + + static const MacAddress nearest_non_tpmr_bridge("01:80:c2:00:00:03"); + static const MacAddress mac_address_mask("ff:ff:ff:ff:ff:ff"); + + attr.id = SAI_ACL_ENTRY_ATTR_TABLE_ID; + attr.value.oid = table_id; + attrs.push_back(attr); + attr.id = SAI_ACL_ENTRY_ATTR_PRIORITY; + if (!getAclPriority(switch_id, SAI_SWITCH_ATTR_ACL_ENTRY_MAXIMUM_PRIORITY, attr.value.u32)) + { + return false; + } + attrs.push_back(attr); + attr.id = SAI_ACL_ENTRY_ATTR_ADMIN_STATE; + attr.value.booldata = true; + attrs.push_back(attr); + attr.id = SAI_ACL_ENTRY_ATTR_FIELD_DST_MAC; + nearest_non_tpmr_bridge.getMac(attr.value.aclfield.data.mac); + mac_address_mask.getMac(attr.value.aclfield.mask.mac); + attr.value.aclfield.enable = true; + attrs.push_back(attr); + attr.id = SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE; + attr.value.aclfield.data.u16 = EAPOL_ETHER_TYPE; + attr.value.aclfield.mask.u16 = 0xFFFF; + attr.value.aclfield.enable = true; + attrs.push_back(attr); + attr.id = SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION; + attr.value.aclaction.parameter.s32 = SAI_PACKET_ACTION_FORWARD; + attr.value.aclaction.enable = true; + attrs.push_back(attr); + if (sai_acl_api->create_acl_entry( + &entry_id, + switch_id, + static_cast(attrs.size()), + attrs.data()) != SAI_STATUS_SUCCESS) + { + return false; + } + return true; +} + +bool MACsecOrch::createMACsecACLDataEntry( + sai_object_id_t &entry_id, + sai_object_id_t table_id, + sai_object_id_t switch_id, + bool sci_in_sectag, + sai_uint64_t sci, + sai_uint32_t priority) +{ + sai_attribute_t attr; + std::vector attrs; + + attr.id = SAI_ACL_ENTRY_ATTR_TABLE_ID; + attr.value.oid = 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_ENTRY_ATTR_ADMIN_STATE; + attr.value.booldata = true; + attrs.push_back(attr); + attr.id = SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION; + attr.value.aclaction.parameter.s32 = SAI_PACKET_ACTION_DROP; + attr.value.aclaction.enable = true; + attrs.push_back(attr); + if (sci_in_sectag) + { + attr.id = SAI_ACL_ENTRY_ATTR_FIELD_MACSEC_SCI; + attr.value.u64 = sci; + attrs.push_back(attr); + } + if (sai_acl_api->create_acl_entry( + &entry_id, + switch_id, + static_cast(attrs.size()), + attrs.data()) != SAI_STATUS_SUCCESS) + { + return false; + } + return true; +} + +bool MACsecOrch::setMACsecFlowActive(sai_object_id_t entry_id, sai_object_id_t flow_id, bool active) +{ + sai_attribute_t attr; + + attr.id = SAI_ACL_ENTRY_ATTR_ACTION_MACSEC_FLOW; + attr.value.aclaction.parameter.oid = flow_id; + attr.value.aclaction.enable = active; + + if (sai_acl_api->set_acl_entry_attribute( + entry_id, + &attr) != SAI_STATUS_SUCCESS) + { + return false; + } + + attr.id = SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION; + attr.value.aclaction.parameter.s32 = SAI_PACKET_ACTION_DROP; + attr.value.aclaction.enable = !active; + if (sai_acl_api->set_acl_entry_attribute( + entry_id, + &attr) != SAI_STATUS_SUCCESS) + { + return false; + } + + return true; +} + +bool MACsecOrch::deleteMACsecACLEntry(sai_object_id_t entry_id) +{ + if (sai_acl_api->remove_acl_entry( + entry_id) != SAI_STATUS_SUCCESS) + { + return false; + } + return true; +} + +bool MACsecOrch::getAclPriority(sai_object_id_t switch_id, sai_attr_id_t priority_id, sai_uint32_t &priority) const +{ + sai_attribute_t attr; + std::vector attrs; + + attr.id = priority_id; + attrs.push_back(attr); + if (sai_switch_api->get_switch_attribute( + switch_id, + static_cast(attrs.size()), + attrs.data()) != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Cannot fetch ACL Priority from switch"); + return false; + } + priority = attrs.front().value.u32; + + return true; +} diff --git a/orchagent/macsecorch.h b/orchagent/macsecorch.h new file mode 100644 index 000000000000..8e6b9e86754d --- /dev/null +++ b/orchagent/macsecorch.h @@ -0,0 +1,241 @@ +#ifndef SWSS_MACSECSORCH_H +#define SWSS_MACSECSORCH_H + +#include "orch.h" + +#include "portsorch.h" +#include "flex_counter_manager.h" + +#include +#include + +#include +#include +#include +#include + +using namespace swss; + +// AN is a 2 bit number, it can only be 0, 1, 2 or 3 +#define MAX_SA_NUMBER (3) + +using macsec_an_t = std::uint16_t; + +class MACsecOrchContext; + +class MACsecOrch : public Orch +{ + friend class MACsecOrchContext; +public: + MACsecOrch( + DBConnector *app_db, + DBConnector *state_db, + const std::vector &tables, + PortsOrch * port_orch); + ~MACsecOrch(); + +private: + void doTask(Consumer &consumer); + +public: + using TaskArgs = std::vector; + +private: + + task_process_status taskUpdateMACsecPort(const std::string & port_name, const TaskArgs & port_attr); + task_process_status taskDisableMACsecPort(const std::string & port_name, const TaskArgs & port_attr); + task_process_status taskUpdateEgressSC(const std::string & port_sci, const TaskArgs & sc_attr); + task_process_status taskDeleteEgressSC(const std::string & port_sci, const TaskArgs & sc_attr); + task_process_status taskUpdateIngressSC(const std::string & port_sci, const TaskArgs & sc_attr); + task_process_status taskDeleteIngressSC(const std::string & port_sci, const TaskArgs & sc_attr); + task_process_status taskUpdateEgressSA(const std::string & port_sci_an, const TaskArgs & sa_attr); + task_process_status taskDeleteEgressSA(const std::string & port_sci_an, const TaskArgs & sa_attr); + task_process_status taskUpdateIngressSA(const std::string & port_sci_an, const TaskArgs & sa_attr); + task_process_status taskDeleteIngressSA(const std::string & port_sci_an, const TaskArgs & sa_attr); + + PortsOrch * m_port_orch; + + Table m_state_macsec_port; + Table m_state_macsec_egress_sc; + Table m_state_macsec_ingress_sc; + Table m_state_macsec_egress_sa; + Table m_state_macsec_ingress_sa; + + DBConnector m_counter_db; + Table m_macsec_counters_map; + FlexCounterManager m_macsec_flex_counter_manager; + + struct MACsecACLTable + { + sai_object_id_t m_table_id; + sai_object_id_t m_eapol_packet_forward_entry_id; + std::set m_available_acl_priorities; + }; + struct MACsecSC + { + macsec_an_t m_encoding_an; + sai_object_id_t m_sc_id; + std::map m_sa_ids; + sai_object_id_t m_flow_id; + sai_object_id_t m_entry_id; + sai_uint32_t m_acl_priority; + }; + struct MACsecPort + { + sai_object_id_t m_egress_port_id; + sai_object_id_t m_ingress_port_id; + sai_object_id_t m_egress_flow_id; + sai_object_id_t m_ingress_flow_id; + std::map m_egress_scs; + std::map m_ingress_scs; + MACsecACLTable m_egress_acl_table; + MACsecACLTable m_ingress_acl_table; + sai_macsec_cipher_suite_t m_cipher_suite; + bool m_enable_encrypt; + bool m_sci_in_sectag; + bool m_enable; + }; + struct MACsecObject + { + sai_object_id_t m_egress_id; + sai_object_id_t m_ingress_id; + map > m_macsec_ports; + bool m_sci_in_ingress_macsec_acl; + }; + map m_macsec_objs; + map > m_macsec_ports; + + /* MACsec Object */ + bool initMACsecObject(sai_object_id_t switch_id); + bool deinitMACsecObject(sai_object_id_t switch_id); + + /* MACsec Port */ + bool createMACsecPort( + MACsecPort &macsec_port, + const std::string &port_name, + const TaskArgs & port_attr, + const MACsecObject &macsec_obj, + sai_object_id_t line_port_id, + sai_object_id_t switch_id); + bool createMACsecPort( + sai_object_id_t &macsec_port_id, + sai_object_id_t line_port_id, + sai_object_id_t switch_id, + sai_macsec_direction_t direction); + bool updateMACsecPort(MACsecPort &macsec_port, const TaskArgs & port_attr); + bool deleteMACsecPort( + const MACsecPort &macsec_port, + const std::string &port_name, + const MACsecObject &macsec_obj, + sai_object_id_t line_port_id); + bool deleteMACsecPort(sai_object_id_t macsec_port_id); + + /* MACsec Flow */ + bool createMACsecFlow( + sai_object_id_t &flow_id, + sai_object_id_t switch_id, + sai_macsec_direction_t direction); + bool deleteMACsecFlow(sai_object_id_t flow_id); + + /* MACsec SC */ + task_process_status updateMACsecSC( + const std::string &port_sci, + const TaskArgs &sc_attr, + sai_macsec_direction_t direction); + bool setEncodingAN( + MACsecSC &sc, + const TaskArgs &sc_attr, + sai_macsec_direction_t direction); + bool createMACsecSC( + MACsecPort &macsec_port, + const std::string &port_name, + const TaskArgs &sc_attr, + const MACsecObject &macsec_obj, + sai_uint64_t sci, + sai_object_id_t switch_id, + sai_macsec_direction_t direction); + bool createMACsecSC( + sai_object_id_t &sc_id, + sai_object_id_t switch_id, + sai_macsec_direction_t direction, + sai_object_id_t flow_id, + sai_uint64_t sci, + bool encryption_enable, + bool send_sci, + sai_macsec_cipher_suite_t cipher_suite); + task_process_status deleteMACsecSC( + const std::string &port_sci, + sai_macsec_direction_t direction); + bool deleteMACsecSC(sai_object_id_t sc_id); + + /* MACsec SA */ + task_process_status createMACsecSA( + const std::string &port_sci_an, + const TaskArgs &sa_attr, + sai_macsec_direction_t direction); + task_process_status deleteMACsecSA( + const std::string &port_sci_an, + sai_macsec_direction_t direction); + bool createMACsecSA( + sai_object_id_t &sa_id, + sai_object_id_t switch_id, + sai_macsec_direction_t direction, + sai_object_id_t sc_id, + macsec_an_t an, + sai_macsec_sak_t sak, + sai_macsec_salt_t salt, + sai_uint32_t ssci, + sai_macsec_auth_key_t auth_key, + sai_uint64_t pn); + bool deleteMACsecSA(sai_object_id_t sa_id); + + /* Counter */ + void installCounter( + CounterType counter_type, + const std::string &obj_name, + sai_object_id_t obj_id, + const std::vector &stats); + void uninstallCounter(const std::string &obj_name, sai_object_id_t obj_id); + + /* MACsec ACL */ + bool initMACsecACLTable( + MACsecACLTable &acl_table, + sai_object_id_t port_id, + sai_object_id_t switch_id, + sai_macsec_direction_t direction, + bool sci_in_sectag); + bool deinitMACsecACLTable( + const MACsecACLTable &acl_table, + sai_object_id_t port_id, + sai_macsec_direction_t direction); + bool createMACsecACLTable( + sai_object_id_t &table_id, + sai_object_id_t switch_id, + sai_macsec_direction_t direction, + bool sci_in_sectag); + bool deleteMACsecACLTable(sai_object_id_t table_id); + bool bindMACsecACLTabletoPort(sai_object_id_t table_id, sai_object_id_t port_id, sai_macsec_direction_t direction); + bool unbindMACsecACLTable(sai_object_id_t port_id, sai_macsec_direction_t direction); + bool createMACsecACLEAPOLEntry( + sai_object_id_t &entry_id, + sai_object_id_t table_id, + sai_object_id_t switch_id); + bool createMACsecACLDataEntry( + sai_object_id_t &entry_id, + sai_object_id_t table_id, + sai_object_id_t switch_id, + bool sci_in_sectag, + sai_uint64_t sci, + sai_uint32_t priority); + bool setMACsecFlowActive( + sai_object_id_t entry_id, + sai_object_id_t flow_id, + bool active); + bool deleteMACsecACLEntry(sai_object_id_t entry_id); + bool getAclPriority( + sai_object_id_t switch_id, + sai_attr_id_t priority_id, + sai_uint32_t &priority) const; +}; + +#endif // ORCHAGENT_MACSECORCH_H_ diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 5e55a8c2d596..638115f631fb 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -38,6 +38,7 @@ BufferOrch *gBufferOrch; SwitchOrch *gSwitchOrch; Directory gDirectory; NatOrch *gNatOrch; +MACsecOrch *gMacsecOrch; bool gIsNatSupported = false; @@ -261,6 +262,16 @@ bool OrchDaemon::init() MuxStateOrch *mux_st_orch = new MuxStateOrch(m_stateDb, STATE_HW_MUX_CABLE_TABLE_NAME); gDirectory.set(mux_st_orch); + vector macsec_app_tables = { + APP_MACSEC_PORT_TABLE_NAME, + APP_MACSEC_EGRESS_SC_TABLE_NAME, + APP_MACSEC_INGRESS_SC_TABLE_NAME, + APP_MACSEC_EGRESS_SA_TABLE_NAME, + APP_MACSEC_INGRESS_SA_TABLE_NAME, + }; + + gMacsecOrch = new MACsecOrch(m_applDb, m_stateDb, macsec_app_tables, gPortsOrch); + /* * 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. @@ -269,7 +280,7 @@ bool OrchDaemon::init() * 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, gPortsOrch, gBufferOrch, 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, gMacsecOrch}; bool initialize_dtel = false; if (platform == BFN_PLATFORM_SUBSTRING || platform == VS_PLATFORM_SUBSTRING) diff --git a/orchagent/orchdaemon.h b/orchagent/orchdaemon.h index b48ffc30a6de..1d34574284fc 100644 --- a/orchagent/orchdaemon.h +++ b/orchagent/orchdaemon.h @@ -32,6 +32,7 @@ #include "directory.h" #include "natorch.h" #include "muxorch.h" +#include "macsecorch.h" using namespace swss; diff --git a/orchagent/saihelper.cpp b/orchagent/saihelper.cpp index dbd7683825bb..eac1414b0b5a 100644 --- a/orchagent/saihelper.cpp +++ b/orchagent/saihelper.cpp @@ -60,6 +60,7 @@ sai_samplepacket_api_t* sai_samplepacket_api; sai_debug_counter_api_t* sai_debug_counter_api; sai_nat_api_t* sai_nat_api; sai_system_port_api_t* sai_system_port_api; +sai_macsec_api_t* sai_macsec_api; extern sai_object_id_t gSwitchId; extern bool gSairedisRecord; @@ -179,6 +180,7 @@ void initSaiApi() sai_api_query(SAI_API_DEBUG_COUNTER, (void **)&sai_debug_counter_api); sai_api_query(SAI_API_NAT, (void **)&sai_nat_api); sai_api_query(SAI_API_SYSTEM_PORT, (void **)&sai_system_port_api); + sai_api_query(SAI_API_MACSEC, (void **)&sai_macsec_api); sai_log_set(SAI_API_SWITCH, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_BRIDGE, SAI_LOG_LEVEL_NOTICE); @@ -208,6 +210,7 @@ void initSaiApi() sai_log_set(SAI_API_DEBUG_COUNTER, SAI_LOG_LEVEL_NOTICE); sai_log_set((sai_api_t)SAI_API_NAT, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_SYSTEM_PORT, SAI_LOG_LEVEL_NOTICE); + sai_log_set(SAI_API_MACSEC, SAI_LOG_LEVEL_NOTICE); } void initSaiRedis(const string &record_location, const std::string &record_filename) diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 252b7d9c889a..050cd5274c76 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -65,7 +65,8 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/sfloworch.cpp \ $(top_srcdir)/orchagent/debugcounterorch.cpp \ $(top_srcdir)/orchagent/natorch.cpp \ - $(top_srcdir)/orchagent/muxorch.cpp + $(top_srcdir)/orchagent/muxorch.cpp \ + $(top_srcdir)/orchagent/macsecorch.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_macsec.py b/tests/test_macsec.py new file mode 100644 index 000000000000..318ab0bc89e8 --- /dev/null +++ b/tests/test_macsec.py @@ -0,0 +1,700 @@ +from swsscommon import swsscommon +import conftest + +import sys +import pytest +import functools +import typing +import re +import time + + +def to_string(value): + if isinstance(value, bool): + return "true" if value else "false" + return str(value) + + +class Table(object): + def __init__(self, database: conftest.DVSDatabase, table_name: str): + self.db = database + self.table_name = table_name + + def convert_key(self, key: str): + return key + + def __setitem__(self, key: str, pairs: dict): + pairs_str = {} + for k, v in pairs.items(): + pairs_str[to_string(k)] = to_string(v) + key = self.convert_key(key) + if self.__getitem__(key) is None: + self.db.create_entry(self.table_name, key, pairs_str) + else: + self.db.update_entry(self.table_name, key, pairs_str) + + def __getitem__(self, key: str): + key = self.convert_key(key) + return self.db.get_entry(self.table_name, key) + + def __delitem__(self, key: str): + key = self.convert_key(key) + self.db.delete_entry(self.table_name, key) + + def wait(self, key: str): + key = self.convert_key(key) + # return True + return self.db.wait_for_entry(self.table_name, key) + + def wait_delete(self, key: str): + key = self.convert_key(key) + # return True + return self.db.wait_for_deleted_entry(self.table_name, key) + + +class ProduceStateTable(object): + def __init__(self, database: conftest.DVSDatabase, table_name: str): + self.table = swsscommon.ProducerStateTable( + database.db_connection, + table_name) + + def __setitem__(self, key: str, pairs: typing.Union[dict, list, tuple]): + pairs_str = [] + if isinstance(pairs, dict): + pairs = pairs.items() + for k, v in pairs: + pairs_str.append((to_string(k), to_string(v))) + self.table.set(key, pairs_str) + + def __delitem__(self, key: str): + self.table.delete(key) + + +class AppDBTable(ProduceStateTable): + SEPARATOR = ":" + + def __init__(self, dvs: conftest.DockerVirtualSwitch, table_name: str): + super(AppDBTable, self).__init__(dvs.get_app_db(), table_name) + + +class StateDBTable(Table): + SEPARATOR = "|" + + def __init__(self, dvs: conftest.DockerVirtualSwitch, table_name: str): + super(StateDBTable, self).__init__(dvs.get_state_db(), table_name) + + def convert_key(self, key: str): + return key.translate( + str.maketrans( + AppDBTable.SEPARATOR, + StateDBTable.SEPARATOR)) + + +def gen_sci(macsec_system_identifier: str, macsec_port_identifier: int) -> str: + macsec_system_identifier = macsec_system_identifier.translate( + str.maketrans("", "", ":.-")) + sci = "{}{}".format( + macsec_system_identifier, + str(macsec_port_identifier).zfill(4)) + sci = int(sci, 16) + if sys.byteorder == "little": + sci = int.from_bytes(sci.to_bytes(8, 'big'), 'little', signed=False) + return str(sci) + + +def gen_sc_key( + seperator: str, + port_name: str, + macsec_system_identifier: str, + macsec_port_identifier: int) -> str: + sci = gen_sci(macsec_system_identifier, macsec_port_identifier) + key = "{}{}{}".format( + port_name, + seperator, + sci) + return key + + +def gen_sa_key( + seperator: str, + port_name: str, + macsec_system_identifier: str, + macsec_port_identifier: int, + an: int): + sc_key = gen_sc_key( + seperator, + port_name, + macsec_system_identifier, + macsec_port_identifier) + key = "{}{}{}".format(sc_key, seperator, an) + return key + + +def macsec_sc(seperator: str = AppDBTable.SEPARATOR): + def inner(func: typing.Callable) -> typing.Callable: + @functools.wraps(func) + def wrap_func( + self, + port_name: str, + macsec_system_identifier: str, + macsec_port_identifier: int, + *args, + **kwargs) -> typing.Any: + key = gen_sc_key( + seperator, + port_name, + macsec_system_identifier, + macsec_port_identifier) + return func(self, key, *args, **kwargs) + return wrap_func + return inner + + +def macsec_sa(seperator: str = AppDBTable.SEPARATOR): + def inner(func: typing.Callable) -> typing.Callable: + @functools.wraps(func) + def wrap_func( + self, + port_name: str, + macsec_system_identifier: str, + macsec_port_identifier: int, + an: int, + *args, + **kwargs) -> typing.Any: + key = gen_sa_key( + seperator, + port_name, + macsec_system_identifier, + macsec_port_identifier, + an) + return func(self, key, *args, **kwargs) + return wrap_func + return inner + + +class WPASupplicantMock(object): + def __init__(self, dvs: conftest.DockerVirtualSwitch): + self.dvs = dvs + self.app_port_table = AppDBTable( + self.dvs, swsscommon.APP_MACSEC_PORT_TABLE_NAME) + self.app_receive_sc_table = AppDBTable( + self.dvs, swsscommon.APP_MACSEC_INGRESS_SC_TABLE_NAME) + self.app_transmit_sc_table = AppDBTable( + self.dvs, swsscommon.APP_MACSEC_EGRESS_SC_TABLE_NAME) + self.app_receive_sa_table = AppDBTable( + self.dvs, swsscommon.APP_MACSEC_INGRESS_SA_TABLE_NAME) + self.app_transmit_sa_table = AppDBTable( + self.dvs, swsscommon.APP_MACSEC_EGRESS_SA_TABLE_NAME) + self.state_port_table = StateDBTable( + self.dvs, swsscommon.STATE_MACSEC_PORT_TABLE_NAME) + self.state_receive_sc_table = StateDBTable( + self.dvs, swsscommon.STATE_MACSEC_INGRESS_SC_TABLE_NAME) + self.state_transmit_sc_table = StateDBTable( + self.dvs, swsscommon.STATE_MACSEC_EGRESS_SC_TABLE_NAME) + self.state_receive_sa_table = StateDBTable( + self.dvs, swsscommon.STATE_MACSEC_INGRESS_SA_TABLE_NAME) + self.state_transmit_sa_table = StateDBTable( + self.dvs, swsscommon.STATE_MACSEC_EGRESS_SA_TABLE_NAME) + + def init_macsec_port(self, port_name: str): + self.app_port_table[port_name] = { + "enable": False, + "cipher_suite": "GCM-AES-128", + } + self.state_port_table.wait(port_name) + + def deinit_macsec_port(self, port_name: str): + del self.app_port_table[port_name] + self.state_port_table.wait_delete(port_name) + + def config_macsec_port( + self, + port_name: str, + config: typing.Dict[str, typing.Any]): + self.app_port_table[port_name] = config + + def set_macsec_control(self, port_name: str, enable: bool): + self.app_port_table[port_name] = {"enable": True} + + @macsec_sc() + def create_receive_sc(self, sci: str, ssci: int): + self.app_receive_sc_table[sci] = {"ssci": ssci} + self.state_receive_sc_table.wait(sci) + + @macsec_sc() + def delete_receive_sc(self, sci: str): + del self.app_receive_sc_table[sci] + self.state_receive_sc_table.wait_delete(sci) + + @macsec_sc() + def create_transmit_sc(self, sci: str, ssci: int): + self.app_transmit_sc_table[sci] = {"sci": sci, "encoding_an": 0} + self.state_transmit_sc_table.wait(sci) + + @macsec_sc() + def delete_transmit_sc(self, sci: str): + del self.app_transmit_sc_table[sci] + self.state_transmit_sc_table.wait_delete(sci) + + def check_valid_sa_parameter( + self, + sak: str, + auth_key: str, + lowest_acceptable_pn: int, + salt: str) -> bool: + # Check SAK is hex string + int(sak, 16) + assert( + len(sak) == 32 or len(sak) == 64, + "Wrong length {} sak {}".format( + len(sak), + sak)) + # Check auth_key is valid + int(auth_key, 16) + assert( + len(auth_key) == 32, + "Wrong length {} auth_key {}".format( + len(auth_key), + auth_key)) + # Check lowest acceptable packet number is valid + assert( + lowest_acceptable_pn > 0, + "Wrong packet number {}".format(lowest_acceptable_pn)) + return True + + @macsec_sa() + def create_receive_sa( + self, + sai: str, + sak: str, + auth_key: str, + lowest_acceptable_pn: int, + salt: str): + assert( + self.check_valid_sa_parameter( + sak, + auth_key, + lowest_acceptable_pn, + salt), + "Wrong parameter to MACsec receive SA") + self.app_receive_sa_table[sai] = { + "active": False, "sak": sak, "auth_key": auth_key, + "lowest_acceptable_pn": lowest_acceptable_pn, "salt": salt} + + @macsec_sa() + def delete_receive_sa(self, sai: str): + del self.app_receive_sa_table[sai] + self.state_receive_sa_table.wait_delete(sai) + + @macsec_sa() + def set_enable_receive_sa(self, sai: str, enable: bool): + self.app_receive_sa_table[sai] = {"active": enable} + if enable: + self.state_receive_sa_table.wait(sai) + + @macsec_sa() + def create_transmit_sa( + self, + sai: str, + sak: str, + auth_key: str, + init_pn: int, + salt: str): + assert( + self.check_valid_sa_parameter( + sak, + auth_key, + init_pn, + salt), + "Wrong parameter to MACsec receive SA") + self.app_transmit_sa_table[sai] = { + "sak": sak, "auth_key": auth_key, + "next_pn": init_pn, "salt": salt} + + @macsec_sa() + def delete_transmit_sa(self, sai: str): + del self.app_transmit_sa_table[sai] + self.state_transmit_sa_table.wait_delete(sai) + + @macsec_sc() + def set_enable_transmit_sa(self, sci: str, an: int, enable: bool): + if enable: + self.app_transmit_sc_table[sci] = {"encoding_an": an} + assert( + self.state_transmit_sa_table.wait( + "{}{}{}".format( + sci, + StateDBTable.SEPARATOR, + an))) + + +class MACsecInspector(object): + def __init__(self, dvs: conftest.DockerVirtualSwitch): + self.dvs = dvs + + def __load_macsec_info(self, port_name: str) -> (bool, str): + return self.dvs.runcmd("ip macsec show {}".format(port_name)) + + def get_macsec_port(self, port_name: str) -> str: + exitcode, info = self.__load_macsec_info(port_name) + if exitcode != 0 or not info: + return "" + print(info) + return info + + def get_macsec_sc( + self, + port_name: str, + macsec_system_identifier: str, + macsec_port_identifier: int) -> str: + info = self.get_macsec_port(port_name) + if not info: + return "" + macsec_system_identifier = macsec_system_identifier.translate( + str.maketrans("", "", ":.-")) + sci = "{}{}".format( + macsec_system_identifier, + str(macsec_port_identifier).zfill(4)) + sc_pattern = r"(TXSC|RXSC):\s*{}[ \w,]+\n?(?:\s*\d:[,\w ]+\n?)*".format( + sci) + info = re.search(sc_pattern, info, re.IGNORECASE) + if not info: + return "" + print(info.group(0)) + return info.group(0) + + def get_macsec_sa( + self, + port_name: str, + macsec_system_identifier: str, + macsec_port_identifier: str, + an: int) -> str: + info = self.get_macsec_sc( + port_name, + macsec_system_identifier, + macsec_port_identifier) + if not info: + return "" + sa_pattern = r"\s*{}:\s*PN\s*\d+[,\w ]+\n?".format(an) + info = re.search(sa_pattern, info, re.IGNORECASE) + if not info: + return "" + print(info.group(0)) + return info.group(0) + + +class TestMACsec(object): + def init_macsec( + self, + wpa: WPASupplicantMock, + port_name: str, + local_mac_address: str, + macsec_port_identifier: int, + ssci: int): + wpa.init_macsec_port(port_name) + wpa.config_macsec_port(port_name, {"enable_protect": True}) + wpa.config_macsec_port(port_name, {"enable_encrypt": True}) + wpa.config_macsec_port( + port_name, + { + "enable_replay_protect": True, + "replay_window": 0 + }) + wpa.set_macsec_control(port_name, False) + wpa.create_transmit_sc( + port_name, + local_mac_address, + macsec_port_identifier, + ssci) + + def establish_macsec( + self, + wpa: WPASupplicantMock, + port_name: str, + local_mac_address: str, + peer_mac_address: str, + macsec_port_identifier: int, + an: int, + sak: str, + packet_number: int, + auth_key: str, + ssci: int, + salt: str): + wpa.create_receive_sc( + port_name, + peer_mac_address, + macsec_port_identifier, + ssci) + wpa.create_receive_sa( + port_name, + peer_mac_address, + macsec_port_identifier, + an, + sak, + auth_key, + packet_number, + salt) + wpa.create_transmit_sa( + port_name, + local_mac_address, + macsec_port_identifier, + an, + sak, + auth_key, + packet_number, + salt) + wpa.set_enable_receive_sa( + port_name, + peer_mac_address, + macsec_port_identifier, + an, + True) + wpa.set_macsec_control(port_name, True) + wpa.set_enable_transmit_sa( + port_name, + local_mac_address, + macsec_port_identifier, + an, + True) + + def rekey_macsec( + self, + wpa: WPASupplicantMock, + port_name: str, + local_mac_address: str, + peer_mac_address: str, + macsec_port_identifier: int, + an: int, + last_an: int, + sak: str, + packet_number: int, + auth_key: str, + salt: str): + wpa.create_receive_sa( + port_name, + peer_mac_address, + macsec_port_identifier, + an, + sak, + auth_key, + packet_number, + salt) + wpa.create_transmit_sa( + port_name, + local_mac_address, + macsec_port_identifier, + an, + sak, + auth_key, + packet_number, + salt) + wpa.set_enable_receive_sa( + port_name, + peer_mac_address, + macsec_port_identifier, + an, + True) + wpa.set_macsec_control(port_name, True) + wpa.set_enable_transmit_sa( + port_name, + local_mac_address, + macsec_port_identifier, + an, + True) + wpa.set_enable_transmit_sa( + port_name, + local_mac_address, + macsec_port_identifier, + last_an, + False) + wpa.delete_transmit_sa( + port_name, + local_mac_address, + macsec_port_identifier, + last_an) + wpa.set_enable_receive_sa( + port_name, + peer_mac_address, + macsec_port_identifier, + last_an, + False) + wpa.delete_receive_sa( + port_name, + peer_mac_address, + macsec_port_identifier, + last_an) + + def deinit_macsec( + self, + wpa: WPASupplicantMock, + inspector: MACsecInspector, + port_name: str, + macsec_port: str, + local_mac_address: str, + peer_mac_address: str, + macsec_port_identifier: int, + last_an: int): + wpa.set_enable_receive_sa( + port_name, + peer_mac_address, + macsec_port_identifier, + last_an, + False) + wpa.delete_receive_sa( + port_name, + peer_mac_address, + macsec_port_identifier, + last_an) + assert( + not inspector.get_macsec_sa( + macsec_port, + peer_mac_address, + macsec_port_identifier, + last_an)) + wpa.delete_receive_sc( + port_name, + peer_mac_address, + macsec_port_identifier) + assert( + not inspector.get_macsec_sc( + macsec_port, + peer_mac_address, + macsec_port_identifier)) + wpa.set_enable_transmit_sa( + port_name, + local_mac_address, + macsec_port_identifier, + last_an, + False) + wpa.delete_transmit_sa( + port_name, + local_mac_address, + macsec_port_identifier, + last_an) + assert( + not inspector.get_macsec_sa( + macsec_port, + local_mac_address, + macsec_port_identifier, + last_an)) + wpa.delete_transmit_sc( + port_name, + local_mac_address, + macsec_port_identifier) + assert( + not inspector.get_macsec_sc( + macsec_port, + local_mac_address, + macsec_port_identifier)) + wpa.deinit_macsec_port(port_name) + + def test_macsec_term_orch(self, dvs: conftest.DockerVirtualSwitch, testlog): + port_name = "Ethernet0" + local_mac_address = "00-15-5D-78-FF-C1" + peer_mac_address = "00-15-5D-78-FF-C2" + macsec_port_identifier = 1 + macsec_port = "macsec_eth1" + sak = "0" * 32 + auth_key = "0" * 32 + packet_number = 1 + ssci = 1 + salt = "0" * 24 + + wpa = WPASupplicantMock(dvs) + inspector = MACsecInspector(dvs) + + self.init_macsec( + wpa, + port_name, + local_mac_address, + macsec_port_identifier, + ssci) + self.establish_macsec( + wpa, + port_name, + local_mac_address, + peer_mac_address, + macsec_port_identifier, + 0, + sak, + packet_number, + auth_key, + ssci, + salt) + assert(inspector.get_macsec_port(macsec_port)) + assert( + inspector.get_macsec_sc( + macsec_port, + local_mac_address, + macsec_port_identifier)) + assert( + inspector.get_macsec_sc( + macsec_port, + peer_mac_address, + macsec_port_identifier)) + assert( + inspector.get_macsec_sa( + macsec_port, + local_mac_address, + macsec_port_identifier, + 0)) + assert( + inspector.get_macsec_sa( + macsec_port, + peer_mac_address, + macsec_port_identifier, + 0)) + self.rekey_macsec( + wpa, + port_name, + local_mac_address, + peer_mac_address, + macsec_port_identifier, + 1, + 0, + sak, + packet_number, + auth_key, + salt) + assert( + inspector.get_macsec_sa( + macsec_port, + local_mac_address, + macsec_port_identifier, + 1)) + assert( + inspector.get_macsec_sa( + macsec_port, + peer_mac_address, + macsec_port_identifier, + 1)) + assert( + not inspector.get_macsec_sa( + macsec_port, + local_mac_address, + macsec_port_identifier, + 0)) + assert( + not inspector.get_macsec_sa( + macsec_port, + peer_mac_address, + macsec_port_identifier, + 0)) + # Exit MACsec port + self.deinit_macsec( + wpa, + inspector, + port_name, + macsec_port, + local_mac_address, + peer_mac_address, + macsec_port_identifier, + 1) + assert(not inspector.get_macsec_port(macsec_port)) + + +# Add Dummy always-pass test at end as workaroud +# for issue when Flaky fail on final test it invokes module tear-down +# before retrying +def test_nonflaky_dummy(): + pass