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