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