diff --git a/cfgmgr/Makefile.am b/cfgmgr/Makefile.am index 096c2224189..0f71ad7b0bb 100644 --- a/cfgmgr/Makefile.am +++ b/cfgmgr/Makefile.am @@ -96,7 +96,7 @@ tunnelmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CF tunnelmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) tunnelmgrd_LDADD = $(LDFLAGS_ASAN) $(COMMON_LIBS) $(SAIMETA_LIBS) -macsecmgrd_SOURCES = macsecmgrd.cpp macsecmgr.cpp $(COMMON_ORCH_SOURCE) shellcmd.h +macsecmgrd_SOURCES = macsecmgrd.cpp macsecmgr.cpp $(COMMON_ORCH_SOURCE) shellcmd.h $(top_srcdir)/orchagent/macsecpost.cpp macsecmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) macsecmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) macsecmgrd_LDADD = $(LDFLAGS_ASAN) $(COMMON_LIBS) $(SAIMETA_LIBS) diff --git a/cfgmgr/macsecmgrd.cpp b/cfgmgr/macsecmgrd.cpp index 263c5b43959..2a390fbee90 100644 --- a/cfgmgr/macsecmgrd.cpp +++ b/cfgmgr/macsecmgrd.cpp @@ -17,6 +17,7 @@ #include #include "macsecmgr.h" +#include "macsecpost.h" using namespace std; using namespace swss; @@ -76,9 +77,28 @@ int main(int argc, char **argv) s.addSelectables(o->getSelectables()); } + bool isPostStateReady = false; + SWSS_LOG_NOTICE("starting main loop"); while (!received_sigterm) { + /* Don't process any config until POST state is ready */ + if (!isPostStateReady) + { + std::string state = getMacsecPostState(&stateDb); + if (state == "pass" || state == "disabled") + { + SWSS_LOG_NOTICE("FIPS MACSec POST ready: state %s", state.c_str()); + isPostStateReady = true; + } + else + { + /* Yield before retry */ + sleep(1); + continue; + } + } + Selectable *sel; int ret; diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index 7fb77a707d6..663ec67f65a 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -132,6 +132,7 @@ orchagent_SOURCES = \ twamporch.cpp \ stporch.cpp \ nexthopkey.cpp \ + macsecpost.cpp \ high_frequency_telemetry/hftelorch.cpp \ high_frequency_telemetry/hftelprofile.cpp \ high_frequency_telemetry/counternameupdater.cpp \ diff --git a/orchagent/macsecorch.cpp b/orchagent/macsecorch.cpp index 1efd16a0237..dbce7eb1a8a 100644 --- a/orchagent/macsecorch.cpp +++ b/orchagent/macsecorch.cpp @@ -1,4 +1,6 @@ #include "macsecorch.h" +#include "macsecpost.h" +#include "notifier.h" #include #include @@ -599,6 +601,7 @@ MACsecOrch::MACsecOrch( const std::vector &tables, PortsOrch *port_orch) : Orch(app_db, tables), m_port_orch(port_orch), + m_state_db(state_db), 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), @@ -649,6 +652,53 @@ MACsecOrch::MACsecOrch( saiAclFieldSciMatchSupported = false; } } + + // Add handler for POST completion callback/notification. + string post_state = getMacsecPostState(m_state_db); + if (post_state == "switch-level-post-in-progress" || + post_state == "macsec-level-post-in-progress" ) + { + m_notificationsDb = make_shared("ASIC_DB", 0); + m_postCompletionNotificationConsumer = new swss::NotificationConsumer(m_notificationsDb.get(), "NOTIFICATIONS"); + auto postCompletionNotificatier = new Notifier(m_postCompletionNotificationConsumer, this, "POST_COMPLETION__NOTIFICATIONS"); + Orch::addExecutor(postCompletionNotificatier); + } + + if (post_state == "switch-level-post-in-progress") + { + // POST was already enabled in switch init. The completion notification may have already been sent + // before MACSecOrch is initialized. So query if POST is completed or not. + sai_attribute_t attr; + attr.id = SAI_SWITCH_ATTR_MACSEC_POST_STATUS; + if (sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr) == SAI_STATUS_SUCCESS) + { + if (attr.value.s32 == SAI_SWITCH_MACSEC_POST_STATUS_PASS) + { + setMacsecPostState(m_state_db, "pass"); + SWSS_LOG_NOTICE("Switch MACSec POST passed"); + } + else if (attr.value.s32 == SAI_SWITCH_MACSEC_POST_STATUS_FAIL) + { + setMacsecPostState(m_state_db, "fail"); + SWSS_LOG_ERROR("Switch MACSec POST failed: oid %" PRIu64, gSwitchId); + } + else + { + SWSS_LOG_NOTICE("Switch MACSec POST status: %d", attr.value.s32); + } + } + else + { + SWSS_LOG_ERROR("Failed to get MACSec POST status"); + } + } + else if (post_state == "macsec-level-post-in-progress") + { + // POST is only supported in MACSec init. Create MACSec and enable POST. + m_enable_post = true; + initMACsecObject(gSwitchId); + SWSS_LOG_NOTICE("Init MACSec objects and enable POST"); + } } MACsecOrch::~MACsecOrch() @@ -661,6 +711,127 @@ MACsecOrch::~MACsecOrch() } } +void MACsecOrch::doTask(NotificationConsumer &consumer) +{ + SWSS_LOG_ENTER(); + + if (&consumer != m_postCompletionNotificationConsumer) + { + return; + } + + std::deque entries; + consumer.pops(entries); + for (auto& entry : entries) + { + handleNotification(consumer, entry); + } +} + +void MACsecOrch::handleNotification(NotificationConsumer &consumer, KeyOpFieldsValuesTuple& entry) +{ + SWSS_LOG_ENTER(); + + if (&consumer != m_postCompletionNotificationConsumer) + { + return; + } + + auto op = kfvOp(entry); + auto data = kfvKey(entry); + SWSS_LOG_NOTICE("Received SAI notification: op %s, data %s", op.c_str(), data.c_str()); + + if (op == "switch_macsec_post_status") + { + sai_object_id_t switch_id; + sai_switch_macsec_post_status_t switch_macsec_post_status; + sai_deserialize_switch_macsec_post_status_ntf(data, switch_id, switch_macsec_post_status); + + string post_state = getMacsecPostState(m_state_db); + if (post_state == "switch-level-post-in-progress") + { + // MACSec POST was enabled in switch init. SAI enables POST in all HW MACSec engines. + // The returned POST status is the aggregated result for all HW MACSec engines. + + if (switch_macsec_post_status == SAI_SWITCH_MACSEC_POST_STATUS_PASS) + { + setMacsecPostState(m_state_db, "pass"); + SWSS_LOG_NOTICE("Switch MACSec POST passed"); + } + else if (switch_macsec_post_status == SAI_SWITCH_MACSEC_POST_STATUS_FAIL) + { + setMacsecPostState(m_state_db, "fail"); + SWSS_LOG_ERROR("Switch MACSec POST failed"); + } + } + else if (post_state == "macsec-level-post-in-progress") + { + SWSS_LOG_ERROR("POST enabled in MACSec init, but got notification from switch init"); + } + } + + if (op == "macsec_post_status") + { + sai_object_id_t macsec_id; + sai_macsec_post_status_t macsec_post_status; + sai_deserialize_macsec_post_status_ntf(data, macsec_id, macsec_post_status); + + if (m_enable_post) + { + // MACSec POST was enabled in MACSec object init. Since two MACSec objects were created + // (one for each direction), two POST status must be returend from SAI. POST is considered + // pass only if POST passes in both MACSec objects. + + string direction = "unknown"; + auto macsec_obj = m_macsec_objs.find(gSwitchId); + if (macsec_obj->second.m_ingress_id == macsec_id) + { + direction = "ingress"; + } + else if (macsec_obj->second.m_egress_id == macsec_id) + { + direction = "egress"; + } + + if (macsec_post_status == SAI_MACSEC_POST_STATUS_PASS) + { + if (direction == "ingress") + { + SWSS_LOG_NOTICE("Ingress MACSec POST passed"); + macsec_obj->second.m_ingress_post_passed = true; + } + else if (direction == "egress") + { + SWSS_LOG_NOTICE("Egress MACSec POST passed"); + macsec_obj->second.m_egress_post_passed = true; + } + + // Check if POST passed on both MACSec objects. + if (macsec_obj->second.m_ingress_post_passed && macsec_obj->second.m_egress_post_passed) + { + setMacsecPostState(m_state_db, "pass"); + SWSS_LOG_NOTICE("Ingress and egress MACSec POST passed"); + } + } + else if(macsec_post_status == SAI_MACSEC_POST_STATUS_FAIL) + { + if (direction == "ingress") + { + SWSS_LOG_ERROR("Ingress MACSec POST failed"); + } + else if (direction == "egress") + { + SWSS_LOG_ERROR("Egress MACSec POST failed"); + } + + // Consider POST failed since it failed on one MACSec object. + setMacsecPostState(m_state_db, "fail"); + SWSS_LOG_ERROR("MACSec POST failed"); + } + } + } +} + void MACsecOrch::doTask(Consumer &consumer) { SWSS_LOG_ENTER(); @@ -1044,6 +1215,13 @@ bool MACsecOrch::initMACsecObject(sai_object_id_t switch_id) attr.value.booldata = true; attrs.push_back(attr); + if (m_enable_post) + { + attr.id = SAI_MACSEC_ATTR_ENABLE_POST; + attr.value.booldata = true; + attrs.push_back(attr); + } + sai_status_t status = sai_macsec_api->create_macsec( &macsec_obj.first->second.m_egress_id, switch_id, @@ -1069,6 +1247,13 @@ bool MACsecOrch::initMACsecObject(sai_object_id_t switch_id) attr.value.booldata = true; attrs.push_back(attr); + if (m_enable_post) + { + attr.id = SAI_MACSEC_ATTR_ENABLE_POST; + attr.value.booldata = true; + attrs.push_back(attr); + } + status = sai_macsec_api->create_macsec( &macsec_obj.first->second.m_ingress_id, switch_id, diff --git a/orchagent/macsecorch.h b/orchagent/macsecorch.h index 9c6e2be6366..6673f701018 100644 --- a/orchagent/macsecorch.h +++ b/orchagent/macsecorch.h @@ -40,6 +40,8 @@ class MACsecOrch : public Orch private: void doTask(Consumer &consumer); + void doTask(NotificationConsumer &consumer); + void handleNotification(NotificationConsumer &consumer, KeyOpFieldsValuesTuple& entry); public: using TaskArgs = std::vector; @@ -57,6 +59,11 @@ class MACsecOrch : public Orch 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); + DBConnector * m_state_db; + shared_ptr m_notificationsDb; + NotificationConsumer* m_postCompletionNotificationConsumer; + bool m_enable_post; + PortsOrch * m_port_orch; Table m_state_macsec_port; @@ -117,6 +124,8 @@ class MACsecOrch : public Orch map > m_macsec_ports; bool m_sci_in_ingress_macsec_acl; sai_uint8_t m_max_sa_per_sc; + bool m_egress_post_passed; + bool m_ingress_post_passed; }; map m_macsec_objs; map > m_macsec_ports; diff --git a/orchagent/macsecpost.cpp b/orchagent/macsecpost.cpp new file mode 100644 index 00000000000..0ac72735134 --- /dev/null +++ b/orchagent/macsecpost.cpp @@ -0,0 +1,42 @@ +#include "dbconnector.h" +#include "macsecpost.h" +#include "redisutility.h" +#include "schema.h" +#include "table.h" + +namespace swss { + +void setMacsecPostState(DBConnector *stateDb, string postState) +{ + Table macsecPostStateTable = Table(stateDb, STATE_FIPS_MACSEC_POST_TABLE_NAME); + vector fvts; + FieldValueTuple postStateFvt("post_state", postState); + fvts.push_back(postStateFvt); + + auto now = std::chrono::system_clock::now(); + std::time_t now_c = std::chrono::system_clock::to_time_t(now); + char buffer[32]; + std::strftime(buffer, sizeof(buffer), "%a %b %d %H:%M:%S %Y", std::gmtime(&now_c)); + FieldValueTuple lastUpdateTimeFvt("last_update_time", buffer); + fvts.push_back(lastUpdateTimeFvt); + + macsecPostStateTable.set("sai", fvts); +} + +string getMacsecPostState(DBConnector *stateDb) +{ + std::string postState = ""; + std::vector fvts; + Table macsecPostStateTable = Table(stateDb, STATE_FIPS_MACSEC_POST_TABLE_NAME); + if (macsecPostStateTable.get("sai", fvts)) + { + auto state = fvsGetValue(fvts, "post_state", true); + if (state) + { + postState = *state; + } + } + return postState; +} + +} diff --git a/orchagent/macsecpost.h b/orchagent/macsecpost.h new file mode 100644 index 00000000000..d7b1b25fd66 --- /dev/null +++ b/orchagent/macsecpost.h @@ -0,0 +1,13 @@ +#ifndef ORCHAGENT_MACSECPOST_H +#define ORCHAGENT_MACSECPOST_H + +using namespace std; + +namespace swss { + +void setMacsecPostState(DBConnector *stateDb, string postState); +string getMacsecPostState(DBConnector *stateDb); + +} + +#endif // ORCHAGENT_MACSECPOST_H diff --git a/orchagent/main.cpp b/orchagent/main.cpp index 89e0971d60f..97626d5e104 100644 --- a/orchagent/main.cpp +++ b/orchagent/main.cpp @@ -30,6 +30,7 @@ extern "C" { #include #include "warm_restart.h" #include "gearboxutils.h" +#include "macsecpost.h" using namespace std; using namespace swss; @@ -79,7 +80,7 @@ uint32_t create_switch_timeout = 0; void usage() { - cout << "usage: orchagent [-h] [-r record_type] [-d record_location] [-f swss_rec_filename] [-j sairedis_rec_filename] [-b batch_size] [-m MAC] [-i INST_ID] [-s] [-z mode] [-k bulk_size] [-q zmq_server_address] [-c mode] [-t create_switch_timeout] [-v VRF] [-I heart_beat_interval] [-R]" << endl; + cout << "usage: orchagent [-h] [-r record_type] [-d record_location] [-f swss_rec_filename] [-j sairedis_rec_filename] [-b batch_size] [-m MAC] [-i INST_ID] [-s] [-z mode] [-k bulk_size] [-q zmq_server_address] [-c mode] [-t create_switch_timeout] [-v VRF] [-I heart_beat_interval] [-R] [-M]" << endl; cout << " -h: display this message" << endl; cout << " -r record_type: record orchagent logs with type (default 3)" << endl; cout << " Bit 0: sairedis.rec, Bit 1: swss.rec, Bit 2: responsepublisher.rec. For example:" << endl; @@ -103,6 +104,7 @@ void usage() cout << " -v vrf: VRF name (default empty)" << endl; cout << " -I heart_beat_interval: Heart beat interval in millisecond (default 10)" << endl; cout << " -R enable the ring thread feature" << endl; + cout << " -M enable SAI MACSec POST" << endl; cout << " -D Delay in seconds before flex counter processing begins after orchagent startup (default 0)" << endl; } @@ -370,7 +372,10 @@ int main(int argc, char **argv) int record_type = 3; // Only swss and sairedis recordings enabled by default. long heartBeatInterval = HEART_BEAT_INTERVAL_MSECS_DEFAULT; - while ((opt = getopt(argc, argv, "b:m:r:f:j:d:i:hsz:k:q:c:t:v:I:R:D:")) != -1) + // Disable SAI MACSec POST by default. Use option -M to enable it. + bool macsec_post_enabled = false; + + while ((opt = getopt(argc, argv, "b:m:r:f:j:d:i:hsz:k:q:c:t:v:I:R:D:M")) != -1) { switch (opt) { @@ -488,6 +493,9 @@ int main(int argc, char **argv) case 'R': gRingMode = true; break; + case 'M': + macsec_post_enabled = true; + break; case 'D': { gFlexCounterDelaySec = swss::to_int(optarg); } break; default: /* '?' */ exit(EXIT_FAILURE); @@ -645,6 +653,29 @@ int main(int argc, char **argv) attrs.push_back(attr); } + string macsec_post_state; + if (gMySwitchType != "fabric" && macsec_post_enabled) + { + macsec_post_state = "switch-level-post-in-progress"; + + attr.id = SAI_SWITCH_ATTR_MACSEC_ENABLE_POST; + attr.value.booldata = true; + attrs.push_back(attr); + + attr.id = SAI_SWITCH_ATTR_SWITCH_MACSEC_POST_STATUS_NOTIFY; + attr.value.ptr = (void *)on_switch_macsec_post_status_notify; + attrs.push_back(attr); + + attr.id = SAI_SWITCH_ATTR_MACSEC_POST_STATUS_NOTIFY; + attr.value.ptr = (void *)on_macsec_post_status_notify; + attrs.push_back(attr); + } + else + { + macsec_post_state = "disabled"; + } + setMacsecPostState(&state_db, macsec_post_state); + /* Must be last Attribute */ attr.id = SAI_REDIS_SWITCH_ATTR_CONTEXT; attr.value.u64 = gSwitchId; @@ -758,6 +789,36 @@ int main(int argc, char **argv) gVirtualRouterId = attr.value.oid; SWSS_LOG_NOTICE("Get switch virtual router ID %" PRIx64, gVirtualRouterId); + /* Query MACSec POST capability and set POST state in state DB accordingly */ + if (macsec_post_enabled) + { + sai_attr_capability_t post_capability; + if (sai_query_attribute_capability(gSwitchId, SAI_OBJECT_TYPE_SWITCH, + SAI_SWITCH_ATTR_MACSEC_ENABLE_POST, + &post_capability) == SAI_STATUS_SUCCESS && + post_capability.create_implemented) + { + // POST is supported in switch init, and it was already enabled in switch init. + SWSS_LOG_NOTICE("MACSec POST enabled in switch init"); + } + else if (sai_query_attribute_capability(gSwitchId, SAI_OBJECT_TYPE_MACSEC, + SAI_MACSEC_ATTR_ENABLE_POST, + &post_capability) == SAI_STATUS_SUCCESS && + post_capability.create_implemented) + { + // POST is only supported in MACSec init. Set POST state to notify MACSecOrch + // to perform POST. + setMacsecPostState(&state_db, "macsec-level-post-in-progress"); + SWSS_LOG_NOTICE("MACSec POST will be enabled in MACSec init"); + } + else + { + // POST is not supported by SAI. Don't declare that SAI POST fails. + setMacsecPostState(&state_db, "disabled"); + SWSS_LOG_ERROR("MACSec POST is not supported by SAI"); + } + } + /* Get the NAT supported info */ attr.id = SAI_SWITCH_ATTR_AVAILABLE_SNAT_ENTRY; diff --git a/orchagent/notifications.cpp b/orchagent/notifications.cpp index 0544c3480b0..978c702e1e4 100644 --- a/orchagent/notifications.cpp +++ b/orchagent/notifications.cpp @@ -131,3 +131,33 @@ void on_switch_asic_sdk_health_event(sai_object_id_t switch_id, void on_tam_tel_type_config_change(sai_object_id_t tam_tel_id) { } + +void on_switch_macsec_post_status_notify(sai_object_id_t switch_id, + sai_switch_macsec_post_status_t switch_macsec_post_status) +{ + if (gRedisCommunicationMode == SAI_REDIS_COMMUNICATION_MODE_ZMQ_SYNC) + { + swss::DBConnector db("ASIC_DB", 0); + swss::NotificationProducer macsec_post_status_notify(&db, "NOTIFICATIONS"); + std::string sdata = sai_serialize_switch_macsec_post_status_ntf(switch_id, switch_macsec_post_status); + std::vector values; + + // Forward switch_macsec_post_status notification to be handled in macsecorch doTask() + macsec_post_status_notify.send("switch_macsec_post_status", sdata, values); + } +} + +void on_macsec_post_status_notify(sai_object_id_t macsec_id, + sai_macsec_post_status_t macsec_post_status) +{ + if (gRedisCommunicationMode == SAI_REDIS_COMMUNICATION_MODE_ZMQ_SYNC) + { + swss::DBConnector db("ASIC_DB", 0); + swss::NotificationProducer macsec_post_status_notify(&db, "NOTIFICATIONS"); + std::string sdata = sai_serialize_macsec_post_status_ntf(macsec_id, macsec_post_status); + std::vector values; + + // Forward macsec_post_status notification to be handled in macsecorch doTask() + macsec_post_status_notify.send("macsec_post_status", sdata, values); + } +} diff --git a/orchagent/notifications.h b/orchagent/notifications.h index 53fccf70e7c..b81efe9c590 100644 --- a/orchagent/notifications.h +++ b/orchagent/notifications.h @@ -26,3 +26,8 @@ void on_switch_asic_sdk_health_event(sai_object_id_t switch_id, const sai_u8_list_t description); void on_tam_tel_type_config_change(sai_object_id_t tam_tel_id); + +void on_switch_macsec_post_status_notify(sai_object_id_t switch_id, + sai_switch_macsec_post_status_t switch_macsec_post_status); +void on_macsec_post_status_notify(sai_object_id_t macsec_id, + sai_macsec_post_status_t macsec_post_status); diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 82ce8657e20..5e28c18c7ff 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -140,6 +140,7 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/mlagorch.cpp \ $(top_srcdir)/orchagent/isolationgrouporch.cpp \ $(top_srcdir)/orchagent/macsecorch.cpp \ + $(top_srcdir)/orchagent/macsecpost.cpp \ $(top_srcdir)/orchagent/lagid.cpp \ $(top_srcdir)/orchagent/bfdorch.cpp \ $(top_srcdir)/orchagent/icmporch.cpp \ diff --git a/tests/test_fips_macsec_post.py b/tests/test_fips_macsec_post.py new file mode 100644 index 00000000000..4d0c192e1a8 --- /dev/null +++ b/tests/test_fips_macsec_post.py @@ -0,0 +1,200 @@ +from dvslib.dvs_common import wait_for_result, PollingConfig + +# State DB POST state +STATE_DB_MACSEC_POST_TABLE = "FIPS_MACSEC_POST_TABLE" +STATE_DB_MACSEC_POST_STATE_DISABLED = "disabled" +STATE_DB_MACSEC_POST_STATE_SWITCH_LEVEL_POST_IN_PROGRESS = "switch-level-post-in-progress" +STATE_DB_MACSEC_POST_STATE_MACSEC_LEVEL_POST_IN_PROGRESS = "macsec-level-post-in-progress" +STATE_DB_MACSEC_POST_STATE_PASS = "pass" +STATE_DB_MACSEC_POST_STATE_FAIL = "fail" + +# SAI POST capability +SAI_MACSEC_POST_CAPABILITY = "macsec-post-capability" +SAI_MACSEC_POST_CAPABILITY_NOT_SUPPORTED = "not-supported" +SAI_MACSEC_POST_CAPABILITY_SWITCH = "switch" +SAI_MACSEC_POST_CAPABILITY_MACSEC = "macsec" + +# VS SAI POST config +VS_SAI_POST_CONFIG_FILE = "/tmp/vs_fips_post_config" +VS_SAI_POST_CONFIG_SWITCH_POST_STATUS_NOTIFY = "switch-macsec-post-status-notify" +VS_SAI_POST_CONFIG_SWITCH_POST_STATUS_QUERY = "switch-macsec-post-status-query" +VS_SAI_POST_CONFIG_INGRESS_MACSEC_POST_STATUS_NOTIFY = "ingress-macsec-post-status-notify" +VS_SAI_POST_CONFIG_EGRESS_MACSEC_POST_STATUS_NOTIFY = "egress-macsec-post-status-notify" +SAI_SWITCH_MACSEC_POST_STATUS_PASS = "SAI_SWITCH_MACSEC_POST_STATUS_PASS" +SAI_SWITCH_MACSEC_POST_STATUS_FAIL = "SAI_SWITCH_MACSEC_POST_STATUS_FAIL" +SAI_SWITCH_MACSEC_POST_STATUS_IN_PROGRESS = "SAI_SWITCH_MACSEC_POST_STATUS_IN_PROGRESS" +SAI_MACSEC_POST_STATUS_PASS = "SAI_MACSEC_POST_STATUS_PASS" +SAI_MACSEC_POST_STATUS_FAIL = "SAI_MACSEC_POST_STATUS_FAIL" +SAI_MACSEC_POST_STATUS_IN_PROGRESS = "SAI_MACSEC_POST_STATUS_IN_PROGRESS" + +# POST syslogs +SWITCH_MACSEC_POST_PASS_SYSYLOG = "Switch MACSec POST passed" +SWITCH_MACSEC_POST_FAIL_SYSYLOG = "Switch MACSec POST failed" +SWITCH_MACSEC_POST_FAIL_SYSYLOG_SAI_NOT_SUPPORTED = "MACSec POST is not supported by SAI" +MACSEC_POST_ENABLED_SYSLOG = "Init MACSec objects and enable POST" +INGRESS_MACSEC_POST_PASS_SYSLOG = "Ingress MACSec POST passed" +INGRESS_MACSEC_POST_FAIL_SYSLOG = "Ingress MACSec POST failed" +EGRESS_MACSEC_POST_PASS_SYSLOG = "Egress MACSec POST passed" +EGRESS_MACSEC_POST_FAIL_SYSLOG = "Egress MACSec POST failed" +MACSEC_POST_PASS_SYSLOG = "Ingress and egress MACSec POST passed" +MACSEC_POST_FAIL_SYSLOG = "MACSec POST failed" + +ORCHAGENT_SH_BACKUP = "/usr/bin/orchagent_sh_macsec_post_ut_backup" + +class TestMacsecPost(object): + def check_state_db_post_state(self, dvs, expected_state): + dvs.get_state_db().wait_for_field_match(STATE_DB_MACSEC_POST_TABLE, "sai", + {'post_state': expected_state}) + + def restart_dvs_with_post_config(self, dvs, sai_post_capability=SAI_MACSEC_POST_CAPABILITY_SWITCH, + sai_post_notification_status_config=None, sai_macsec_post_enabled=True): + + sai_post_config = {} + if sai_post_capability != SAI_MACSEC_POST_CAPABILITY_NOT_SUPPORTED: + sai_post_config[SAI_MACSEC_POST_CAPABILITY] = sai_post_capability + if sai_post_notification_status_config: + sai_post_config.update(sai_post_notification_status_config) + dvs.runcmd(["sh", "-c", f"rm -f {VS_SAI_POST_CONFIG_FILE}"]) + dvs.runcmd(["sh", "-c", f"touch {VS_SAI_POST_CONFIG_FILE}"]) + for k, v in sai_post_config.items(): + dvs.runcmd(["sh", "-c", f"echo '{k} {v}' >> {VS_SAI_POST_CONFIG_FILE}"]) + + if sai_macsec_post_enabled: + rc, _ = dvs.runcmd(["sh", "-c", f"ls {ORCHAGENT_SH_BACKUP}"]) + if rc == 0: + dvs.runcmd(f"cp {ORCHAGENT_SH_BACKUP} /usr/bin/orchagent.sh") + else: + dvs.runcmd(f"cp /usr/bin/orchagent.sh {ORCHAGENT_SH_BACKUP}") + dvs.runcmd("sed -i.bak 's/\/usr\/bin\/orchagent /\/usr\/bin\/orchagent -M /g' /usr/bin/orchagent.sh") + + marker = dvs.add_log_marker() + + dvs.runcmd('killall5 -15') + dvs.net_cleanup() + dvs.destroy_servers() + dvs.create_servers() + dvs.restart() + + return marker + + def check_syslog(self, dvs, marker, log): + def do_check_syslog(): + (ec, out) = dvs.runcmd(['sh', '-c', "awk \'/%s/,ENDFILE {print;}\' /var/log/syslog | grep \'%s\' | wc -l" %(marker, log)]) + return (int(out.strip()) >= 1, None) + max_poll = PollingConfig(polling_interval=5, timeout=600, strict=True) + wait_for_result(do_check_syslog, polling_config=max_poll) + + def check_asic_db_post_state(self, dvs, sai_macsec_post_enabled=True, sai_post_capability=SAI_MACSEC_POST_CAPABILITY_SWITCH): + switch_oids = dvs.get_asic_db().get_keys("ASIC_STATE:SAI_OBJECT_TYPE_SWITCH") + assert len(switch_oids) == 1 + entry = dvs.get_asic_db().get_entry("ASIC_STATE", f"SAI_OBJECT_TYPE_SWITCH:{switch_oids[0]}") + if sai_macsec_post_enabled: + assert entry["SAI_SWITCH_ATTR_MACSEC_ENABLE_POST"] and entry["SAI_SWITCH_ATTR_SWITCH_MACSEC_POST_STATUS_NOTIFY"] + else: + assert "SAI_SWITCH_ATTR_MACSEC_ENABLE_POST" not in entry and "SAI_SWITCH_ATTR_SWITCH_MACSEC_POST_STATUS_NOTIFY" not in entry + + macsec_oids = dvs.get_asic_db().get_keys("ASIC_STATE:SAI_OBJECT_TYPE_MACSEC") + if sai_post_capability == SAI_MACSEC_POST_CAPABILITY_SWITCH: + # No MACSec object should be created since POST is supported in switch init. + assert not macsec_oids + elif sai_post_capability == SAI_MACSEC_POST_CAPABILITY_MACSEC: + # POST is only supported in MACSec init. Two MACSec objects - ingress and egress - must be created to enable POST. + assert len(macsec_oids) == 2 + for oid in macsec_oids: + entry = dvs.get_asic_db().get_entry("ASIC_STATE", f"SAI_OBJECT_TYPE_MACSEC:{oid}") + assert entry["SAI_MACSEC_ATTR_ENABLE_POST"] + + def test_PostDisabled(self, dvs): + self.restart_dvs_with_post_config(dvs, sai_macsec_post_enabled=False) + self.check_state_db_post_state(dvs, STATE_DB_MACSEC_POST_STATE_DISABLED) + self.check_asic_db_post_state(dvs, sai_macsec_post_enabled=False) + + def test_PostEnabled_InitialState(self, dvs): + sai_post_notification_status_config = {VS_SAI_POST_CONFIG_SWITCH_POST_STATUS_QUERY : SAI_SWITCH_MACSEC_POST_STATUS_IN_PROGRESS} + self.restart_dvs_with_post_config(dvs, sai_post_notification_status_config=sai_post_notification_status_config) + self.check_state_db_post_state(dvs, STATE_DB_MACSEC_POST_STATE_SWITCH_LEVEL_POST_IN_PROGRESS) + self.check_asic_db_post_state(dvs) + + def test_PostEnabled_SaiPostNotSupported(self, dvs): + marker = self.restart_dvs_with_post_config(dvs, sai_post_capability=SAI_MACSEC_POST_CAPABILITY_NOT_SUPPORTED) + self.check_state_db_post_state(dvs, STATE_DB_MACSEC_POST_STATE_DISABLED) + self.check_syslog(dvs, marker, SWITCH_MACSEC_POST_FAIL_SYSYLOG_SAI_NOT_SUPPORTED) + self.check_asic_db_post_state(dvs) + + def test_PostEnabled_SwitchLevelPost_NotificationPass(self, dvs): + sai_post_notification_status_config = {VS_SAI_POST_CONFIG_SWITCH_POST_STATUS_NOTIFY : SAI_SWITCH_MACSEC_POST_STATUS_PASS, + VS_SAI_POST_CONFIG_SWITCH_POST_STATUS_QUERY : SAI_SWITCH_MACSEC_POST_STATUS_IN_PROGRESS} + marker = self.restart_dvs_with_post_config(dvs, sai_post_notification_status_config=sai_post_notification_status_config) + self.check_state_db_post_state(dvs, STATE_DB_MACSEC_POST_STATE_PASS) + self.check_syslog(dvs, marker, SWITCH_MACSEC_POST_PASS_SYSYLOG) + self.check_asic_db_post_state(dvs) + + def test_PostEnabled_SwitchLevelPost_NotificationFail(self, dvs): + sai_post_notification_status_config = {VS_SAI_POST_CONFIG_SWITCH_POST_STATUS_NOTIFY : SAI_SWITCH_MACSEC_POST_STATUS_FAIL, + VS_SAI_POST_CONFIG_SWITCH_POST_STATUS_QUERY : SAI_SWITCH_MACSEC_POST_STATUS_IN_PROGRESS} + marker = self.restart_dvs_with_post_config(dvs, sai_post_notification_status_config=sai_post_notification_status_config) + self.check_state_db_post_state(dvs, STATE_DB_MACSEC_POST_STATE_FAIL) + self.check_syslog(dvs, marker, SWITCH_MACSEC_POST_FAIL_SYSYLOG) + self.check_asic_db_post_state(dvs) + + def test_PostEnabled_SwitchLevelPost_QueryPass(self, dvs): + sai_post_notification_status_config = {VS_SAI_POST_CONFIG_SWITCH_POST_STATUS_QUERY : SAI_SWITCH_MACSEC_POST_STATUS_PASS} + marker =self.restart_dvs_with_post_config(dvs, sai_post_notification_status_config=sai_post_notification_status_config) + self.check_state_db_post_state(dvs, STATE_DB_MACSEC_POST_STATE_PASS) + self.check_syslog(dvs, marker, SWITCH_MACSEC_POST_PASS_SYSYLOG) + self.check_asic_db_post_state(dvs) + + def test_PostEnabled_SwitchLevelPost_QueryFail(self, dvs): + sai_post_notification_status_config = {VS_SAI_POST_CONFIG_SWITCH_POST_STATUS_QUERY : SAI_SWITCH_MACSEC_POST_STATUS_FAIL} + marker =self.restart_dvs_with_post_config(dvs, sai_post_notification_status_config=sai_post_notification_status_config) + self.check_state_db_post_state(dvs, STATE_DB_MACSEC_POST_STATE_FAIL) + self.check_syslog(dvs, marker, SWITCH_MACSEC_POST_FAIL_SYSYLOG) + self.check_asic_db_post_state(dvs) + + def test_PostEnabled_MacsecLevelPost_StateBeforeNotification(self, dvs): + marker = self.restart_dvs_with_post_config(dvs, sai_post_capability=SAI_MACSEC_POST_CAPABILITY_MACSEC) + self.check_state_db_post_state(dvs, STATE_DB_MACSEC_POST_STATE_MACSEC_LEVEL_POST_IN_PROGRESS) + self.check_syslog(dvs, marker, MACSEC_POST_ENABLED_SYSLOG) + self.check_asic_db_post_state(dvs, sai_post_capability=SAI_MACSEC_POST_CAPABILITY_MACSEC) + + def test_PostEnabled_MacsecLevelPost_NotificationPass(self, dvs): + sai_post_notification_status_config = {VS_SAI_POST_CONFIG_INGRESS_MACSEC_POST_STATUS_NOTIFY : SAI_MACSEC_POST_STATUS_PASS, + VS_SAI_POST_CONFIG_EGRESS_MACSEC_POST_STATUS_NOTIFY : SAI_MACSEC_POST_STATUS_PASS} + marker = self.restart_dvs_with_post_config(dvs, sai_post_capability=SAI_MACSEC_POST_CAPABILITY_MACSEC, + sai_post_notification_status_config=sai_post_notification_status_config) + self.check_state_db_post_state(dvs, STATE_DB_MACSEC_POST_STATE_PASS) + for syslog in [INGRESS_MACSEC_POST_PASS_SYSLOG, EGRESS_MACSEC_POST_PASS_SYSLOG, MACSEC_POST_PASS_SYSLOG]: + self.check_syslog(dvs, marker, syslog) + self.check_asic_db_post_state(dvs, sai_post_capability=SAI_MACSEC_POST_CAPABILITY_MACSEC) + + def test_PostEnabled_MacsecLevelPost_NotificationIngressPostFail(self, dvs): + sai_post_notification_status_config = {VS_SAI_POST_CONFIG_INGRESS_MACSEC_POST_STATUS_NOTIFY : SAI_MACSEC_POST_STATUS_FAIL, + VS_SAI_POST_CONFIG_EGRESS_MACSEC_POST_STATUS_NOTIFY : SAI_MACSEC_POST_STATUS_PASS} + marker = self.restart_dvs_with_post_config(dvs, sai_post_capability=SAI_MACSEC_POST_CAPABILITY_MACSEC, + sai_post_notification_status_config=sai_post_notification_status_config) + self.check_state_db_post_state(dvs, STATE_DB_MACSEC_POST_STATE_FAIL) + for syslog in [INGRESS_MACSEC_POST_FAIL_SYSLOG, EGRESS_MACSEC_POST_PASS_SYSLOG, MACSEC_POST_FAIL_SYSLOG]: + self.check_syslog(dvs, marker, syslog) + self.check_asic_db_post_state(dvs, sai_post_capability=SAI_MACSEC_POST_CAPABILITY_MACSEC) + + def test_PostEnabled_MacsecLevelPost_NotificationEgressPostFail(self, dvs): + sai_post_notification_status_config = {VS_SAI_POST_CONFIG_INGRESS_MACSEC_POST_STATUS_NOTIFY : SAI_MACSEC_POST_STATUS_PASS, + VS_SAI_POST_CONFIG_EGRESS_MACSEC_POST_STATUS_NOTIFY : SAI_MACSEC_POST_STATUS_FAIL} + marker = self.restart_dvs_with_post_config(dvs, sai_post_capability=SAI_MACSEC_POST_CAPABILITY_MACSEC, + sai_post_notification_status_config=sai_post_notification_status_config) + self.check_state_db_post_state(dvs, STATE_DB_MACSEC_POST_STATE_FAIL) + for syslog in [INGRESS_MACSEC_POST_PASS_SYSLOG, EGRESS_MACSEC_POST_FAIL_SYSLOG, MACSEC_POST_FAIL_SYSLOG]: + self.check_syslog(dvs, marker, syslog) + self.check_asic_db_post_state(dvs, sai_post_capability=SAI_MACSEC_POST_CAPABILITY_MACSEC) + + def test_CleanUp(self,dvs): + rc, _ = dvs.runcmd(["sh", "-c", f"ls {ORCHAGENT_SH_BACKUP}"]) + if rc == 0: + dvs.runcmd(f"cp {ORCHAGENT_SH_BACKUP} /usr/bin/orchagent.sh") + dvs.runcmd(["sh", "-c", f"rm -f {VS_SAI_POST_CONFIG_FILE}"]) + dvs.restart() + +# 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