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 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 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 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 */ 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<fs::path> files; 393 if (!findFiles(fs::path("/sys/class/net/"), R"(eth\d+/ifindex)", files)) 394 { 395 std::cerr << "No eth in system\n"; 396 return false; 397 } 398 399 // iterate through all found eth files, and save ifindex 400 for (const fs::path& fileName : files) 401 { 402 if (debugLanLeash) 403 { 404 std::cout << "Reading " << fileName << "\n"; 405 } 406 std::ifstream sysFile(fileName); 407 if (!sysFile.good()) 408 { 409 std::cerr << "Failure reading " << fileName << "\n"; 410 continue; 411 } 412 std::string line; 413 getline(sysFile, line); 414 const uint8_t ifindex = std::stoi(line); 415 // pathSuffix is ASCII of ifindex 416 const std::string& pathSuffix = std::to_string(ifindex + 30); 417 418 // extract ethNum 419 const std::string& fileStr = fileName.string(); 420 const int pos = fileStr.find("eth"); 421 const std::string& ethNumStr = fileStr.substr(pos + 3); 422 int ethNum = 0; 423 std::from_chars_result r = std::from_chars( 424 ethNumStr.data(), ethNumStr.data() + ethNumStr.size(), ethNum); 425 if (r.ec != std::errc()) 426 { 427 std::cerr << "invalid ethNum string: " << ethNumStr << "\n"; 428 continue; 429 } 430 431 // save pathSuffix 432 pathSuffixMap[pathSuffix] = ethNum; 433 if (debugLanLeash) 434 { 435 std::cout << "ethNum = " << std::to_string(ethNum) << ", ifindex = " 436 << line << ", 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 447 << "Error reading init status of eth" << ethNum << "\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 = 457 (*pState == "routable" || *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 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, 511 intrusionSensor); 512 }); 513 }; 514 515 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches = 516 setupPropertiesChangedMatches( 517 *systemBus, std::to_array<const char*>({sensorType}), eventHandler); 518 519 if (initializeLanStatus(systemBus)) 520 { 521 // add match to monitor lan status change 522 sdbusplus::bus::match_t lanStatusMatch( 523 static_cast<sdbusplus::bus_t&>(*systemBus), 524 "type='signal', member='PropertiesChanged'," 525 "arg0namespace='org.freedesktop.network1.Link'", 526 [](sdbusplus::message_t& msg) { processLanStatusChange(msg); }); 527 528 // add match to monitor entity manager signal about nic name config 529 // change 530 sdbusplus::bus::match_t lanConfigMatch( 531 static_cast<sdbusplus::bus_t&>(*systemBus), 532 "type='signal', member='PropertiesChanged',path_namespace='" + 533 std::string(inventoryPath) + "',arg0namespace='" + 534 configInterfaceName(nicType) + "'", 535 [&systemBus](sdbusplus::message_t& msg) { 536 if (msg.is_method_error()) 537 { 538 std::cerr << "callback method error\n"; 539 return; 540 } 541 getNicNameInfo(systemBus); 542 }); 543 } 544 545 io.run(); 546 547 return 0; 548 } 549