xref: /openbmc/dbus-sensors/src/intrusion/IntrusionSensorMain.cpp (revision 89be6147e5a7ffa86d88d8f3e27eba8eb2c3a9da)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 
17 #include "ChassisIntrusionSensor.hpp"
18 #include "Utils.hpp"
19 
20 #include <boost/asio/error.hpp>
21 #include <boost/asio/io_context.hpp>
22 #include <boost/asio/steady_timer.hpp>
23 #include <boost/container/flat_map.hpp>
24 #include <phosphor-logging/lg2.hpp>
25 #include <sdbusplus/asio/connection.hpp>
26 #include <sdbusplus/asio/object_server.hpp>
27 #include <sdbusplus/bus.hpp>
28 #include <sdbusplus/bus/match.hpp>
29 #include <sdbusplus/message.hpp>
30 
31 #include <array>
32 #include <charconv>
33 #include <chrono>
34 #include <cstdint>
35 #include <ctime>
36 #include <exception>
37 #include <filesystem>
38 #include <fstream>
39 #include <functional>
40 #include <map>
41 #include <memory>
42 #include <string>
43 #include <system_error>
44 #include <utility>
45 #include <variant>
46 #include <vector>
47 
48 static constexpr const char* sensorType = "ChassisIntrusionSensor";
49 static constexpr const char* nicType = "NIC";
50 static constexpr auto nicTypes{std::to_array<const char*>({nicType})};
51 
52 static const std::map<std::string, std::string> compatibleHwmonNames = {
53     {"Aspeed2600_Hwmon", "intrusion0_alarm"}
54     // Add compatible strings here for new hwmon intrusion detection
55     // drivers that have different hwmon names but would also like to
56     // use the available Hwmon class.
57 };
58 
createSensorsFromConfig(boost::asio::io_context & io,sdbusplus::asio::object_server & objServer,const std::shared_ptr<sdbusplus::asio::connection> & dbusConnection,std::shared_ptr<ChassisIntrusionSensor> & pSensor)59 static void createSensorsFromConfig(
60     boost::asio::io_context& io, sdbusplus::asio::object_server& objServer,
61     const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
62     std::shared_ptr<ChassisIntrusionSensor>& pSensor)
63 {
64     // find matched configuration according to sensor type
65     ManagedObjectType sensorConfigurations;
66     bool useCache = false;
67 
68     if (!getSensorConfiguration(sensorType, dbusConnection,
69                                 sensorConfigurations, useCache))
70     {
71         lg2::error("error communicating to entity manager");
72         return;
73     }
74 
75     const SensorData* sensorData = nullptr;
76     const std::pair<std::string, SensorBaseConfigMap>* baseConfiguration =
77         nullptr;
78 
79     for (const auto& [path, cfgData] : sensorConfigurations)
80     {
81         baseConfiguration = nullptr;
82         sensorData = &cfgData;
83 
84         // match sensor type
85         auto sensorBase = sensorData->find(configInterfaceName(sensorType));
86         if (sensorBase == sensorData->end())
87         {
88             lg2::error("error finding base configuration");
89             continue;
90         }
91 
92         baseConfiguration = &(*sensorBase);
93 
94         // Rearm defaults to "Automatic" mode
95         bool autoRearm = true;
96         auto findRearm = baseConfiguration->second.find("Rearm");
97         if (findRearm != baseConfiguration->second.end())
98         {
99             std::string rearmStr = std::get<std::string>(findRearm->second);
100             if (rearmStr != "Automatic" && rearmStr != "Manual")
101             {
102                 lg2::error("Wrong input for Rearm parameter");
103                 continue;
104             }
105             autoRearm = (rearmStr == "Automatic");
106         }
107 
108         // judge class, "Gpio", "Hwmon" or "I2C"
109         auto findClass = baseConfiguration->second.find("Class");
110         if (findClass != baseConfiguration->second.end())
111         {
112             auto classString = std::get<std::string>(findClass->second);
113             if (classString == "Gpio")
114             {
115                 auto findGpioPolarity =
116                     baseConfiguration->second.find("GpioPolarity");
117 
118                 if (findGpioPolarity == baseConfiguration->second.end())
119                 {
120                     lg2::error("error finding gpio polarity in configuration");
121                     continue;
122                 }
123 
124                 try
125                 {
126                     bool gpioInverted =
127                         (std::get<std::string>(findGpioPolarity->second) ==
128                          "Low");
129                     pSensor = std::make_shared<ChassisIntrusionGpioSensor>(
130                         autoRearm, io, objServer, gpioInverted);
131                     pSensor->start();
132                     lg2::debug(
133                         "find chassis intrusion sensor polarity inverted flag is '{GPIO_INVERTED}'",
134                         "GPIO_INVERTED", gpioInverted);
135                     return;
136                 }
137                 catch (const std::bad_variant_access& e)
138                 {
139                     lg2::error("invalid value for gpio info in config.");
140                     continue;
141                 }
142                 catch (const std::exception& e)
143                 {
144                     lg2::error(
145                         "error creating chassis intrusion gpio sensor: '{ERROR}'",
146                         "ERROR", e);
147                     continue;
148                 }
149             }
150             // If class string contains Hwmon string
151             else if (classString.find("Hwmon") != std::string::npos)
152             {
153                 std::string hwmonName;
154                 std::map<std::string, std::string>::const_iterator
155                     compatIterator = compatibleHwmonNames.find(classString);
156 
157                 if (compatIterator == compatibleHwmonNames.end())
158                 {
159                     lg2::error("Hwmon Class string is not supported");
160                     continue;
161                 }
162 
163                 hwmonName = compatIterator->second;
164 
165                 try
166                 {
167                     pSensor = std::make_shared<ChassisIntrusionHwmonSensor>(
168                         autoRearm, io, objServer, hwmonName);
169                     pSensor->start();
170                     return;
171                 }
172                 catch (const std::exception& e)
173                 {
174                     lg2::error(
175                         "error creating chassis intrusion hwmon sensor: '{ERROR}'",
176                         "ERROR", e);
177                     continue;
178                 }
179             }
180             else
181             {
182                 auto findBus = baseConfiguration->second.find("Bus");
183                 auto findAddress = baseConfiguration->second.find("Address");
184                 if (findBus == baseConfiguration->second.end() ||
185                     findAddress == baseConfiguration->second.end())
186                 {
187                     lg2::error("error finding bus or address in configuration");
188                     continue;
189                 }
190                 try
191                 {
192                     int busId = std::get<uint64_t>(findBus->second);
193                     int slaveAddr = std::get<uint64_t>(findAddress->second);
194                     pSensor = std::make_shared<ChassisIntrusionPchSensor>(
195                         autoRearm, io, objServer, busId, slaveAddr);
196                     pSensor->start();
197                     lg2::debug(
198                         "find matched bus '{BUS}', matched slave addr '{ADDR}'",
199                         "BUS", busId, "ADDR", slaveAddr);
200                     return;
201                 }
202                 catch (const std::bad_variant_access& e)
203                 {
204                     lg2::error("invalid value for bus or address in config.");
205                     continue;
206                 }
207                 catch (const std::exception& e)
208                 {
209                     lg2::error(
210                         "error creating chassis intrusion pch sensor: '{ERROR}'",
211                         "ERROR", e);
212                     continue;
213                 }
214             }
215         }
216     }
217 
218     lg2::error("Can't find matched I2C, GPIO or Hwmon configuration");
219 
220     // Make sure nothing runs when there's failure in configuration for the
221     // sensor after rescan
222     if (pSensor)
223     {
224         lg2::error("Reset the occupied sensor pointer");
225         pSensor = nullptr;
226     }
227 }
228 
229 boost::container::flat_map<int, bool> lanStatusMap;
230 boost::container::flat_map<int, std::string> lanInfoMap;
231 boost::container::flat_map<std::string, int> pathSuffixMap;
232 
getNicNameInfo(const std::shared_ptr<sdbusplus::asio::connection> & dbusConnection)233 static void getNicNameInfo(
234     const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
235 {
236     auto getter = std::make_shared<GetSensorConfiguration>(
237         dbusConnection, [](const ManagedObjectType& sensorConfigurations) {
238             // Get NIC name and save to map
239             lanInfoMap.clear();
240             for (const auto& [path, cfgData] : sensorConfigurations)
241             {
242                 const std::pair<std::string, SensorBaseConfigMap>*
243                     baseConfiguration = nullptr;
244 
245                 // find base configuration
246                 auto sensorBase = cfgData.find(configInterfaceName(nicType));
247                 if (sensorBase == cfgData.end())
248                 {
249                     continue;
250                 }
251                 baseConfiguration = &(*sensorBase);
252 
253                 auto findEthIndex = baseConfiguration->second.find("EthIndex");
254                 auto findName = baseConfiguration->second.find("Name");
255 
256                 if (findEthIndex != baseConfiguration->second.end() &&
257                     findName != baseConfiguration->second.end())
258                 {
259                     const auto* pEthIndex =
260                         std::get_if<uint64_t>(&findEthIndex->second);
261                     const auto* pName =
262                         std::get_if<std::string>(&findName->second);
263                     if (pEthIndex != nullptr && pName != nullptr)
264                     {
265                         lanInfoMap[*pEthIndex] = *pName;
266                         lg2::debug("find name of eth{ETH_INDEX} is '{NAME}'",
267                                    "ETH_INDEX", *pEthIndex, "NAME", *pName);
268                     }
269                 }
270             }
271 
272             if (lanInfoMap.empty())
273             {
274                 lg2::error("can't find matched NIC name.");
275             }
276         });
277 
278     getter->getConfiguration(
279         std::vector<std::string>{nicTypes.begin(), nicTypes.end()});
280 }
281 
processLanStatusChange(sdbusplus::message_t & message)282 static void processLanStatusChange(sdbusplus::message_t& message)
283 {
284     const std::string& pathName = message.get_path();
285     std::string interfaceName;
286     SensorBaseConfigMap properties;
287     message.read(interfaceName, properties);
288 
289     auto findStateProperty = properties.find("OperationalState");
290     if (findStateProperty == properties.end())
291     {
292         return;
293     }
294     std::string* pState =
295         std::get_if<std::string>(&(findStateProperty->second));
296     if (pState == nullptr)
297     {
298         lg2::error("invalid OperationalState");
299         return;
300     }
301 
302     bool newLanConnected = (*pState == "routable" || *pState == "carrier" ||
303                             *pState == "degraded");
304 
305     // get ethNum from path. /org/freedesktop/network1/link/_32 for eth0
306     size_t pos = pathName.find("/_");
307     if (pos == std::string::npos || pathName.length() <= pos + 2)
308     {
309         lg2::error("unexpected path name '{NAME}'", "NAME", pathName);
310         return;
311     }
312     std::string suffixStr = pathName.substr(pos + 2);
313 
314     auto findEthNum = pathSuffixMap.find(suffixStr);
315     if (findEthNum == pathSuffixMap.end())
316     {
317         lg2::error("unexpected eth for suffixStr '{SUFFIX}'", "SUFFIX",
318                    suffixStr);
319         return;
320     }
321     int ethNum = findEthNum->second;
322 
323     // get lan status from map
324     auto findLanStatus = lanStatusMap.find(ethNum);
325     if (findLanStatus == lanStatusMap.end())
326     {
327         lg2::error("unexpected eth{ETH_INDEX} is lanStatusMap", "ETH_INDEX",
328                    ethNum);
329         return;
330     }
331     bool oldLanConnected = findLanStatus->second;
332 
333     // get lan info from map
334     std::string lanInfo;
335     if (!lanInfoMap.empty())
336     {
337         auto findLanInfo = lanInfoMap.find(ethNum);
338         if (findLanInfo == lanInfoMap.end())
339         {
340             lg2::error("unexpected eth{ETH_INDEX} is lanInfoMap", "ETH_INDEX",
341                        ethNum);
342         }
343         else
344         {
345             lanInfo = "(" + findLanInfo->second + ")";
346         }
347     }
348 
349     lg2::debug(
350         "ethNum = {ETH_INDEX}, state = {LAN_STATUS}, oldLanConnected = {OLD_LAN_CONNECTED}, "
351         "newLanConnected = {NEW_LAN_CONNECTED}",
352         "ETH_INDEX", ethNum, "LAN_STATUS", *pState, "OLD_LAN_CONNECTED",
353         (oldLanConnected ? "true" : "false"), "NEW_LAN_CONNECTED",
354         (newLanConnected ? "true" : "false"));
355 
356     if (oldLanConnected != newLanConnected)
357     {
358         std::string strEthNum = "eth" + std::to_string(ethNum) + lanInfo;
359         const auto* strState = newLanConnected ? "connected" : "lost";
360         const auto* strMsgId =
361             newLanConnected ? "OpenBMC.0.1.LanRegained" : "OpenBMC.0.1.LanLost";
362 
363         lg2::info("'{ETH_INFO}' LAN leash '{LAN_STATUS}'", "ETH_INFO",
364                   strEthNum, "LAN_STATUS", strState, "REDFISH_MESSAGE_ID",
365                   strMsgId, "REDFISH_MESSAGE_ARGS", strEthNum);
366 
367         lanStatusMap[ethNum] = newLanConnected;
368     }
369 }
370 
371 /** @brief Initialize the lan status.
372  *
373  * @return true on success and false on failure
374  */
initializeLanStatus(const std::shared_ptr<sdbusplus::asio::connection> & conn)375 static bool initializeLanStatus(
376     const std::shared_ptr<sdbusplus::asio::connection>& conn)
377 {
378     // init lan port name from configuration
379     getNicNameInfo(conn);
380 
381     // get eth info from sysfs
382     std::vector<std::filesystem::path> files;
383     if (!findFiles(std::filesystem::path("/sys/class/net/"),
384                    R"(eth\d+/ifindex)", files))
385     {
386         lg2::error("No eth in system");
387         return false;
388     }
389 
390     // iterate through all found eth files, and save ifindex
391     for (const std::filesystem::path& fileName : files)
392     {
393         lg2::debug("Reading '{NAME}'", "NAME", fileName);
394         std::ifstream sysFile(fileName);
395         if (!sysFile.good())
396         {
397             lg2::error("Failure reading '{NAME}'", "NAME", fileName);
398             continue;
399         }
400         std::string line;
401         getline(sysFile, line);
402         const uint8_t ifindex = std::stoi(line);
403         // pathSuffix is ASCII of ifindex
404         const std::string& pathSuffix = std::to_string(ifindex + 30);
405 
406         // extract ethNum
407         const std::string& fileStr = fileName.string();
408         const int pos = fileStr.find("eth");
409         const std::string& ethNumStr = fileStr.substr(pos + 3);
410         int ethNum = 0;
411         std::from_chars_result r = std::from_chars(
412             ethNumStr.data(), ethNumStr.data() + ethNumStr.size(), ethNum);
413         if (r.ec != std::errc())
414         {
415             lg2::error("invalid ethNum string: '{ETH_INDEX}'", "ETH_INDEX",
416                        ethNumStr);
417             continue;
418         }
419 
420         // save pathSuffix
421         pathSuffixMap[pathSuffix] = ethNum;
422         lg2::debug(
423             "ethNum = {ETH_INDEX}, ifindex = {LINE}, pathSuffix = {PATH}",
424             "ETH_INDEX", ethNum, "LINE", line, "PATH", pathSuffix);
425 
426         // init lan connected status from networkd
427         conn->async_method_call(
428             [ethNum](boost::system::error_code ec,
429                      const std::variant<std::string>& property) {
430                 lanStatusMap[ethNum] = false;
431                 if (ec)
432                 {
433                     lg2::error("Error reading init status of eth{ETH_INDEX}",
434                                "ETH_INDEX", ethNum);
435                     return;
436                 }
437                 const std::string* pState = std::get_if<std::string>(&property);
438                 if (pState == nullptr)
439                 {
440                     lg2::error("Unable to read lan status value");
441                     return;
442                 }
443                 bool isLanConnected =
444                     (*pState == "routable" || *pState == "carrier" ||
445                      *pState == "degraded");
446                 lg2::debug("ethNum = {ETH_INDEX}, init LAN status = {STATUS}",
447                            "ETH_INDEX", ethNum, "STATUS",
448                            (isLanConnected ? "true" : "false"));
449                 lanStatusMap[ethNum] = isLanConnected;
450             },
451             "org.freedesktop.network1",
452             "/org/freedesktop/network1/link/_" + pathSuffix,
453             "org.freedesktop.DBus.Properties", "Get",
454             "org.freedesktop.network1.Link", "OperationalState");
455     }
456     return true;
457 }
458 
main()459 int main()
460 {
461     std::shared_ptr<ChassisIntrusionSensor> intrusionSensor;
462 
463     // setup connection to dbus
464     boost::asio::io_context io;
465     auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
466 
467     // setup object server, define interface
468     systemBus->request_name("xyz.openbmc_project.IntrusionSensor");
469 
470     sdbusplus::asio::object_server objServer(systemBus, true);
471 
472     objServer.add_manager("/xyz/openbmc_project/Chassis");
473 
474     createSensorsFromConfig(io, objServer, systemBus, intrusionSensor);
475 
476     // callback to handle configuration change
477     boost::asio::steady_timer filterTimer(io);
478     std::function<void(sdbusplus::message_t&)> eventHandler =
479         [&](sdbusplus::message_t& message) {
480             if (message.is_method_error())
481             {
482                 lg2::error("callback method error");
483                 return;
484             }
485             // this implicitly cancels the timer
486             filterTimer.expires_after(std::chrono::seconds(1));
487             filterTimer.async_wait([&](const boost::system::error_code& ec) {
488                 if (ec == boost::asio::error::operation_aborted)
489                 {
490                     // timer was cancelled
491                     return;
492                 }
493                 lg2::info("rescan due to configuration change");
494                 createSensorsFromConfig(io, objServer, systemBus,
495                                         intrusionSensor);
496             });
497         };
498 
499     std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
500         setupPropertiesChangedMatches(
501             *systemBus, std::to_array<const char*>({sensorType}), eventHandler);
502 
503     if (initializeLanStatus(systemBus))
504     {
505         // add match to monitor lan status change
506         sdbusplus::bus::match_t lanStatusMatch(
507             static_cast<sdbusplus::bus_t&>(*systemBus),
508             "type='signal', member='PropertiesChanged',"
509             "arg0namespace='org.freedesktop.network1.Link'",
510             [](sdbusplus::message_t& msg) { processLanStatusChange(msg); });
511 
512         // add match to monitor entity manager signal about nic name config
513         // change
514         sdbusplus::bus::match_t lanConfigMatch(
515             static_cast<sdbusplus::bus_t&>(*systemBus),
516             "type='signal', member='PropertiesChanged',path_namespace='" +
517                 std::string(inventoryPath) + "',arg0namespace='" +
518                 configInterfaceName(nicType) + "'",
519             [&systemBus](sdbusplus::message_t& msg) {
520                 if (msg.is_method_error())
521                 {
522                     lg2::error("callback method error");
523                     return;
524                 }
525                 getNicNameInfo(systemBus);
526             });
527     }
528 
529     io.run();
530 
531     return 0;
532 }
533