Log physical security event of lan leash

Log event of lan link status change by processing dbus signal sent by
networkd in systemd.

Tested:
Unplug network cable of dedicate NIC
Check the below log message in
https://$bmcip/redfish/v1/Systems/system/LogServices/EventLog/Entries
"Message": "Physical Security: eth0 LAN leash lost is asserted"

Change-Id: I16cf41a93896355aa28aaeb07cb16bc788b1abf1
Signed-off-by: Qiang XU <qiang.xu@linux.intel.com>
diff --git a/src/IntrusionSensorMain.cpp b/src/IntrusionSensorMain.cpp
index 2def3c1..2c8628b 100644
--- a/src/IntrusionSensorMain.cpp
+++ b/src/IntrusionSensorMain.cpp
@@ -14,12 +14,15 @@
 // limitations under the License.
 */
 
+#include <systemd/sd-journal.h>
+
 #include <ChassisIntrusionSensor.hpp>
 #include <Utils.hpp>
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/asio.hpp>
 #include <chrono>
 #include <ctime>
+#include <fstream>
 #include <iostream>
 #include <sdbusplus/asio/connection.hpp>
 #include <sdbusplus/asio/object_server.hpp>
@@ -34,6 +37,8 @@
 static constexpr const char* sensorType =
     "xyz.openbmc_project.Configuration.ChassisIntrusionSensor";
 
+namespace fs = std::filesystem;
+
 static bool getIntrusionSensorConfig(
     std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
     IntrusionSensorType* pType, int* pBusId, int* pSlaveAddr, int* pGpioIndex,
@@ -173,6 +178,178 @@
     return false;
 }
 
+static constexpr bool debugLanLeash = false;
+boost::container::flat_map<int, bool> lanStatusMap;
+boost::container::flat_map<std::string, int> pathSuffixMap;
+
+static void processLanStatusChange(sdbusplus::message::message& message)
+{
+    const std::string& pathName = message.get_path();
+    std::string interfaceName;
+    boost::container::flat_map<std::string, BasicVariantType> properties;
+    message.read(interfaceName, properties);
+
+    auto findStateProperty = properties.find("OperationalState");
+    if (findStateProperty == properties.end())
+    {
+        return;
+    }
+    std::string* pState = sdbusplus::message::variant_ns::get_if<std::string>(
+        &(findStateProperty->second));
+    if (pState == nullptr)
+    {
+        std::cerr << "invalid OperationalState \n";
+        return;
+    }
+
+    bool newLanConnected = (*pState == "routable" || *pState == "carrier" ||
+                            *pState == "degraded");
+
+    // get ethNum from path. /org/freedesktop/network1/link/_32 for eth0
+    int pos = pathName.find("/_");
+    if (pos == std::string::npos || pathName.length() <= pos + 2)
+    {
+        std::cerr << "unexpected path name " << pathName << "\n";
+        return;
+    }
+    std::string suffixStr = pathName.substr(pos + 2);
+
+    auto findEthNum = pathSuffixMap.find(suffixStr);
+    if (findEthNum == pathSuffixMap.end())
+    {
+        std::cerr << "unexpected eth for suffixStr " << suffixStr << "\n";
+        return;
+    }
+    int ethNum = findEthNum->second;
+    auto findLanStatus = lanStatusMap.find(ethNum);
+    if (findLanStatus == lanStatusMap.end())
+    {
+        std::cerr << "unexpected eth " << ethNum << " in lanStatusMap \n";
+        return;
+    }
+    bool oldLanConnected = findLanStatus->second;
+
+    if (debugLanLeash)
+    {
+        std::cout << "ethNum = " << ethNum << ", state = " << *pState
+                  << ", oldLanConnected = "
+                  << (oldLanConnected ? "true" : "false")
+                  << ", newLanConnected = "
+                  << (newLanConnected ? "true" : "false") << "\n";
+    }
+
+    if (oldLanConnected != newLanConnected)
+    {
+        std::string strEthNum = "eth" + std::to_string(ethNum);
+        std::string strEvent = strEthNum + " LAN leash lost";
+        std::string strAssert = newLanConnected ? "de-asserted" : "asserted";
+        std::string strMsg = strEthNum + " is " +
+                             (newLanConnected ? "connected" : "disconnected");
+        std::string strMsgId = "OpenBMC.0.1.PhysicalSecurity";
+        sd_journal_send("MESSAGE=%s", strMsg.c_str(), "PRIORITY=%i", LOG_INFO,
+                        "REDFISH_MESSAGE_ID=%s", strMsgId.c_str(),
+                        "REDFISH_MESSAGE_ARGS=%s,%s", strEvent.c_str(),
+                        strAssert.c_str(), NULL);
+        lanStatusMap[ethNum] = newLanConnected;
+        if (debugLanLeash)
+        {
+            std::cout << "log redfish event: " << strMsg << "\n";
+        }
+    }
+}
+
+static void
+    monitorLanStatusChange(std::shared_ptr<sdbusplus::asio::connection> conn)
+{
+    std::vector<fs::path> files;
+    if (!findFiles(fs::path("/sys/class/net/"), R"(eth\d+/ifindex)", files))
+    {
+        std::cerr << "No eth in system\n";
+        return;
+    }
+
+    // iterate through all found eth files, and save ifindex
+    for (auto& fileName : files)
+    {
+        if (debugLanLeash)
+        {
+            std::cout << "Reading " << fileName << "\n";
+        }
+        std::ifstream sysFile(fileName);
+        if (!sysFile.good())
+        {
+            std::cerr << "Failure reading " << fileName << "\n";
+            continue;
+        }
+        std::string line;
+        getline(sysFile, line);
+        const uint8_t ifindex = std::stoi(line);
+        // pathSuffix is ASCII of ifindex
+        const std::string& pathSuffix = std::to_string(ifindex + 30);
+
+        // extract ethNum
+        const std::string& fileStr = fileName.string();
+        const int pos = fileStr.find("eth");
+        const std::string& ethNumStr = fileStr.substr(pos + 3);
+        int ethNum = 0;
+        try
+        {
+            ethNum = std::stoul(ethNumStr);
+        }
+        catch (const std::invalid_argument& err)
+        {
+            std::cerr << "invalid ethNum string: " << ethNumStr << "\n";
+            continue;
+        }
+
+        // save pathSuffix
+        pathSuffixMap[pathSuffix] = ethNum;
+        if (debugLanLeash)
+        {
+            std::cout << "ethNum = " << std::to_string(ethNum)
+                      << ", ifindex = " << line
+                      << ", pathSuffix = " << pathSuffix << "\n";
+        }
+
+        // init lan connected status
+        conn->async_method_call(
+            [ethNum](boost::system::error_code ec,
+                     const std::variant<std::string>& property) {
+                if (ec)
+                {
+                    return;
+                }
+                const std::string* pState = std::get_if<std::string>(&property);
+                if (pState == nullptr)
+                {
+                    std::cerr << "Unable to read lan status value\n";
+                    return;
+                }
+                bool isLanConnected =
+                    (*pState == "routable" || *pState == "carrier" ||
+                     *pState == "degraded");
+                if (debugLanLeash)
+                {
+                    std::cout << "ethNum = " << std::to_string(ethNum)
+                              << ", init LAN status = "
+                              << (isLanConnected ? "true" : "false") << "\n";
+                }
+                lanStatusMap[ethNum] = isLanConnected;
+            },
+            "org.freedesktop.network1",
+            "/org/freedesktop/network1/link/_" + pathSuffix,
+            "org.freedesktop.DBus.Properties", "Get",
+            "org.freedesktop.network1.Link", "OperationalState");
+    }
+
+    // add match to monitor lan status change
+    static sdbusplus::bus::match::match match(
+        static_cast<sdbusplus::bus::bus&>(*conn),
+        "type='signal', member='PropertiesChanged',"
+        "arg0namespace='org.freedesktop.network1.Link'",
+        [](sdbusplus::message::message& msg) { processLanStatusChange(msg); });
+}
+
 int main()
 {
     int busId = -1, slaveAddr = -1, gpioIndex = -1;
@@ -225,6 +402,8 @@
             std::string(inventoryPath) + "',arg0namespace='" + sensorType + "'",
         eventHandler);
 
+    monitorLanStatusChange(systemBus);
+
     io.run();
 
     return 0;