xref: /openbmc/bmcweb/features/redfish/lib/network_protocol.hpp (revision 4ed69245822e548763e7d4ba7bdb9f5cdba75c22)
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 #pragma once
17 
18 #include "error_messages.hpp"
19 #include "node.hpp"
20 
21 #include <optional>
22 #include <utils/json_utils.hpp>
23 #include <variant>
24 namespace redfish
25 {
26 
27 enum NetworkProtocolUnitStructFields
28 {
29     NET_PROTO_UNIT_NAME,
30     NET_PROTO_UNIT_DESC,
31     NET_PROTO_UNIT_LOAD_STATE,
32     NET_PROTO_UNIT_ACTIVE_STATE,
33     NET_PROTO_UNIT_SUB_STATE,
34     NET_PROTO_UNIT_DEVICE,
35     NET_PROTO_UNIT_OBJ_PATH,
36     NET_PROTO_UNIT_ALWAYS_0,
37     NET_PROTO_UNIT_ALWAYS_EMPTY,
38     NET_PROTO_UNIT_ALWAYS_ROOT_PATH
39 };
40 
41 enum NetworkProtocolListenResponseElements
42 {
43     NET_PROTO_LISTEN_TYPE,
44     NET_PROTO_LISTEN_STREAM
45 };
46 
47 /**
48  * @brief D-Bus Unit structure returned in array from ListUnits Method
49  */
50 using UnitStruct =
51     std::tuple<std::string, std::string, std::string, std::string, std::string,
52                std::string, sdbusplus::message::object_path, uint32_t,
53                std::string, sdbusplus::message::object_path>;
54 
55 struct ServiceConfiguration
56 {
57     const char* serviceName;
58     const char* socketPath;
59 };
60 
61 const static boost::container::flat_map<const char*, ServiceConfiguration>
62     protocolToDBus{
63         {"SSH",
64          {"dropbear.socket",
65           "/org/freedesktop/systemd1/unit/dropbear_2esocket"}},
66         {"HTTPS",
67          {"bmcweb.service",
68           "/org/freedesktop/systemd1/unit/"
69           "bmcweb_2esocket"}}, //"/org/freedesktop/systemd1/unit/phosphor_2dgevent_2esocket"}},
70         {"IPMI",
71          {"phosphor-ipmi-net.socket", "/org/freedesktop/systemd1/unit/"
72                                       "phosphor_2dipmi_2dnet_2esocket"}}};
73 
74 inline void extractNTPServersData(const GetManagedObjects& dbus_data,
75                                   std::vector<std::string>& ntpData)
76 {
77     for (const auto& obj : dbus_data)
78     {
79         for (const auto& ifacePair : obj.second)
80         {
81             if (obj.first == "/xyz/openbmc_project/network/eth0")
82             {
83                 if (ifacePair.first ==
84                     "xyz.openbmc_project.Network.EthernetInterface")
85                 {
86                     for (const auto& propertyPair : ifacePair.second)
87                     {
88                         if (propertyPair.first == "NTPServers")
89                         {
90                             const std::vector<std::string>* ntpServers =
91                                 sdbusplus::message::variant_ns::get_if<
92                                     std::vector<std::string>>(
93                                     &propertyPair.second);
94                             if (ntpServers != nullptr)
95                             {
96                                 ntpData = std::move(*ntpServers);
97                             }
98                         }
99                     }
100                 }
101             }
102         }
103     }
104 }
105 
106 template <typename CallbackFunc>
107 void getEthernetIfaceData(CallbackFunc&& callback)
108 {
109     crow::connections::systemBus->async_method_call(
110         [callback{std::move(callback)}](
111             const boost::system::error_code error_code,
112             const GetManagedObjects& dbus_data) {
113             std::vector<std::string> ntpServers;
114 
115             if (error_code)
116             {
117                 callback(false, ntpServers);
118                 return;
119             }
120 
121             extractNTPServersData(dbus_data, ntpServers);
122 
123             callback(true, ntpServers);
124         },
125         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
126         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
127 };
128 
129 class NetworkProtocol : public Node
130 {
131   public:
132     NetworkProtocol(CrowApp& app) :
133         Node(app, "/redfish/v1/Managers/bmc/NetworkProtocol")
134     {
135         entityPrivileges = {
136             {boost::beast::http::verb::get, {{"Login"}}},
137             {boost::beast::http::verb::head, {{"Login"}}},
138             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
139             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
140             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
141             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
142     }
143 
144   private:
145     void doGet(crow::Response& res, const crow::Request& req,
146                const std::vector<std::string>& params) override
147     {
148         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
149 
150         getData(asyncResp);
151     }
152 
153     std::string getHostName() const
154     {
155         std::string hostName;
156 
157         std::array<char, HOST_NAME_MAX> hostNameCStr;
158         if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0)
159         {
160             hostName = hostNameCStr.data();
161         }
162         return hostName;
163     }
164 
165     void getNTPProtocolEnabled(const std::shared_ptr<AsyncResp>& asyncResp)
166     {
167         crow::connections::systemBus->async_method_call(
168             [asyncResp](const boost::system::error_code error_code,
169                         const std::variant<std::string>& timeSyncMethod) {
170                 const std::string* s =
171                     std::get_if<std::string>(&timeSyncMethod);
172 
173                 if (*s == "xyz.openbmc_project.Time.Synchronization.Method.NTP")
174                 {
175                     asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
176                 }
177                 else if (*s == "xyz.openbmc_project.Time.Synchronization."
178                                "Method.Manual")
179                 {
180                     asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
181                 }
182             },
183             "xyz.openbmc_project.Settings",
184             "/xyz/openbmc_project/time/sync_method",
185             "org.freedesktop.DBus.Properties", "Get",
186             "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod");
187     }
188 
189     void getData(const std::shared_ptr<AsyncResp>& asyncResp)
190     {
191         asyncResp->res.jsonValue["@odata.type"] =
192             "#ManagerNetworkProtocol.v1_4_0.ManagerNetworkProtocol";
193         asyncResp->res.jsonValue["@odata.id"] =
194             "/redfish/v1/Managers/bmc/NetworkProtocol";
195         asyncResp->res.jsonValue["@odata.context"] =
196             "/redfish/v1/"
197             "$metadata#ManagerNetworkProtocol.ManagerNetworkProtocol";
198         asyncResp->res.jsonValue["Id"] = "NetworkProtocol";
199         asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
200         asyncResp->res.jsonValue["Description"] = "Manager Network Service";
201         asyncResp->res.jsonValue["Status"]["Health"] = "OK";
202         asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
203         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
204 
205         for (auto& protocol : protocolToDBus)
206         {
207             asyncResp->res.jsonValue[protocol.first]["ProtocolEnabled"] = false;
208         }
209 
210         asyncResp->res.jsonValue["HostName"] = getHostName();
211 
212         getNTPProtocolEnabled(asyncResp);
213 
214         // TODO Get eth0 interface data, and call the below callback for JSON
215         // preparation
216         getEthernetIfaceData(
217             [this, asyncResp](const bool& success,
218                               const std::vector<std::string>& ntpServers) {
219                 if (!success)
220                 {
221                     messages::resourceNotFound(asyncResp->res,
222                                                "EthernetInterface", "eth0");
223                     return;
224                 }
225                 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
226             });
227 
228         crow::connections::systemBus->async_method_call(
229             [asyncResp](const boost::system::error_code ec,
230                         const std::vector<UnitStruct>& resp) {
231                 if (ec)
232                 {
233                     asyncResp->res.jsonValue = nlohmann::json::object();
234                     messages::internalError(asyncResp->res);
235                     return;
236                 }
237 
238                 for (auto& unit : resp)
239                 {
240                     for (auto& kv : protocolToDBus)
241                     {
242                         if (kv.second.serviceName !=
243                             std::get<NET_PROTO_UNIT_NAME>(unit))
244                         {
245                             continue;
246                         }
247                         const char* service = kv.first;
248                         const char* socketPath = kv.second.socketPath;
249 
250                         asyncResp->res.jsonValue[service]["ProtocolEnabled"] =
251                             (std::get<NET_PROTO_UNIT_SUB_STATE>(unit) ==
252                              "running") ||
253                             (std::get<NET_PROTO_UNIT_SUB_STATE>(unit) ==
254                              "listening");
255 
256                         crow::connections::systemBus->async_method_call(
257                             [asyncResp, service{std::string(service)}](
258                                 const boost::system::error_code ec,
259                                 const std::variant<std::vector<std::tuple<
260                                     std::string, std::string>>>& resp) {
261                                 if (ec)
262                                 {
263                                     messages::internalError(asyncResp->res);
264                                     return;
265                                 }
266                                 const std::vector<
267                                     std::tuple<std::string, std::string>>*
268                                     responsePtr = std::get_if<std::vector<
269                                         std::tuple<std::string, std::string>>>(
270                                         &resp);
271                                 if (responsePtr == nullptr ||
272                                     responsePtr->size() < 1)
273                                 {
274                                     return;
275                                 }
276 
277                                 const std::string& listenStream =
278                                     std::get<NET_PROTO_LISTEN_STREAM>(
279                                         (*responsePtr)[0]);
280                                 std::size_t lastColonPos =
281                                     listenStream.rfind(":");
282                                 if (lastColonPos == std::string::npos)
283                                 {
284                                     // Not a port
285                                     return;
286                                 }
287                                 std::string portStr =
288                                     listenStream.substr(lastColonPos + 1);
289                                 char* endPtr = nullptr;
290                                 // Use strtol instead of stroi to avoid
291                                 // exceptions
292                                 long port =
293                                     std::strtol(portStr.c_str(), &endPtr, 10);
294 
295                                 if (*endPtr != '\0' || portStr.empty())
296                                 {
297                                     // Invalid value
298                                     asyncResp->res.jsonValue[service]["Port"] =
299                                         nullptr;
300                                 }
301                                 else
302                                 {
303                                     // Everything OK
304                                     asyncResp->res.jsonValue[service]["Port"] =
305                                         port;
306                                 }
307                             },
308                             "org.freedesktop.systemd1", socketPath,
309                             "org.freedesktop.DBus.Properties", "Get",
310                             "org.freedesktop.systemd1.Socket", "Listen");
311                     }
312                 }
313             },
314             "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
315             "org.freedesktop.systemd1.Manager", "ListUnits");
316     }
317 
318     void handleHostnamePatch(const std::string& hostName,
319                              const std::shared_ptr<AsyncResp>& asyncResp)
320     {
321         crow::connections::systemBus->async_method_call(
322             [asyncResp](const boost::system::error_code ec) {
323                 if (ec)
324                 {
325                     messages::internalError(asyncResp->res);
326                     return;
327                 }
328                 messages::success(asyncResp->res);
329             },
330             "xyz.openbmc_project.Network",
331             "/xyz/openbmc_project/network/config",
332             "org.freedesktop.DBus.Properties", "Set",
333             "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
334             std::variant<std::string>(hostName));
335     }
336 
337     void handleNTPProtocolEnabled(const bool& ntpEnabled,
338                                   const std::shared_ptr<AsyncResp>& asyncResp)
339     {
340         std::string timeSyncMethod;
341         if (ntpEnabled)
342         {
343             timeSyncMethod =
344                 "xyz.openbmc_project.Time.Synchronization.Method.NTP";
345         }
346         else
347         {
348             timeSyncMethod =
349                 "xyz.openbmc_project.Time.Synchronization.Method.Manual";
350         }
351 
352         crow::connections::systemBus->async_method_call(
353             [asyncResp,
354              ntpEnabled](const boost::system::error_code error_code) {
355                 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = ntpEnabled;
356             },
357             "xyz.openbmc_project.Settings",
358             "/xyz/openbmc_project/time/sync_method",
359             "org.freedesktop.DBus.Properties", "Set",
360             "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
361             std::variant<std::string>{timeSyncMethod});
362     }
363 
364     void handleNTPServersPatch(const std::vector<std::string>& ntpServers,
365                                const std::shared_ptr<AsyncResp>& asyncResp)
366     {
367         crow::connections::systemBus->async_method_call(
368             [asyncResp, ntpServers](const boost::system::error_code ec) {
369                 if (ec)
370                 {
371                     messages::internalError(asyncResp->res);
372                     return;
373                 }
374                 asyncResp->res.jsonValue["NTP"]["NTPServers"] =
375                     std::move(ntpServers);
376             },
377             "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0",
378             "org.freedesktop.DBus.Properties", "Set",
379             "xyz.openbmc_project.Network.EthernetInterface", "NTPServers",
380             std::variant<std::vector<std::string>>{ntpServers});
381     }
382 
383     void doPatch(crow::Response& res, const crow::Request& req,
384                  const std::vector<std::string>& params) override
385     {
386         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
387         std::optional<std::string> newHostName;
388         std::optional<std::vector<std::string>> ntpServers;
389         std::optional<bool> ntpEnabled;
390 
391         if (!json_util::readJson(req, res, "HostName", newHostName,
392                                  "NTPServers", ntpServers, "NTPEnabled",
393                                  ntpEnabled))
394         {
395             return;
396         }
397         if (newHostName)
398         {
399             handleHostnamePatch(*newHostName, asyncResp);
400             return;
401         }
402         if (ntpEnabled)
403         {
404             handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
405         }
406         if (ntpServers)
407         {
408             handleNTPServersPatch(*ntpServers, asyncResp);
409         }
410     }
411 };
412 
413 } // namespace redfish
414