xref: /openbmc/bmcweb/features/redfish/lib/network_protocol.hpp (revision 5968caee7ccf978755a9a63d225965101a0c0378)
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                 asyncResp->res.jsonValue["HTTPS"]["Certificates"] = {
238                     {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol/"
239                                   "HTTPS/Certificates/"}};
240 
241                 for (auto& unit : resp)
242                 {
243                     for (auto& kv : protocolToDBus)
244                     {
245                         if (kv.second.serviceName !=
246                             std::get<NET_PROTO_UNIT_NAME>(unit))
247                         {
248                             continue;
249                         }
250                         const char* service = kv.first;
251                         const char* socketPath = kv.second.socketPath;
252 
253                         asyncResp->res.jsonValue[service]["ProtocolEnabled"] =
254                             (std::get<NET_PROTO_UNIT_SUB_STATE>(unit) ==
255                              "running") ||
256                             (std::get<NET_PROTO_UNIT_SUB_STATE>(unit) ==
257                              "listening");
258                         crow::connections::systemBus->async_method_call(
259                             [asyncResp, service{std::string(service)}](
260                                 const boost::system::error_code ec,
261                                 const std::variant<std::vector<std::tuple<
262                                     std::string, std::string>>>& resp) {
263                                 if (ec)
264                                 {
265                                     messages::internalError(asyncResp->res);
266                                     return;
267                                 }
268                                 const std::vector<
269                                     std::tuple<std::string, std::string>>*
270                                     responsePtr = std::get_if<std::vector<
271                                         std::tuple<std::string, std::string>>>(
272                                         &resp);
273                                 if (responsePtr == nullptr ||
274                                     responsePtr->size() < 1)
275                                 {
276                                     return;
277                                 }
278 
279                                 const std::string& listenStream =
280                                     std::get<NET_PROTO_LISTEN_STREAM>(
281                                         (*responsePtr)[0]);
282                                 std::size_t lastColonPos =
283                                     listenStream.rfind(":");
284                                 if (lastColonPos == std::string::npos)
285                                 {
286                                     // Not a port
287                                     return;
288                                 }
289                                 std::string portStr =
290                                     listenStream.substr(lastColonPos + 1);
291                                 char* endPtr = nullptr;
292                                 // Use strtol instead of stroi to avoid
293                                 // exceptions
294                                 long port =
295                                     std::strtol(portStr.c_str(), &endPtr, 10);
296 
297                                 if (*endPtr != '\0' || portStr.empty())
298                                 {
299                                     // Invalid value
300                                     asyncResp->res.jsonValue[service]["Port"] =
301                                         nullptr;
302                                 }
303                                 else
304                                 {
305                                     // Everything OK
306                                     asyncResp->res.jsonValue[service]["Port"] =
307                                         port;
308                                 }
309                             },
310                             "org.freedesktop.systemd1", socketPath,
311                             "org.freedesktop.DBus.Properties", "Get",
312                             "org.freedesktop.systemd1.Socket", "Listen");
313                     }
314                 }
315             },
316             "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
317             "org.freedesktop.systemd1.Manager", "ListUnits");
318     }
319 
320     void handleHostnamePatch(const std::string& hostName,
321                              const std::shared_ptr<AsyncResp>& asyncResp)
322     {
323         crow::connections::systemBus->async_method_call(
324             [asyncResp](const boost::system::error_code ec) {
325                 if (ec)
326                 {
327                     messages::internalError(asyncResp->res);
328                     return;
329                 }
330                 messages::success(asyncResp->res);
331             },
332             "xyz.openbmc_project.Network",
333             "/xyz/openbmc_project/network/config",
334             "org.freedesktop.DBus.Properties", "Set",
335             "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
336             std::variant<std::string>(hostName));
337     }
338 
339     void handleNTPProtocolEnabled(const bool& ntpEnabled,
340                                   const std::shared_ptr<AsyncResp>& asyncResp)
341     {
342         std::string timeSyncMethod;
343         if (ntpEnabled)
344         {
345             timeSyncMethod =
346                 "xyz.openbmc_project.Time.Synchronization.Method.NTP";
347         }
348         else
349         {
350             timeSyncMethod =
351                 "xyz.openbmc_project.Time.Synchronization.Method.Manual";
352         }
353 
354         crow::connections::systemBus->async_method_call(
355             [asyncResp,
356              ntpEnabled](const boost::system::error_code error_code) {
357                 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = ntpEnabled;
358             },
359             "xyz.openbmc_project.Settings",
360             "/xyz/openbmc_project/time/sync_method",
361             "org.freedesktop.DBus.Properties", "Set",
362             "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
363             std::variant<std::string>{timeSyncMethod});
364     }
365 
366     void handleNTPServersPatch(const std::vector<std::string>& ntpServers,
367                                const std::shared_ptr<AsyncResp>& asyncResp)
368     {
369         crow::connections::systemBus->async_method_call(
370             [asyncResp, ntpServers](const boost::system::error_code ec) {
371                 if (ec)
372                 {
373                     messages::internalError(asyncResp->res);
374                     return;
375                 }
376                 asyncResp->res.jsonValue["NTP"]["NTPServers"] =
377                     std::move(ntpServers);
378             },
379             "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0",
380             "org.freedesktop.DBus.Properties", "Set",
381             "xyz.openbmc_project.Network.EthernetInterface", "NTPServers",
382             std::variant<std::vector<std::string>>{ntpServers});
383     }
384 
385     void doPatch(crow::Response& res, const crow::Request& req,
386                  const std::vector<std::string>& params) override
387     {
388         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
389         std::optional<std::string> newHostName;
390         std::optional<std::vector<std::string>> ntpServers;
391         std::optional<bool> ntpEnabled;
392 
393         if (!json_util::readJson(req, res, "HostName", newHostName,
394                                  "NTPServers", ntpServers, "NTPEnabled",
395                                  ntpEnabled))
396         {
397             return;
398         }
399         if (newHostName)
400         {
401             handleHostnamePatch(*newHostName, asyncResp);
402             return;
403         }
404         if (ntpEnabled)
405         {
406             handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
407         }
408         if (ntpServers)
409         {
410             handleNTPServersPatch(*ntpServers, asyncResp);
411         }
412     }
413 };
414 
415 } // namespace redfish
416