1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2019 Intel Corporation
4 #pragma once
5
6 #include "async_resp.hpp"
7 #include "boost_formatters.hpp"
8 #include "dbus_singleton.hpp"
9 #include "dbus_utility.hpp"
10 #include "error_messages.hpp"
11 #include "logging.hpp"
12 #include "utils/chassis_utils.hpp"
13
14 #include <boost/system/errc.hpp>
15 #include <boost/system/error_code.hpp>
16 #include <sdbusplus/message/native_types.hpp>
17
18 #include <algorithm>
19 #include <charconv>
20 #include <cstddef>
21 #include <cstdint>
22 #include <memory>
23 #include <ranges>
24 #include <span>
25 #include <string>
26 #include <string_view>
27 #include <system_error>
28 #include <tuple>
29 #include <utility>
30 #include <vector>
31
32 namespace redfish
33 {
34
35 enum NetworkProtocolUnitStructFields
36 {
37 NET_PROTO_UNIT_NAME,
38 NET_PROTO_UNIT_DESC,
39 NET_PROTO_UNIT_LOAD_STATE,
40 NET_PROTO_UNIT_ACTIVE_STATE,
41 NET_PROTO_UNIT_SUB_STATE,
42 NET_PROTO_UNIT_DEVICE,
43 NET_PROTO_UNIT_OBJ_PATH,
44 NET_PROTO_UNIT_ALWAYS_0,
45 NET_PROTO_UNIT_ALWAYS_EMPTY,
46 NET_PROTO_UNIT_ALWAYS_ROOT_PATH
47 };
48
49 enum NetworkProtocolListenResponseElements
50 {
51 NET_PROTO_LISTEN_TYPE,
52 NET_PROTO_LISTEN_STREAM
53 };
54
55 /**
56 * @brief D-Bus Unit structure returned in array from ListUnits Method
57 */
58 using UnitStruct =
59 std::tuple<std::string, std::string, std::string, std::string, std::string,
60 std::string, sdbusplus::message::object_path, uint32_t,
61 std::string, sdbusplus::message::object_path>;
62
63 template <typename CallbackFunc>
getMainChassisId(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,CallbackFunc && callback)64 void getMainChassisId(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
65 CallbackFunc&& callback)
66 {
67 // Find managed chassis
68 dbus::utility::getSubTree(
69 "/xyz/openbmc_project/inventory", 0, chassisInterfaces,
70 [callback = std::forward<CallbackFunc>(callback),
71 asyncResp](const boost::system::error_code& ec,
72 const dbus::utility::MapperGetSubTreeResponse& subtree) {
73 if (ec)
74 {
75 BMCWEB_LOG_ERROR("{}", ec);
76 return;
77 }
78 if (subtree.empty())
79 {
80 BMCWEB_LOG_DEBUG("Can't find chassis!");
81 return;
82 }
83
84 std::size_t idPos = subtree[0].first.rfind('/');
85 if (idPos == std::string::npos ||
86 (idPos + 1) >= subtree[0].first.size())
87 {
88 messages::internalError(asyncResp->res);
89 BMCWEB_LOG_DEBUG("Can't parse chassis ID!");
90 return;
91 }
92 std::string chassisId = subtree[0].first.substr(idPos + 1);
93 BMCWEB_LOG_DEBUG("chassisId = {}", chassisId);
94 callback(chassisId, asyncResp);
95 });
96 }
97
98 template <typename CallbackFunc>
getPortStatusAndPath(std::span<const std::pair<std::string_view,std::string_view>> protocolToDBus,CallbackFunc && callback)99 void getPortStatusAndPath(
100 std::span<const std::pair<std::string_view, std::string_view>>
101 protocolToDBus,
102 CallbackFunc&& callback)
103 {
104 dbus::utility::async_method_call(
105 [protocolToDBus, callback = std::forward<CallbackFunc>(callback)](
106 const boost::system::error_code& ec,
107 const std::vector<UnitStruct>& r) {
108 std::vector<std::tuple<std::string, std::string, bool>> socketData;
109 if (ec)
110 {
111 BMCWEB_LOG_ERROR("{}", ec);
112 // return error code
113 callback(ec, socketData);
114 return;
115 }
116
117 // save all service output into vector
118 for (const UnitStruct& unit : r)
119 {
120 // Only traverse through <xyz>.socket units
121 const std::string& unitName =
122 std::get<NET_PROTO_UNIT_NAME>(unit);
123
124 // find "." into unitsName
125 size_t lastCharPos = unitName.rfind('.');
126 if (lastCharPos == std::string::npos)
127 {
128 continue;
129 }
130
131 // is unitsName end with ".socket"
132 std::string unitNameEnd = unitName.substr(lastCharPos);
133 if (unitNameEnd != ".socket")
134 {
135 continue;
136 }
137
138 // find "@" into unitsName
139 if (size_t atCharPos = unitName.rfind('@');
140 atCharPos != std::string::npos)
141 {
142 lastCharPos = atCharPos;
143 }
144
145 // unitsName without "@eth(x).socket", only <xyz>
146 // unitsName without ".socket", only <xyz>
147 std::string unitNameStr = unitName.substr(0, lastCharPos);
148
149 for (const auto& kv : protocolToDBus)
150 {
151 // We are interested in services, which starts with
152 // mapped service name
153 if (unitNameStr != kv.second)
154 {
155 continue;
156 }
157
158 const std::string& socketPath =
159 std::get<NET_PROTO_UNIT_OBJ_PATH>(unit);
160 const std::string& unitState =
161 std::get<NET_PROTO_UNIT_SUB_STATE>(unit);
162
163 bool isProtocolEnabled = ((unitState == "running") ||
164 (unitState == "listening"));
165
166 // Some protocols may have multiple services associated with
167 // them (for example IPMI). Look to see if we've already
168 // added an entry for the current protocol.
169 auto find = std::ranges::find_if(
170 socketData,
171 [&kv](const std::tuple<std::string, std::string, bool>&
172 i) { return std::get<1>(i) == kv.first; });
173 if (find != socketData.end())
174 {
175 // It only takes one enabled systemd service to consider
176 // a protocol enabled so if the current entry already
177 // has it enabled (or the new one is disabled) then just
178 // continue, otherwise remove the current one and add
179 // this new one.
180 if (std::get<2>(*find) || !isProtocolEnabled)
181 {
182 // Already registered as enabled or current one is
183 // not enabled, nothing to do
184 BMCWEB_LOG_DEBUG(
185 "protocolName: {}, already true or current one is false: {}",
186 kv.first, isProtocolEnabled);
187 break;
188 }
189 // Remove existing entry and replace with new one
190 // (below)
191 socketData.erase(find);
192 }
193
194 socketData.emplace_back(socketPath, std::string(kv.first),
195 isProtocolEnabled);
196 // We found service, return from inner loop.
197 break;
198 }
199 }
200
201 callback(ec, socketData);
202 },
203 "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
204 "org.freedesktop.systemd1.Manager", "ListUnits");
205 }
206
207 template <typename CallbackFunc>
getPortNumber(const std::string & socketPath,CallbackFunc && callback)208 void getPortNumber(const std::string& socketPath, CallbackFunc&& callback)
209 {
210 dbus::utility::getProperty<
211 std::vector<std::tuple<std::string, std::string>>>(
212 *crow::connections::systemBus, "org.freedesktop.systemd1", socketPath,
213 "org.freedesktop.systemd1.Socket", "Listen",
214 [callback = std::forward<CallbackFunc>(callback)](
215 const boost::system::error_code& ec,
216 const std::vector<std::tuple<std::string, std::string>>& resp) {
217 if (ec)
218 {
219 BMCWEB_LOG_ERROR("{}", ec);
220 callback(ec, 0);
221 return;
222 }
223 if (resp.empty())
224 {
225 // Network Protocol Listen Response Elements is empty
226 boost::system::error_code ec1 =
227 boost::system::errc::make_error_code(
228 boost::system::errc::bad_message);
229 // return error code
230 callback(ec1, 0);
231 BMCWEB_LOG_ERROR("{}", ec1);
232 return;
233 }
234 const std::string& listenStream =
235 std::get<NET_PROTO_LISTEN_STREAM>(resp[0]);
236 const char* pa = &listenStream[listenStream.rfind(':') + 1];
237 int port{0};
238 if (auto [p, ec2] = std::from_chars(pa, nullptr, port);
239 ec2 != std::errc())
240 {
241 // there is only two possibility invalid_argument and
242 // result_out_of_range
243 boost::system::error_code ec3 =
244 boost::system::errc::make_error_code(
245 boost::system::errc::invalid_argument);
246 if (ec2 == std::errc::result_out_of_range)
247 {
248 ec3 = boost::system::errc::make_error_code(
249 boost::system::errc::result_out_of_range);
250 }
251 // return error code
252 callback(ec3, 0);
253 BMCWEB_LOG_ERROR("{}", ec3);
254 }
255 callback(ec, port);
256 });
257 }
258
259 } // namespace redfish
260