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