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