From 832815e815d8e14e711b40e0a9851d3d1a4a6c49 Mon Sep 17 00:00:00 2001 From: Ze Gan Date: Thu, 28 Jan 2021 18:46:38 -0800 Subject: [PATCH] [orchagent]: Add MACsec Orchagent (#1474) Add MACsec orchagent for MACsec feature. The MACsecOrch is introduced in the Orchagent to handle configuration requests. It monitors MACsec related tables in APP DB and convert those messages to SAI commands to manage the MACsec object. The main functions are defined in class MACsecOrch as follow ``` 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); ``` The HLD of MACsec orchagent is at [MACsec HLD](https://github.com/Azure/SONiC/blob/master/doc/macsec/MACsec_hld.md#344-macsec-orch) Signed-off-by: Ze Gan --- orchagent/Makefile.am | 3 +- .../flex_counter/flex_counter_manager.cpp | 11 +- orchagent/flex_counter/flex_counter_manager.h | 3 +- orchagent/macsecorch.cpp | 2213 +++++++++++++++++ orchagent/macsecorch.h | 241 ++ orchagent/orchdaemon.cpp | 13 +- orchagent/orchdaemon.h | 1 + orchagent/saihelper.cpp | 3 + tests/mock_tests/Makefile.am | 3 +- tests/test_macsec.py | 700 ++++++ 10 files changed, 3183 insertions(+), 8 deletions(-) create mode 100644 orchagent/macsecorch.cpp create mode 100644 orchagent/macsecorch.h create mode 100644 tests/test_macsec.py 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