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