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 << "find matched bus " << busId
205 << ", matched slave addr " << slaveAddr
206 << "\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 = std::get_if<std::string>(&findName->second);
270 if (pEthIndex != nullptr && pName != nullptr)
271 {
272 lanInfoMap[*pEthIndex] = *pName;
273 if (debugLanLeash)
274 {
275 std::cout << "find name of eth" << *pEthIndex << " is "
276 << *pName << "\n";
277 }
278 }
279 }
280 }
281
282 if (lanInfoMap.empty())
283 {
284 std::cerr << "can't find matched NIC name. \n";
285 }
286 });
287
288 getter->getConfiguration(
289 std::vector<std::string>{nicTypes.begin(), nicTypes.end()});
290 }
291
processLanStatusChange(sdbusplus::message_t & message)292 static void processLanStatusChange(sdbusplus::message_t& message)
293 {
294 const std::string& pathName = message.get_path();
295 std::string interfaceName;
296 SensorBaseConfigMap properties;
297 message.read(interfaceName, properties);
298
299 auto findStateProperty = properties.find("OperationalState");
300 if (findStateProperty == properties.end())
301 {
302 return;
303 }
304 std::string* pState =
305 std::get_if<std::string>(&(findStateProperty->second));
306 if (pState == nullptr)
307 {
308 std::cerr << "invalid OperationalState \n";
309 return;
310 }
311
312 bool newLanConnected = (*pState == "routable" || *pState == "carrier" ||
313 *pState == "degraded");
314
315 // get ethNum from path. /org/freedesktop/network1/link/_32 for eth0
316 size_t pos = pathName.find("/_");
317 if (pos == std::string::npos || pathName.length() <= pos + 2)
318 {
319 std::cerr << "unexpected path name " << pathName << "\n";
320 return;
321 }
322 std::string suffixStr = pathName.substr(pos + 2);
323
324 auto findEthNum = pathSuffixMap.find(suffixStr);
325 if (findEthNum == pathSuffixMap.end())
326 {
327 std::cerr << "unexpected eth for suffixStr " << suffixStr << "\n";
328 return;
329 }
330 int ethNum = findEthNum->second;
331
332 // get lan status from map
333 auto findLanStatus = lanStatusMap.find(ethNum);
334 if (findLanStatus == lanStatusMap.end())
335 {
336 std::cerr << "unexpected eth " << ethNum << " in lanStatusMap \n";
337 return;
338 }
339 bool oldLanConnected = findLanStatus->second;
340
341 // get lan info from map
342 std::string lanInfo;
343 if (!lanInfoMap.empty())
344 {
345 auto findLanInfo = lanInfoMap.find(ethNum);
346 if (findLanInfo == lanInfoMap.end())
347 {
348 std::cerr << "unexpected eth " << ethNum << " in lanInfoMap \n";
349 }
350 else
351 {
352 lanInfo = "(" + findLanInfo->second + ")";
353 }
354 }
355
356 if (debugLanLeash)
357 {
358 std::cout << "ethNum = " << ethNum << ", state = " << *pState
359 << ", oldLanConnected = "
360 << (oldLanConnected ? "true" : "false")
361 << ", newLanConnected = "
362 << (newLanConnected ? "true" : "false") << "\n";
363 }
364
365 if (oldLanConnected != newLanConnected)
366 {
367 std::string strEthNum = "eth" + std::to_string(ethNum) + lanInfo;
368 const auto* strState = newLanConnected ? "connected" : "lost";
369 const auto* strMsgId = newLanConnected ? "OpenBMC.0.1.LanRegained"
370 : "OpenBMC.0.1.LanLost";
371
372 lg2::info("{ETHDEV} LAN leash {STATE}", "ETHDEV", strEthNum, "STATE",
373 strState, "REDFISH_MESSAGE_ID", strMsgId,
374 "REDFISH_MESSAGE_ARGS", strEthNum);
375
376 lanStatusMap[ethNum] = newLanConnected;
377 }
378 }
379
380 /** @brief Initialize the lan status.
381 *
382 * @return true on success and false on failure
383 */
initializeLanStatus(const std::shared_ptr<sdbusplus::asio::connection> & conn)384 static bool initializeLanStatus(
385 const std::shared_ptr<sdbusplus::asio::connection>& conn)
386 {
387 // init lan port name from configuration
388 getNicNameInfo(conn);
389
390 // get eth info from sysfs
391 std::vector<fs::path> files;
392 if (!findFiles(fs::path("/sys/class/net/"), R"(eth\d+/ifindex)", files))
393 {
394 std::cerr << "No eth in system\n";
395 return false;
396 }
397
398 // iterate through all found eth files, and save ifindex
399 for (const fs::path& fileName : files)
400 {
401 if (debugLanLeash)
402 {
403 std::cout << "Reading " << fileName << "\n";
404 }
405 std::ifstream sysFile(fileName);
406 if (!sysFile.good())
407 {
408 std::cerr << "Failure reading " << fileName << "\n";
409 continue;
410 }
411 std::string line;
412 getline(sysFile, line);
413 const uint8_t ifindex = std::stoi(line);
414 // pathSuffix is ASCII of ifindex
415 const std::string& pathSuffix = std::to_string(ifindex + 30);
416
417 // extract ethNum
418 const std::string& fileStr = fileName.string();
419 const int pos = fileStr.find("eth");
420 const std::string& ethNumStr = fileStr.substr(pos + 3);
421 int ethNum = 0;
422 std::from_chars_result r = std::from_chars(
423 ethNumStr.data(), ethNumStr.data() + ethNumStr.size(), ethNum);
424 if (r.ec != std::errc())
425 {
426 std::cerr << "invalid ethNum string: " << ethNumStr << "\n";
427 continue;
428 }
429
430 // save pathSuffix
431 pathSuffixMap[pathSuffix] = ethNum;
432 if (debugLanLeash)
433 {
434 std::cout << "ethNum = " << std::to_string(ethNum)
435 << ", ifindex = " << line
436 << ", pathSuffix = " << pathSuffix << "\n";
437 }
438
439 // init lan connected status from networkd
440 conn->async_method_call(
441 [ethNum](boost::system::error_code ec,
442 const std::variant<std::string>& property) {
443 lanStatusMap[ethNum] = false;
444 if (ec)
445 {
446 std::cerr << "Error reading init status of eth" << ethNum
447 << "\n";
448 return;
449 }
450 const std::string* pState = std::get_if<std::string>(&property);
451 if (pState == nullptr)
452 {
453 std::cerr << "Unable to read lan status value\n";
454 return;
455 }
456 bool isLanConnected = (*pState == "routable" ||
457 *pState == "carrier" ||
458 *pState == "degraded");
459 if (debugLanLeash)
460 {
461 std::cout << "ethNum = " << std::to_string(ethNum)
462 << ", init LAN status = "
463 << (isLanConnected ? "true" : "false") << "\n";
464 }
465 lanStatusMap[ethNum] = isLanConnected;
466 },
467 "org.freedesktop.network1",
468 "/org/freedesktop/network1/link/_" + pathSuffix,
469 "org.freedesktop.DBus.Properties", "Get",
470 "org.freedesktop.network1.Link", "OperationalState");
471 }
472 return true;
473 }
474
main()475 int main()
476 {
477 std::shared_ptr<ChassisIntrusionSensor> intrusionSensor;
478
479 // setup connection to dbus
480 boost::asio::io_context io;
481 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
482
483 // setup object server, define interface
484 systemBus->request_name("xyz.openbmc_project.IntrusionSensor");
485
486 sdbusplus::asio::object_server objServer(systemBus, true);
487
488 objServer.add_manager("/xyz/openbmc_project/Chassis");
489
490 createSensorsFromConfig(io, objServer, systemBus, intrusionSensor);
491
492 // callback to handle configuration change
493 boost::asio::steady_timer filterTimer(io);
494 std::function<void(sdbusplus::message_t&)> eventHandler =
495 [&](sdbusplus::message_t& message) {
496 if (message.is_method_error())
497 {
498 std::cerr << "callback method error\n";
499 return;
500 }
501 // this implicitly cancels the timer
502 filterTimer.expires_after(std::chrono::seconds(1));
503 filterTimer.async_wait([&](const boost::system::error_code& ec) {
504 if (ec == boost::asio::error::operation_aborted)
505 {
506 // timer was cancelled
507 return;
508 }
509 std::cout << "rescan due to configuration change \n";
510 createSensorsFromConfig(io, objServer, systemBus, intrusionSensor);
511 });
512 };
513
514 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
515 setupPropertiesChangedMatches(
516 *systemBus, std::to_array<const char*>({sensorType}), eventHandler);
517
518 if (initializeLanStatus(systemBus))
519 {
520 // add match to monitor lan status change
521 sdbusplus::bus::match_t lanStatusMatch(
522 static_cast<sdbusplus::bus_t&>(*systemBus),
523 "type='signal', member='PropertiesChanged',"
524 "arg0namespace='org.freedesktop.network1.Link'",
525 [](sdbusplus::message_t& msg) { processLanStatusChange(msg); });
526
527 // add match to monitor entity manager signal about nic name config
528 // change
529 sdbusplus::bus::match_t lanConfigMatch(
530 static_cast<sdbusplus::bus_t&>(*systemBus),
531 "type='signal', member='PropertiesChanged',path_namespace='" +
532 std::string(inventoryPath) + "',arg0namespace='" +
533 configInterfaceName(nicType) + "'",
534 [&systemBus](sdbusplus::message_t& msg) {
535 if (msg.is_method_error())
536 {
537 std::cerr << "callback method error\n";
538 return;
539 }
540 getNicNameInfo(systemBus);
541 });
542 }
543
544 io.run();
545
546 return 0;
547 }
548