1 #include "sol_manager.hpp"
2
3 #include "main.hpp"
4 #include "sol_context.hpp"
5
6 #include <sys/socket.h>
7 #include <sys/un.h>
8
9 #include <boost/asio/basic_stream_socket.hpp>
10 #include <boost/asio/io_context.hpp>
11 #include <boost/asio/local/stream_protocol.hpp>
12 #include <boost/asio/write.hpp>
13 #include <ipmid/utils.hpp>
14 #include <phosphor-logging/lg2.hpp>
15 #include <sdbusplus/message/types.hpp>
16
17 #include <chrono>
18 #include <cmath>
19
20 constexpr const char* solInterface = "xyz.openbmc_project.Ipmi.SOL";
21 constexpr const char* solPath = "/xyz/openbmc_project/ipmi/sol/";
22
23 namespace sol
24 {
25
26 std::unique_ptr<sdbusplus::bus::match_t> matchPtrSOL(nullptr);
27 std::unique_ptr<sdbusplus::bus::match_t> solConfPropertiesSignal(nullptr);
28
initConsoleSocket()29 void Manager::initConsoleSocket()
30 {
31 // explicit length constructor for NUL-prefixed abstract path
32 std::string path(CONSOLE_SOCKET_PATH, CONSOLE_SOCKET_PATH_LEN);
33 boost::asio::local::stream_protocol::endpoint ep(path);
34 consoleSocket =
35 std::make_unique<boost::asio::local::stream_protocol::socket>(*io);
36 consoleSocket->connect(ep);
37 }
38
consoleInputHandler()39 void Manager::consoleInputHandler()
40 {
41 boost::system::error_code ec;
42 boost::asio::socket_base::bytes_readable cmd(true);
43 consoleSocket->io_control(cmd, ec);
44 size_t readSize;
45 if (!ec)
46 {
47 readSize = cmd.get();
48 }
49 else
50 {
51 lg2::error(
52 "Reading ready count from host console socket failed: {ERROR}",
53 "ERROR", ec.value());
54 return;
55 }
56 std::vector<uint8_t> buffer(readSize);
57 ec.clear();
58 size_t readDataLen =
59 consoleSocket->read_some(boost::asio::buffer(buffer), ec);
60 if (ec)
61 {
62 lg2::error("Reading from host console socket failed: {ERROR}", "ERROR",
63 ec.value());
64 return;
65 }
66
67 // Update the Console buffer with data read from the socket
68 buffer.resize(readDataLen);
69 dataBuffer.write(buffer);
70 }
71
writeConsoleSocket(const std::vector<uint8_t> & input,bool breakFlag) const72 int Manager::writeConsoleSocket(const std::vector<uint8_t>& input,
73 bool breakFlag) const
74 {
75 boost::system::error_code ec;
76 if (breakFlag)
77 {
78 consoleSocket->send(boost::asio::buffer(input), MSG_OOB, ec);
79 }
80 else
81 {
82 consoleSocket->send(boost::asio::buffer(input), 0, ec);
83 }
84
85 return ec.value();
86 }
87
startHostConsole()88 void Manager::startHostConsole()
89 {
90 if (!consoleSocket)
91 {
92 initConsoleSocket();
93 }
94
95 // Register callback to close SOL session for disable SSH SOL
96 if (matchPtrSOL == nullptr)
97 {
98 registerSOLServiceChangeCallback();
99 }
100
101 consoleSocket->async_wait(boost::asio::socket_base::wait_read,
102 [this](const boost::system::error_code& ec) {
103 if (!ec)
104 {
105 consoleInputHandler();
106 startHostConsole();
107 }
108 });
109 } // namespace sol
110
stopHostConsole()111 void Manager::stopHostConsole()
112 {
113 if (consoleSocket)
114 {
115 consoleSocket->cancel();
116 consoleSocket.reset();
117 }
118 }
119
updateSOLParameter(uint8_t channelNum)120 void Manager::updateSOLParameter(uint8_t channelNum)
121 {
122 sdbusplus::bus_t dbus(ipmid_get_sd_bus_connection());
123 static std::string solService{};
124 ipmi::PropertyMap properties;
125 std::string ethdevice = ipmi::getChannelName(channelNum);
126 std::string solPathWitheEthName = solPath + ethdevice;
127 if (solService.empty())
128 {
129 try
130 {
131 solService =
132 ipmi::getService(dbus, solInterface, solPathWitheEthName);
133 }
134 catch (const std::runtime_error& e)
135 {
136 solService.clear();
137 lg2::error("Get SOL service failed: {ERROR}", "ERROR", e);
138 return;
139 }
140 }
141 try
142 {
143 properties = ipmi::getAllDbusProperties(
144 dbus, solService, solPathWitheEthName, solInterface);
145 }
146 catch (const std::runtime_error& e)
147 {
148 lg2::error("Setting sol parameter: {ERROR}", "ERROR", e);
149 return;
150 }
151
152 progress = std::get<uint8_t>(properties["Progress"]);
153
154 enable = std::get<bool>(properties["Enable"]);
155
156 forceEncrypt = std::get<bool>(properties["ForceEncryption"]);
157
158 forceAuth = std::get<bool>(properties["ForceAuthentication"]);
159
160 solMinPrivilege = static_cast<session::Privilege>(
161 std::get<uint8_t>(properties["Privilege"]));
162
163 accumulateInterval =
164 std::get<uint8_t>((properties["AccumulateIntervalMS"])) *
165 sol::accIntervalFactor * 1ms;
166
167 sendThreshold = std::get<uint8_t>(properties["Threshold"]);
168
169 retryCount = std::get<uint8_t>(properties["RetryCount"]);
170
171 retryInterval = std::get<uint8_t>(properties["RetryIntervalMS"]) *
172 sol::retryIntervalFactor * 1ms;
173
174 return;
175 }
176
startPayloadInstance(uint8_t payloadInstance,session::SessionID sessionID)177 void Manager::startPayloadInstance(uint8_t payloadInstance,
178 session::SessionID sessionID)
179 {
180 if (payloadMap.empty())
181 {
182 try
183 {
184 startHostConsole();
185 }
186 catch (const std::exception& e)
187 {
188 lg2::error(
189 "Encountered exception when starting host console. Hence stopping host console: {ERROR}",
190 "ERROR", e);
191 stopHostConsole();
192 throw;
193 }
194 }
195
196 // Create the SOL Context data for payload instance
197 std::shared_ptr<Context> context = Context::makeContext(
198 io, retryCount, sendThreshold, payloadInstance, sessionID);
199
200 payloadMap.emplace(payloadInstance, std::move(context));
201 }
202
stopPayloadInstance(uint8_t payloadInstance)203 void Manager::stopPayloadInstance(uint8_t payloadInstance)
204 {
205 auto iter = payloadMap.find(payloadInstance);
206 if (iter == payloadMap.end())
207 {
208 throw std::runtime_error("SOL Payload instance not found ");
209 }
210
211 payloadMap.erase(iter);
212
213 if (payloadMap.empty())
214 {
215 stopHostConsole();
216
217 dataBuffer.erase(dataBuffer.size());
218 }
219 }
220
stopAllPayloadInstance()221 void Manager::stopAllPayloadInstance()
222 {
223 // Erase all payload instance
224 payloadMap.erase(payloadMap.begin(), payloadMap.end());
225
226 stopHostConsole();
227
228 dataBuffer.erase(dataBuffer.size());
229 }
230
registerSOLServiceChangeCallback()231 void registerSOLServiceChangeCallback()
232 {
233 using namespace sdbusplus::bus::match::rules;
234 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
235 try
236 {
237 auto servicePath = ipmi::getDbusObject(
238 bus, "xyz.openbmc_project.Control.Service.Attributes",
239 "/xyz/openbmc_project/control/service", "_6fbmc_2dconsole");
240
241 if (!std::empty(servicePath.first))
242 {
243 matchPtrSOL = std::make_unique<sdbusplus::bus::match_t>(
244 bus,
245 path_namespace(servicePath.first) +
246 "arg0namespace='xyz.openbmc_project.Control.Service."
247 "Attributes'"
248 ", " +
249 type::signal() + member("PropertiesChanged") +
250 interface("org.freedesktop.DBus.Properties"),
251 [](sdbusplus::message_t& msg) {
252 std::string intfName;
253 std::map<std::string, std::variant<bool>> properties;
254 msg.read(intfName, properties);
255
256 const auto it = properties.find("Enabled");
257 if (it != properties.end())
258 {
259 const bool* state = std::get_if<bool>(&it->second);
260
261 if (state != nullptr && *state == false)
262 {
263 // Stop all the payload session.
264 sol::Manager::get().stopAllPayloadInstance();
265 }
266 }
267 });
268 }
269 }
270 catch (const sdbusplus::exception_t& e)
271 {
272 lg2::error(
273 "Failed to get service path in registerSOLServiceChangeCallback: {ERROR}",
274 "ERROR", e);
275 }
276 }
277
procSolConfChange(sdbusplus::message_t & msg)278 void procSolConfChange(sdbusplus::message_t& msg)
279 {
280 using SolConfVariant = std::variant<bool, uint8_t>;
281 using SolConfProperties =
282 std::vector<std::pair<std::string, SolConfVariant>>;
283
284 std::string iface;
285 SolConfProperties properties;
286
287 try
288 {
289 msg.read(iface, properties);
290 }
291 catch (const std::exception& e)
292 {
293 lg2::error("procSolConfChange get properties FAIL: {ERROR}", "ERROR",
294 e);
295 return;
296 }
297
298 for (const auto& prop : properties)
299 {
300 if (prop.first == "Progress")
301 {
302 sol::Manager::get().progress = std::get<uint8_t>(prop.second);
303 }
304 else if (prop.first == "Enable")
305 {
306 sol::Manager::get().enable = std::get<bool>(prop.second);
307 }
308 else if (prop.first == "ForceEncryption")
309 {
310 sol::Manager::get().forceEncrypt = std::get<bool>(prop.second);
311 }
312 else if (prop.first == "ForceAuthentication")
313 {
314 sol::Manager::get().forceAuth = std::get<bool>(prop.second);
315 }
316 else if (prop.first == "Privilege")
317 {
318 sol::Manager::get().solMinPrivilege =
319 static_cast<session::Privilege>(std::get<uint8_t>(prop.second));
320 }
321 else if (prop.first == "AccumulateIntervalMS")
322 {
323 sol::Manager::get().accumulateInterval =
324 std::get<uint8_t>(prop.second) * sol::accIntervalFactor * 1ms;
325 }
326 else if (prop.first == "Threshold")
327 {
328 sol::Manager::get().sendThreshold = std::get<uint8_t>(prop.second);
329 }
330 else if (prop.first == "RetryCount")
331 {
332 sol::Manager::get().retryCount = std::get<uint8_t>(prop.second);
333 }
334 else if (prop.first == "RetryIntervalMS")
335 {
336 sol::Manager::get().retryInterval =
337 std::get<uint8_t>(prop.second) * sol::retryIntervalFactor * 1ms;
338 }
339 }
340 }
341
registerSolConfChangeCallbackHandler(std::string channel)342 void registerSolConfChangeCallbackHandler(std::string channel)
343 {
344 if (solConfPropertiesSignal == nullptr)
345 {
346 using namespace sdbusplus::bus::match::rules;
347 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
348 try
349 {
350 auto servicePath = solPath + channel;
351
352 solConfPropertiesSignal = std::make_unique<sdbusplus::bus::match_t>(
353 bus, propertiesChangedNamespace(servicePath, solInterface),
354 procSolConfChange);
355 }
356 catch (const sdbusplus::exception_t& e)
357 {
358 lg2::error(
359 "Failed to get service path in registerSolConfChangeCallbackHandler, channel: {CHANNEL}, error: {ERROR}",
360 "CHANNEL", channel, "ERROR", e);
361 }
362 }
363 return;
364 }
365
366 } // namespace sol
367