vslib/MACsecManager.cpp (708 lines of code) (raw):

#include "MACsecManager.h" #include "MACsecIngressFilter.h" #include "MACsecEgressFilter.h" #include <swss/logger.h> #include <swss/exec.h> #include <unistd.h> #include <regex> #include <cstring> #include <system_error> #include <cinttypes> #include <string> using namespace saivs; static constexpr macsec_an_t MAX_MACSEC_SA_NUMBER = 3; MACsecManager::MACsecManager() { SWSS_LOG_ENTER(); // empty } MACsecManager::~MACsecManager() { SWSS_LOG_ENTER(); // empty } bool MACsecManager::create_macsec_port( _In_ const MACsecAttr &attr) { SWSS_LOG_ENTER(); // Linux MACsec driver would not differentiate the Ingress and Egress port, // We just need to create the Linux MACsec device when the egress port was created // And we assume that creating the ingress port is always success. if (attr.m_direction == SAI_MACSEC_DIRECTION_INGRESS) { return true; } return add_macsec_manager(attr.m_macsecName, attr.m_info); } bool MACsecManager::create_macsec_sc( _In_ const MACsecAttr &attr) { SWSS_LOG_ENTER(); if (is_macsec_sc_existing( attr.m_macsecName, attr.m_direction, attr.m_sci)) { SWSS_LOG_WARN( "MACsec SC %s at the device %s has been created", attr.m_sci.c_str(), attr.m_macsecName.c_str()); return true; } if (attr.m_direction == SAI_MACSEC_DIRECTION_EGRESS) { if (!create_macsec_egress_sc(attr)) { SWSS_LOG_WARN( "Cannot create MACsec egress SC %s at the device %s", attr.m_sci.c_str(), attr.m_macsecName.c_str()); return false; } } else { if (!create_macsec_ingress_sc(attr)) { SWSS_LOG_WARN( "Cannot create MACsec ingress SC %s at the device %s", attr.m_sci.c_str(), attr.m_macsecName.c_str()); return false; } } return true; } bool MACsecManager::create_macsec_sa( _In_ const MACsecAttr &attr) { SWSS_LOG_ENTER(); if (is_macsec_sa_existing( attr.m_macsecName, attr.m_direction, attr.m_sci, attr.m_an)) { SWSS_LOG_WARN( "MACsec SA %s:%u at the device %s has been created", attr.m_sci.c_str(), static_cast<std::uint32_t>(attr.m_an), attr.m_macsecName.c_str()); return true; } if (!is_macsec_sc_existing( attr.m_macsecName, attr.m_direction, attr.m_sci)) { if (!create_macsec_sc(attr)) { SWSS_LOG_WARN( "Cannot create MACsec SC %s at the device %s.", attr.m_sci.c_str(), attr.m_macsecName.c_str()); return false; } } if (attr.m_direction == SAI_MACSEC_DIRECTION_EGRESS) { if (!create_macsec_egress_sa(attr)) { SWSS_LOG_WARN( "Cannot create MACsec egress SA %s:%u at the device %s.", attr.m_sci.c_str(), static_cast<std::uint32_t>(attr.m_an), attr.m_macsecName.c_str()); return false; } } else { if (!create_macsec_ingress_sa(attr)) { SWSS_LOG_WARN( "Cannot create MACsec ingress SA %s:%u at the device %s.", attr.m_sci.c_str(), static_cast<std::uint32_t>(attr.m_an), attr.m_macsecName.c_str()); return false; } } return true; } bool MACsecManager::delete_macsec_port( _In_ const MACsecAttr &attr) { SWSS_LOG_ENTER(); // Linux MACsec driver would not differentiate the Ingress and Egress port, // We just need to delete the Linux MACsec device when the egress port was deleted // And we assume that deleting the ingress port is always success. if (attr.m_direction == SAI_MACSEC_DIRECTION_INGRESS) { return true; } return delete_macsec_manager(attr.m_macsecName); } bool MACsecManager::delete_macsec_sc( _In_ const MACsecAttr &attr) { SWSS_LOG_ENTER(); if (!is_macsec_sc_existing( attr.m_macsecName, attr.m_direction, attr.m_sci)) { SWSS_LOG_WARN( "MACsec SC %s at the device %s isn't existing", attr.m_sci.c_str(), attr.m_macsecName.c_str()); return true; } if (attr.m_direction == SAI_MACSEC_DIRECTION_EGRESS) { if (!delete_macsec_egress_sc(attr)) { SWSS_LOG_WARN( "Cannot delete MACsec egress SC %s at the device %s.", attr.m_sci.c_str(), attr.m_macsecName.c_str()); return false; } } else { if (!delete_macsec_ingress_sc(attr)) { SWSS_LOG_WARN( "Cannot delete MACsec ingress SC %s at the device %s.", attr.m_sci.c_str(), attr.m_macsecName.c_str()); return false; } } return true; } bool MACsecManager::delete_macsec_sa( _In_ const MACsecAttr &attr) { SWSS_LOG_ENTER(); if (!is_macsec_sa_existing( attr.m_macsecName, attr.m_direction, attr.m_sci, attr.m_an)) { SWSS_LOG_WARN( "MACsec SA %s:%u at the device %s isn't existing", attr.m_sci.c_str(), static_cast<std::uint32_t>(attr.m_an), attr.m_macsecName.c_str()); return true; } if (attr.m_direction == SAI_MACSEC_DIRECTION_EGRESS) { if (!delete_macsec_egress_sa(attr)) { SWSS_LOG_WARN( "Cannot delete MACsec egress SA %s:%u at the device %s.", attr.m_sci.c_str(), static_cast<std::uint32_t>(attr.m_an), attr.m_macsecName.c_str()); return false; } } else { if (!delete_macsec_ingress_sa(attr)) { SWSS_LOG_WARN( "Cannot delete MACsec ingress SA %s:%u at the device %s.", attr.m_sci.c_str(), static_cast<std::uint32_t>(attr.m_an), attr.m_macsecName.c_str()); return false; } } return true; } bool MACsecManager::enable_macsec_filter( _In_ const std::string &macsecInterface, bool enable) { SWSS_LOG_ENTER(); auto itr = m_macsecTrafficManagers.find(macsecInterface); if (itr == m_macsecTrafficManagers.end()) { SWSS_LOG_ERROR("MACsec port %s cannot be found", macsecInterface.c_str()); return false; } auto &manager = itr->second; if (enable) { if (manager.m_forwarder == nullptr) { SWSS_LOG_ERROR("MACsec forwarder for device %s isn't existing", macsecInterface.c_str()); return false; } manager.m_ingressFilter->set_macsec_fd(manager.m_forwarder->get_macsecfd()); manager.m_egressFilter->set_macsec_fd(manager.m_forwarder->get_macsecfd()); } manager.m_ingressFilter->enable_macsec_device(enable); manager.m_egressFilter->enable_macsec_device(enable); return true; } bool MACsecManager::update_macsec_sa_pn( _In_ const MACsecAttr &attr, _In_ sai_uint64_t pn) { SWSS_LOG_ENTER(); std::ostringstream ostream; ostream << "/sbin/ip macsec set " << shellquote(attr.m_macsecName); if (attr.m_direction == SAI_MACSEC_DIRECTION_EGRESS) { ostream << " tx"; } else { ostream << " rx sci " << attr.m_sci; } ostream << " sa " << attr.m_an; if (attr.is_xpn()) { ostream << " ssci " << attr.m_ssci; } ostream << ( attr.is_xpn() ? " xpn " : " pn " ) << pn; SWSS_LOG_NOTICE("%s", ostream.str().c_str()); return exec(ostream.str()); } bool MACsecManager::get_macsec_sa_pn( _In_ const MACsecAttr &attr, _Out_ sai_uint64_t &pn) const { SWSS_LOG_ENTER(); pn = 1; std::string macsecSaInfo; if (!get_macsec_sa_info( attr.m_macsecName, attr.m_direction, attr.m_sci, attr.m_an, macsecSaInfo)) { return false; } // Here is an example of MACsec SA // 0: PN 28, state on, key ebe9123ecbbfd96bee92c8ab01000000 // Use pattern 'PN\s*(\d+)' // to extract packet number from MACsec SA const std::regex pattern("PN\\s*(\\d+)"); std::smatch matches; if (std::regex_search(macsecSaInfo, matches, pattern)) { if (matches.size() != 2) { SWSS_LOG_ERROR( "Wrong match result %s in %s", matches.str().c_str(), macsecSaInfo.c_str()); return false; } std::istringstream iss(matches[1].str()); iss >> pn; return true; } else { SWSS_LOG_WARN( "The packet number isn't in the MACsec SA %s:%u at the device %s.", attr.m_sci.c_str(), static_cast<std::uint32_t>(attr.m_an), attr.m_macsecName.c_str()); return false; } } // Create MACsec Egress SC // $ ip link add link <VETH_NAME> name <MACSEC_NAME> type macsec sci <SCI> // $ ip link set dev <MACSEC_NAME> up // MACsec egress SC will be automatically create when the MACsec port is created bool MACsecManager::create_macsec_egress_sc( _In_ const MACsecAttr &attr) { SWSS_LOG_ENTER(); std::ostringstream ostream; ostream << "/sbin/ip link add link " << shellquote(attr.m_vethName) << " name " << shellquote(attr.m_macsecName) << " type macsec " << " sci " << attr.m_sci << " encrypt " << (attr.m_encryptionEnable ? " on " : " off ") << " cipher " << attr.m_cipher << " send_sci " << (attr.m_sendSci ? " on " : " off ") << " && ip link set dev " << shellquote(attr.m_macsecName) << " up"; SWSS_LOG_NOTICE("%s", ostream.str().c_str()); if (!exec(ostream.str())) { return false; } return add_macsec_forwarder(attr.m_macsecName) && enable_macsec_filter(attr.m_macsecName, true); } // Create MACsec Ingress SC // $ ip macsec add <MACSEC_NAME> rx sci <SCI> bool MACsecManager::create_macsec_ingress_sc( _In_ const MACsecAttr &attr) { SWSS_LOG_ENTER(); std::ostringstream ostream; ostream << "/sbin/ip macsec add " << shellquote(attr.m_macsecName) << " rx sci " << attr.m_sci << " on"; SWSS_LOG_NOTICE("%s", ostream.str().c_str()); return exec(ostream.str()); } // Create MACsec Egress SA // $ ip macsec add <MACSEC_NAME> tx sa <AN> pn <PN> on key <AUTH_KEY> <SAK> && // ip link set link <VETH_NAME> name <MACSEC_NAME> type macsec encodingsa <AN> bool MACsecManager::create_macsec_egress_sa( _In_ const MACsecAttr &attr) { SWSS_LOG_ENTER(); std::ostringstream ostream; ostream << "/sbin/ip macsec add " << shellquote(attr.m_macsecName) << " tx sa " << attr.m_an << ( attr.is_xpn() ? " xpn " : " pn ") << attr.m_pn << ( attr.is_xpn() ? " ssci " : "" ) << ( attr.is_xpn() ? attr.m_ssci : "" ) << ( attr.is_xpn() ? " salt " : "" ) << ( attr.is_xpn() ? attr.m_salt : "" ) << " on key " << attr.m_authKey << " " << attr.m_sak << " && ip link set link " << attr.m_vethName << " name " << attr.m_macsecName << " type macsec encodingsa " << attr.m_an; SWSS_LOG_NOTICE("%s", ostream.str().c_str()); return exec(ostream.str()); } // Create MACsec Ingress SA // $ ip macsec add <MACSEC_NAME> rx sci <SCI> sa <SA> pn <PN> on key <AUTH_KEY> <SAK> bool MACsecManager::create_macsec_ingress_sa( _In_ const MACsecAttr &attr) { SWSS_LOG_ENTER(); std::ostringstream ostream; ostream << "/sbin/ip macsec add " << shellquote(attr.m_macsecName) << " rx sci " << attr.m_sci << " sa " << attr.m_an << ( attr.is_xpn() ? " xpn " : " pn " ) << attr.m_pn << ( attr.is_xpn() ? " ssci " : "" ) << ( attr.is_xpn() ? attr.m_ssci : "" ) << ( attr.is_xpn() ? " salt " : "" ) << ( attr.is_xpn() ? attr.m_salt : "" ) << " on key " << attr.m_authKey << " " << attr.m_sak; SWSS_LOG_NOTICE("%s", ostream.str().c_str()); return exec(ostream.str()); } // Delete MACsec Egress SC // $ ip link del link <VETH_NAME> name <MACSEC_NAME> type macsec bool MACsecManager::delete_macsec_egress_sc( _In_ const MACsecAttr &attr) { SWSS_LOG_ENTER(); bool result = true; std::ostringstream ostream; ostream << "/sbin/ip link del link " << shellquote(attr.m_vethName) << " name " << shellquote(attr.m_macsecName) << " type macsec"; SWSS_LOG_NOTICE("%s", ostream.str().c_str()); result &= delete_macsec_forwarder(attr.m_macsecName); result &= enable_macsec_filter(attr.m_macsecName, false); result &= exec(ostream.str()); return result; } // Delete MACsec Ingress SC // $ ip macsec set <MACSEC_NAME> rx sci <SCI> off // $ ip macsec del <MACSEC_NAME> rx sci <SCI> bool MACsecManager::delete_macsec_ingress_sc( _In_ const MACsecAttr &attr) { SWSS_LOG_ENTER(); std::ostringstream ostream; ostream << "/sbin/ip macsec set " << shellquote(attr.m_macsecName) << " rx sci " << attr.m_sci << " off" << " && ip macsec del " << shellquote(attr.m_macsecName) << " rx sci " << attr.m_sci; SWSS_LOG_NOTICE("%s", ostream.str().c_str()); return exec(ostream.str()); } // Delete MACsec Egress SA // $ ip macsec set <MACSEC_NAME> tx sa 0 off // $ ip macsec del <MACSEC_NAME> tx sa 0 bool MACsecManager::delete_macsec_egress_sa( _In_ const MACsecAttr &attr) { SWSS_LOG_ENTER(); std::ostringstream ostream; ostream << "/sbin/ip macsec set " << shellquote(attr.m_macsecName) << " tx sa " << attr.m_an << " off" << " && ip macsec del " << shellquote(attr.m_macsecName) << " tx sa " << attr.m_an ; SWSS_LOG_NOTICE("%s", ostream.str().c_str()); return exec(ostream.str()); } // Delete MACsec Ingress SA // $ ip macsec set <MACSEC_NAME> rx sci <SCI> sa <SA> off // $ ip macsec del <MACSEC_NAME> rx sci <SCI> sa <SA> bool MACsecManager::delete_macsec_ingress_sa( _In_ const MACsecAttr &attr) { SWSS_LOG_ENTER(); std::ostringstream ostream; ostream << "/sbin/ip macsec set " << shellquote(attr.m_macsecName) << " rx sci " << attr.m_sci << " sa " << attr.m_an << " off" << " && ip macsec del " << shellquote(attr.m_macsecName) << " rx sci " << attr.m_sci << " sa " << attr.m_an; SWSS_LOG_NOTICE("%s", ostream.str().c_str()); return exec(ostream.str()); } bool MACsecManager::add_macsec_forwarder( _In_ const std::string &macsecInterface) { SWSS_LOG_ENTER(); auto itr = m_macsecTrafficManagers.find(macsecInterface); if (itr == m_macsecTrafficManagers.end()) { SWSS_LOG_ERROR("MACsec port %s cannot be found", macsecInterface.c_str()); return false; } auto &manager = itr->second; manager.m_forwarder = std::make_shared<MACsecForwarder>(macsecInterface, manager.m_info); return true; } bool MACsecManager::delete_macsec_forwarder( _In_ const std::string &macsecInterface) { SWSS_LOG_ENTER(); auto itr = m_macsecTrafficManagers.find(macsecInterface); if (itr == m_macsecTrafficManagers.end()) { SWSS_LOG_ERROR("MACsec port %s cannot be found", macsecInterface.c_str()); return false; } auto &manager = itr->second; manager.m_forwarder.reset(); return true; } bool MACsecManager::add_macsec_manager( _In_ const std::string &macsecInterface, _In_ std::shared_ptr<HostInterfaceInfo> info) { SWSS_LOG_ENTER(); auto itr = m_macsecTrafficManagers.emplace( std::piecewise_construct, std::forward_as_tuple(macsecInterface), std::forward_as_tuple()); if (!itr.second) { SWSS_LOG_ERROR( "macsec manager for %s was existed", macsecInterface.c_str()); return false; } auto &manager = itr.first->second; manager.m_info = info; manager.m_ingressFilter = std::make_shared<MACsecIngressFilter>(macsecInterface); manager.m_egressFilter = std::make_shared<MACsecEgressFilter>(macsecInterface); manager.m_info->installEth2TapFilter( FilterPriority::MACSEC_FILTER, manager.m_ingressFilter); manager.m_info->installTap2EthFilter( FilterPriority::MACSEC_FILTER, manager.m_egressFilter); return true; } bool MACsecManager::delete_macsec_manager( _In_ const std::string &macsecInterface) { SWSS_LOG_ENTER(); auto itr = m_macsecTrafficManagers.find(macsecInterface); if (itr == m_macsecTrafficManagers.end()) { SWSS_LOG_ERROR( "macsec manager for %s isn't existed", macsecInterface.c_str()); return false; } auto &manager = itr->second; manager.m_info->uninstallEth2TapFilter(manager.m_ingressFilter); manager.m_info->uninstallTap2EthFilter(manager.m_egressFilter); m_macsecTrafficManagers.erase(itr); return true; } // Query MACsec session // $ ip macsec show <MACSEC_NAME> bool MACsecManager::get_macsec_device_info( _In_ const std::string &macsecDevice, _Out_ std::string &info) const { SWSS_LOG_ENTER(); std::ostringstream ostream; ostream << "/sbin/ip macsec show " << shellquote(macsecDevice); return exec(ostream.str(), info); } bool MACsecManager::is_macsec_device_existing( _In_ const std::string &macsecDevice) const { SWSS_LOG_ENTER(); std::string macsec_info; return get_macsec_device_info(macsecDevice, macsec_info); } bool MACsecManager::get_macsec_sc_info( _In_ const std::string &macsecDevice, _In_ sai_int32_t direction, _In_ const std::string &sci, _Out_ std::string &info) const { SWSS_LOG_ENTER(); std::string macsec_info; if (!get_macsec_device_info(macsecDevice, macsec_info)) { SWSS_LOG_DEBUG( "MACsec device %s is nonexisting", macsecDevice.c_str()); return false; } const char *strDirection = (direction == SAI_MACSEC_DIRECTION_EGRESS) ? "TXSC" : "RXSC"; // Here is an example of MACsec device information // cipher suite: GCM-AES-128, using ICV length 16 // TXSC: fe5400bd9b360001 on SA 0 // 0: PN 84, state on, key ebe9123ecbbfd96bee92c8ab01000000 // 1: PN 0, state on, key ebe9123ecbbfd96bee92c8ab01000001 // RXSC: 5254001234560001, state on // 0: PN 28, state on, key ebe9123ecbbfd96bee92c8ab01000000 // Use pattern '<DIRECTION>:\s*<SCI>[ \w,]+\n?(?:\s*\d:[,\w ]+\n?)*' // to extract MACsec SC information std::ostringstream ostream; ostream << strDirection << ":\\s*" << sci << "[ \\w,]+\n?(?:\\s*\\d:[,\\w ]+\n?)*"; const std::regex pattern(ostream.str()); std::smatch matches; if (std::regex_search(macsec_info, matches, pattern)) { info = matches[0].str(); return true; } return false; } bool MACsecManager::is_macsec_sc_existing( _In_ const std::string &macsecDevice, _In_ sai_int32_t direction, _In_ const std::string &sci) const { SWSS_LOG_ENTER(); std::string macsec_sc_info; return get_macsec_sc_info(macsecDevice, direction, sci, macsec_sc_info); } bool MACsecManager::get_macsec_sa_info( _In_ const std::string &macsecDevice, _In_ sai_int32_t direction, _In_ const std::string &sci, _In_ macsec_an_t an, _Out_ std::string &info) const { SWSS_LOG_ENTER(); std::string macsec_sc_info; if (!get_macsec_sc_info(macsecDevice, direction, sci, macsec_sc_info)) { SWSS_LOG_DEBUG( "The MACsec SC %s at the device %s is nonexisting.", sci.c_str(), macsecDevice.c_str()); return false; } // Here is an example of MACsec SA // 0: PN 28, state on, key ebe9123ecbbfd96bee92c8ab01000000 // Use pattern '\s*<AN>:\s*PN\s*\d+[,\w ]+\n?' // to extract MACsec SA information from MACsec SC std::ostringstream ostream; ostream << "\\s*" << an << ":\\s*PN\\s*\\d+[,\\w ]+\n?"; const std::regex pattern(ostream.str()); std::smatch matches; if (std::regex_search(macsec_sc_info, matches, pattern)) { info = matches[0].str(); return true; } return false; } bool MACsecManager::is_macsec_sa_existing( _In_ const std::string &macsecDevice, _In_ sai_int32_t direction, _In_ const std::string &sci, _In_ macsec_an_t an) const { SWSS_LOG_ENTER(); std::string macsecSaInfo; return get_macsec_sa_info( macsecDevice, direction, sci, an, macsecSaInfo); } size_t MACsecManager::get_macsec_sa_count( _In_ const std::string &macsecDevice, _In_ sai_int32_t direction, _In_ const std::string &sci) const { SWSS_LOG_ENTER(); size_t sa_count = 0; for (macsec_an_t an = 0; an <= MAX_MACSEC_SA_NUMBER; an++) // lgtm [cpp/constant-comparison] { if (is_macsec_sa_existing(macsecDevice, direction, sci, an)) { sa_count++; } } return sa_count; } void MACsecManager::cleanup_macsec_device() const { SWSS_LOG_ENTER(); if (access("/sbin/ip", F_OK) == -1) { SWSS_LOG_WARN("file /sbin/ip not accessible, skipping"); return; } std::string macsecInfos; if (!exec("/sbin/ip macsec show", macsecInfos)) { // this is workaround, there was exception thrown here, and it probably // suggest that, ip command is in place, but don't support "macsec" SWSS_LOG_WARN("Cannot show MACsec ports"); return; } // Here is an example of MACsec device information // 2774: macsec0: protect on validate strict sc off sa off encrypt on send_sci on end_station off scb off replay on window 0 // cipher suite: GCM-AES-128, using ICV length 16 // TXSC: fe5400409b920001 on SA 0 // 2775: macsec1: protect on validate strict sc off sa off encrypt on send_sci on end_station off scb off replay on window 0 // cipher suite: GCM-AES-128, using ICV length 16 // TXSC: fe5400409b920001 on SA 0 // 2776: macsec2: protect on validate strict sc off sa off encrypt on send_sci on end_station off scb off replay on window 0 // cipher suite: GCM-AES-128, using ICV length 16 // TXSC: fe5400409b920001 on SA 0 // Use pattern : '^\d+:\s*(\w+):' to extract all MACsec interface names const std::regex pattern("\\d+:\\s*(\\w+):"); std::smatch matches; std::string::const_iterator searchPos(macsecInfos.cbegin()); while(std::regex_search(searchPos, macsecInfos.cend(), matches, pattern)) { std::ostringstream ostream; ostream << "/sbin/ip link del " << matches[1].str(); if (!exec(ostream.str())) { SWSS_LOG_WARN( "Cannot cleanup MACsec interface %s", matches[1].str().c_str()); } searchPos = matches.suffix().first; } } std::string MACsecManager::shellquote( _In_ const std::string &str) const { SWSS_LOG_ENTER(); static const std::regex re("([$`\"\\\n])"); return "\"" + std::regex_replace(str, re, "\\$1") + "\""; } bool MACsecManager::exec( _In_ const std::string &command, _Out_ std::string &output) const { SWSS_LOG_ENTER(); if (swss::exec(command, output) != 0) { SWSS_LOG_DEBUG("FAIL %s", command.c_str()); return false; } return true; } bool MACsecManager::exec( _In_ const std::string &command) const { SWSS_LOG_ENTER(); std::string res; return exec(command, res); }