blob: 96da5f658b2adbc0eae98c12df477199c49dd813 [file] [log] [blame]
Qiang XUe28d1fa2019-02-27 13:50:56 +08001/*
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
Patrick Ventureca44b2f2019-10-31 11:02:26 -070017#include "ChassisIntrusionSensor.hpp"
18#include "Utils.hpp"
19
Qiang XU88b7f282019-08-14 22:51:43 +080020#include <systemd/sd-journal.h>
21
Patrick Venture96e97db2019-10-31 13:44:38 -070022#include <array>
Qiang XUe28d1fa2019-02-27 13:50:56 +080023#include <boost/algorithm/string/predicate.hpp>
24#include <boost/asio.hpp>
Patrick Venture96e97db2019-10-31 13:44:38 -070025#include <boost/container/flat_map.hpp>
Qiang XUe28d1fa2019-02-27 13:50:56 +080026#include <chrono>
27#include <ctime>
Qiang XU88b7f282019-08-14 22:51:43 +080028#include <fstream>
Patrick Venture96e97db2019-10-31 13:44:38 -070029#include <functional>
Qiang XUe28d1fa2019-02-27 13:50:56 +080030#include <iostream>
Patrick Venture96e97db2019-10-31 13:44:38 -070031#include <memory>
Qiang XUe28d1fa2019-02-27 13:50:56 +080032#include <sdbusplus/asio/connection.hpp>
33#include <sdbusplus/asio/object_server.hpp>
34#include <sdbusplus/asio/sd_event.hpp>
35#include <sdbusplus/bus.hpp>
Patrick Venture96e97db2019-10-31 13:44:38 -070036#include <sdbusplus/bus/match.hpp>
Qiang XUe28d1fa2019-02-27 13:50:56 +080037#include <sdbusplus/exception.hpp>
38#include <sdbusplus/server.hpp>
39#include <sdbusplus/timer.hpp>
Patrick Venture96e97db2019-10-31 13:44:38 -070040#include <stdexcept>
41#include <string>
42#include <utility>
43#include <vector>
Qiang XUe28d1fa2019-02-27 13:50:56 +080044
45static constexpr bool DEBUG = false;
46
47static constexpr const char* sensorType =
48 "xyz.openbmc_project.Configuration.ChassisIntrusionSensor";
Qiang XU74ddf862019-09-12 17:12:13 +080049static constexpr const char* nicType = "xyz.openbmc_project.Configuration.NIC";
50static constexpr std::array<const char*, 1> nicTypes = {nicType};
Qiang XUe28d1fa2019-02-27 13:50:56 +080051
Qiang XU88b7f282019-08-14 22:51:43 +080052namespace fs = std::filesystem;
53
Qiang XUe28d1fa2019-02-27 13:50:56 +080054static bool getIntrusionSensorConfig(
55 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
56 IntrusionSensorType* pType, int* pBusId, int* pSlaveAddr, int* pGpioIndex,
57 bool* pGpioInverted)
58{
59 // find matched configuration according to sensor type
60 ManagedObjectType sensorConfigurations;
61 bool useCache = false;
62
63 if (!getSensorConfiguration(sensorType, dbusConnection,
64 sensorConfigurations, useCache))
65 {
66 std::cerr << "error communicating to entity manager\n";
67 return false;
68 }
69
70 const SensorData* sensorData = nullptr;
71 const std::pair<std::string,
72 boost::container::flat_map<std::string, BasicVariantType>>*
73 baseConfiguration = nullptr;
74
75 // Get bus and addr of matched configuration
76 for (const std::pair<sdbusplus::message::object_path, SensorData>& sensor :
77 sensorConfigurations)
78 {
79 baseConfiguration = nullptr;
80 sensorData = &(sensor.second);
81
82 // match sensor type
83 auto sensorBase = sensorData->find(sensorType);
84 if (sensorBase == sensorData->end())
85 {
86 std::cerr << "error finding base configuration \n";
87 continue;
88 }
89
90 baseConfiguration = &(*sensorBase);
91
92 // judge class, "Gpio" or "I2C"
93 auto findClass = baseConfiguration->second.find("Class");
94 if (findClass != baseConfiguration->second.end() &&
95 sdbusplus::message::variant_ns::get<std::string>(
96 findClass->second) == "Gpio")
97 {
98 *pType = IntrusionSensorType::gpio;
99 }
100 else
101 {
102 *pType = IntrusionSensorType::pch;
103 }
104
105 // case to find GPIO info
106 if (*pType == IntrusionSensorType::gpio)
107 {
108 auto gpioConfig =
109 sensorData->find(sensorType + std::string(".GpioIntrusion"));
110
111 if (gpioConfig == sensorData->end())
112 {
113 std::cerr
114 << "error finding GpioIntrusion info in configuration \n";
115 continue;
116 }
117
118 auto findGpioIndex = gpioConfig->second.find("Index");
119 auto findGpioPolarity = gpioConfig->second.find("Polarity");
120
121 if (findGpioIndex == gpioConfig->second.end() ||
122 findGpioPolarity == gpioConfig->second.end())
123 {
124 std::cerr << "error finding gpio info in configuration \n";
125 continue;
126 }
127
128 try
129 {
130 *pGpioIndex = sdbusplus::message::variant_ns::get<uint64_t>(
131 findGpioIndex->second);
132 *pGpioInverted =
133 (sdbusplus::message::variant_ns::get<std::string>(
134 findGpioPolarity->second) == "Low");
135 }
136 catch (const std::bad_variant_access& e)
137 {
138 std::cerr << "invalid value for gpio info in config. \n";
139 continue;
140 }
141
142 if (DEBUG)
143 {
144 std::cout << "find matched GPIO index " << *pGpioIndex
145 << ", polarity inverted flag is " << *pGpioInverted
146 << "\n";
147 }
148
149 return true;
150 }
151
152 // case to find I2C info
153 else if (*pType == IntrusionSensorType::pch)
154 {
155 auto findBus = baseConfiguration->second.find("Bus");
156 auto findAddress = baseConfiguration->second.find("Address");
157 if (findBus == baseConfiguration->second.end() ||
158 findAddress == baseConfiguration->second.end())
159 {
160 std::cerr << "error finding bus or address in configuration \n";
161 continue;
162 }
163
164 try
165 {
166 *pBusId = sdbusplus::message::variant_ns::get<uint64_t>(
167 findBus->second);
168 *pSlaveAddr = sdbusplus::message::variant_ns::get<uint64_t>(
169 findAddress->second);
170 }
171 catch (const std::bad_variant_access& e)
172 {
173 std::cerr << "invalid value for bus or address in config. \n";
174 continue;
175 }
176
177 if (DEBUG)
178 {
179 std::cout << "find matched bus " << *pBusId
180 << ", matched slave addr " << *pSlaveAddr << "\n";
181 }
182 return true;
183 }
184 }
185
Qiang XU74ddf862019-09-12 17:12:13 +0800186 std::cerr << "can't find matched I2C or GPIO configuration for intrusion "
187 "sensor. \n";
Qiang XUe28d1fa2019-02-27 13:50:56 +0800188 *pBusId = -1;
189 *pSlaveAddr = -1;
190 *pGpioIndex = -1;
191 return false;
192}
193
Qiang XU88b7f282019-08-14 22:51:43 +0800194static constexpr bool debugLanLeash = false;
195boost::container::flat_map<int, bool> lanStatusMap;
Qiang XU74ddf862019-09-12 17:12:13 +0800196boost::container::flat_map<int, std::string> lanInfoMap;
Qiang XU88b7f282019-08-14 22:51:43 +0800197boost::container::flat_map<std::string, int> pathSuffixMap;
198
Qiang XU74ddf862019-09-12 17:12:13 +0800199static void
200 getNicNameInfo(std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
201{
202 auto getter = std::make_shared<GetSensorConfiguration>(
203 dbusConnection,
204 std::move([](const ManagedObjectType& sensorConfigurations) {
205 // Get NIC name and save to map
206 lanInfoMap.clear();
207 for (const std::pair<sdbusplus::message::object_path, SensorData>&
208 sensor : sensorConfigurations)
209 {
210 const std::pair<
211 std::string,
212 boost::container::flat_map<std::string, BasicVariantType>>*
213 baseConfiguration = nullptr;
214
215 // find base configuration
216 auto sensorBase = sensor.second.find(nicType);
217 if (sensorBase == sensor.second.end())
218 {
219 continue;
220 }
221 baseConfiguration = &(*sensorBase);
222
223 auto findEthIndex = baseConfiguration->second.find("EthIndex");
224 auto findName = baseConfiguration->second.find("Name");
225
226 if (findEthIndex != baseConfiguration->second.end() &&
227 findName != baseConfiguration->second.end())
228 {
229 auto* pEthIndex =
230 std::get_if<uint64_t>(&findEthIndex->second);
231 auto* pName = std::get_if<std::string>(&findName->second);
232 if (pEthIndex != nullptr && pName != nullptr)
233 {
234 lanInfoMap[*pEthIndex] = *pName;
235 if (debugLanLeash)
236 {
237 std::cout << "find name of eth" << *pEthIndex
238 << " is " << *pName << "\n";
239 }
240 }
241 }
242 }
243
244 if (lanInfoMap.size() == 0)
245 {
246 std::cerr << "can't find matched NIC name. \n";
247 }
248 }));
249
250 getter->getConfiguration(
251 std::vector<std::string>{nicTypes.begin(), nicTypes.end()});
252}
253
Qiang XU88b7f282019-08-14 22:51:43 +0800254static void processLanStatusChange(sdbusplus::message::message& message)
255{
256 const std::string& pathName = message.get_path();
257 std::string interfaceName;
258 boost::container::flat_map<std::string, BasicVariantType> properties;
259 message.read(interfaceName, properties);
260
261 auto findStateProperty = properties.find("OperationalState");
262 if (findStateProperty == properties.end())
263 {
264 return;
265 }
Qiang XU74ddf862019-09-12 17:12:13 +0800266 std::string* pState =
267 std::get_if<std::string>(&(findStateProperty->second));
Qiang XU88b7f282019-08-14 22:51:43 +0800268 if (pState == nullptr)
269 {
270 std::cerr << "invalid OperationalState \n";
271 return;
272 }
273
274 bool newLanConnected = (*pState == "routable" || *pState == "carrier" ||
275 *pState == "degraded");
276
277 // get ethNum from path. /org/freedesktop/network1/link/_32 for eth0
Brad Bishopfbb44ad2019-11-08 09:42:37 -0500278 size_t pos = pathName.find("/_");
Qiang XU88b7f282019-08-14 22:51:43 +0800279 if (pos == std::string::npos || pathName.length() <= pos + 2)
280 {
281 std::cerr << "unexpected path name " << pathName << "\n";
282 return;
283 }
284 std::string suffixStr = pathName.substr(pos + 2);
285
286 auto findEthNum = pathSuffixMap.find(suffixStr);
287 if (findEthNum == pathSuffixMap.end())
288 {
289 std::cerr << "unexpected eth for suffixStr " << suffixStr << "\n";
290 return;
291 }
292 int ethNum = findEthNum->second;
Qiang XU74ddf862019-09-12 17:12:13 +0800293
294 // get lan status from map
Qiang XU88b7f282019-08-14 22:51:43 +0800295 auto findLanStatus = lanStatusMap.find(ethNum);
296 if (findLanStatus == lanStatusMap.end())
297 {
298 std::cerr << "unexpected eth " << ethNum << " in lanStatusMap \n";
299 return;
300 }
301 bool oldLanConnected = findLanStatus->second;
302
Qiang XU74ddf862019-09-12 17:12:13 +0800303 // get lan info from map
304 std::string lanInfo = "";
305 if (lanInfoMap.size() > 0)
306 {
307 auto findLanInfo = lanInfoMap.find(ethNum);
308 if (findLanInfo == lanInfoMap.end())
309 {
310 std::cerr << "unexpected eth " << ethNum << " in lanInfoMap \n";
311 }
312 else
313 {
314 lanInfo = "(" + findLanInfo->second + ")";
315 }
316 }
317
Qiang XU88b7f282019-08-14 22:51:43 +0800318 if (debugLanLeash)
319 {
320 std::cout << "ethNum = " << ethNum << ", state = " << *pState
321 << ", oldLanConnected = "
322 << (oldLanConnected ? "true" : "false")
323 << ", newLanConnected = "
324 << (newLanConnected ? "true" : "false") << "\n";
325 }
326
327 if (oldLanConnected != newLanConnected)
328 {
Qiang XU74ddf862019-09-12 17:12:13 +0800329 std::string strEthNum = "eth" + std::to_string(ethNum) + lanInfo;
330 std::string strEvent = strEthNum + " LAN leash " +
331 (newLanConnected ? "connected" : "lost");
332 std::string strMsgId =
333 newLanConnected ? "OpenBMC.0.1.LanRegained" : "OpenBMC.0.1.LanLost";
334 sd_journal_send("MESSAGE=%s", strEvent.c_str(), "PRIORITY=%i", LOG_INFO,
Qiang XU88b7f282019-08-14 22:51:43 +0800335 "REDFISH_MESSAGE_ID=%s", strMsgId.c_str(),
Qiang XU74ddf862019-09-12 17:12:13 +0800336 "REDFISH_MESSAGE_ARGS=%s", strEthNum.c_str(), NULL);
Qiang XU88b7f282019-08-14 22:51:43 +0800337 lanStatusMap[ethNum] = newLanConnected;
338 if (debugLanLeash)
339 {
Qiang XU74ddf862019-09-12 17:12:13 +0800340 std::cout << "log redfish event: " << strEvent << "\n";
Qiang XU88b7f282019-08-14 22:51:43 +0800341 }
342 }
343}
344
345static void
346 monitorLanStatusChange(std::shared_ptr<sdbusplus::asio::connection> conn)
347{
Qiang XU74ddf862019-09-12 17:12:13 +0800348 // init lan port name from configuration
349 getNicNameInfo(conn);
350
351 // get eth info from sysfs
Qiang XU88b7f282019-08-14 22:51:43 +0800352 std::vector<fs::path> files;
353 if (!findFiles(fs::path("/sys/class/net/"), R"(eth\d+/ifindex)", files))
354 {
355 std::cerr << "No eth in system\n";
356 return;
357 }
358
359 // iterate through all found eth files, and save ifindex
360 for (auto& fileName : files)
361 {
362 if (debugLanLeash)
363 {
364 std::cout << "Reading " << fileName << "\n";
365 }
366 std::ifstream sysFile(fileName);
367 if (!sysFile.good())
368 {
369 std::cerr << "Failure reading " << fileName << "\n";
370 continue;
371 }
372 std::string line;
373 getline(sysFile, line);
374 const uint8_t ifindex = std::stoi(line);
375 // pathSuffix is ASCII of ifindex
376 const std::string& pathSuffix = std::to_string(ifindex + 30);
377
378 // extract ethNum
379 const std::string& fileStr = fileName.string();
380 const int pos = fileStr.find("eth");
381 const std::string& ethNumStr = fileStr.substr(pos + 3);
382 int ethNum = 0;
383 try
384 {
385 ethNum = std::stoul(ethNumStr);
386 }
387 catch (const std::invalid_argument& err)
388 {
389 std::cerr << "invalid ethNum string: " << ethNumStr << "\n";
390 continue;
391 }
392
393 // save pathSuffix
394 pathSuffixMap[pathSuffix] = ethNum;
395 if (debugLanLeash)
396 {
397 std::cout << "ethNum = " << std::to_string(ethNum)
398 << ", ifindex = " << line
399 << ", pathSuffix = " << pathSuffix << "\n";
400 }
401
Qiang XU74ddf862019-09-12 17:12:13 +0800402 // init lan connected status from networkd
Qiang XU88b7f282019-08-14 22:51:43 +0800403 conn->async_method_call(
404 [ethNum](boost::system::error_code ec,
405 const std::variant<std::string>& property) {
Qiang XU74ddf862019-09-12 17:12:13 +0800406 lanStatusMap[ethNum] = false;
Qiang XU88b7f282019-08-14 22:51:43 +0800407 if (ec)
408 {
Qiang XU74ddf862019-09-12 17:12:13 +0800409 std::cerr << "Error reading init status of eth" << ethNum
410 << "\n";
Qiang XU88b7f282019-08-14 22:51:43 +0800411 return;
412 }
413 const std::string* pState = std::get_if<std::string>(&property);
414 if (pState == nullptr)
415 {
416 std::cerr << "Unable to read lan status value\n";
417 return;
418 }
419 bool isLanConnected =
420 (*pState == "routable" || *pState == "carrier" ||
421 *pState == "degraded");
422 if (debugLanLeash)
423 {
424 std::cout << "ethNum = " << std::to_string(ethNum)
425 << ", init LAN status = "
426 << (isLanConnected ? "true" : "false") << "\n";
427 }
428 lanStatusMap[ethNum] = isLanConnected;
429 },
430 "org.freedesktop.network1",
431 "/org/freedesktop/network1/link/_" + pathSuffix,
432 "org.freedesktop.DBus.Properties", "Get",
433 "org.freedesktop.network1.Link", "OperationalState");
434 }
435
436 // add match to monitor lan status change
437 static sdbusplus::bus::match::match match(
438 static_cast<sdbusplus::bus::bus&>(*conn),
439 "type='signal', member='PropertiesChanged',"
440 "arg0namespace='org.freedesktop.network1.Link'",
441 [](sdbusplus::message::message& msg) { processLanStatusChange(msg); });
Qiang XU74ddf862019-09-12 17:12:13 +0800442
443 // add match to monitor entity manager signal about nic name config change
444 static sdbusplus::bus::match::match match2(
445 static_cast<sdbusplus::bus::bus&>(*conn),
446 "type='signal', member='PropertiesChanged',path_namespace='" +
447 std::string(inventoryPath) + "',arg0namespace='" + nicType + "'",
448 [&conn](sdbusplus::message::message& msg) {
449 if (msg.is_method_error())
450 {
451 std::cerr << "callback method error\n";
452 return;
453 }
454 getNicNameInfo(conn);
455 });
Qiang XU88b7f282019-08-14 22:51:43 +0800456}
457
Qiang XUe28d1fa2019-02-27 13:50:56 +0800458int main()
459{
460 int busId = -1, slaveAddr = -1, gpioIndex = -1;
461 bool gpioInverted = false;
462 IntrusionSensorType type = IntrusionSensorType::gpio;
463
464 // setup connection to dbus
465 boost::asio::io_service io;
466 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
467 auto objServer = sdbusplus::asio::object_server(systemBus);
468
469 // setup object server, define interface
470 systemBus->request_name("xyz.openbmc_project.IntrusionSensor");
471
472 std::shared_ptr<sdbusplus::asio::dbus_interface> ifaceChassis =
473 objServer.add_interface(
474 "/xyz/openbmc_project/Intrusion/Chassis_Intrusion",
475 "xyz.openbmc_project.Chassis.Intrusion");
476
477 ChassisIntrusionSensor chassisIntrusionSensor(io, ifaceChassis);
478
479 if (getIntrusionSensorConfig(systemBus, &type, &busId, &slaveAddr,
480 &gpioIndex, &gpioInverted))
481 {
482 chassisIntrusionSensor.start(type, busId, slaveAddr, gpioIndex,
483 gpioInverted);
484 }
485
486 // callback to handle configuration change
487 std::function<void(sdbusplus::message::message&)> eventHandler =
488 [&](sdbusplus::message::message& message) {
489 if (message.is_method_error())
490 {
491 std::cerr << "callback method error\n";
492 return;
493 }
494
495 std::cout << "rescan due to configuration change \n";
496 if (getIntrusionSensorConfig(systemBus, &type, &busId, &slaveAddr,
497 &gpioIndex, &gpioInverted))
498 {
499 chassisIntrusionSensor.start(type, busId, slaveAddr, gpioIndex,
500 gpioInverted);
501 }
502 };
503
504 auto match = std::make_unique<sdbusplus::bus::match::match>(
505 static_cast<sdbusplus::bus::bus&>(*systemBus),
506 "type='signal',member='PropertiesChanged',path_namespace='" +
507 std::string(inventoryPath) + "',arg0namespace='" + sensorType + "'",
508 eventHandler);
509
Qiang XU88b7f282019-08-14 22:51:43 +0800510 monitorLanStatusChange(systemBus);
511
Qiang XUe28d1fa2019-02-27 13:50:56 +0800512 io.run();
513
514 return 0;
515}