xref: /openbmc/bmcweb/features/redfish/lib/network_protocol.hpp (revision cf05f9dc31a47123521348bc870fc687e3f35b4c)
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
75     extractNTPServersAndDomainNamesData(const GetManagedObjects& dbus_data,
76                                         std::vector<std::string>& ntpData,
77                                         std::vector<std::string>& dnData)
78 {
79     for (const auto& obj : dbus_data)
80     {
81         for (const auto& ifacePair : obj.second)
82         {
83             if (obj.first == "/xyz/openbmc_project/network/eth0")
84             {
85                 if (ifacePair.first ==
86                     "xyz.openbmc_project.Network.EthernetInterface")
87                 {
88                     for (const auto& propertyPair : ifacePair.second)
89                     {
90                         if (propertyPair.first == "NTPServers")
91                         {
92                             const std::vector<std::string>* ntpServers =
93                                 sdbusplus::message::variant_ns::get_if<
94                                     std::vector<std::string>>(
95                                     &propertyPair.second);
96                             if (ntpServers != nullptr)
97                             {
98                                 ntpData = std::move(*ntpServers);
99                             }
100                         }
101                         else if (propertyPair.first == "DomainName")
102                         {
103                             const std::vector<std::string>* domainNames =
104                                 sdbusplus::message::variant_ns::get_if<
105                                     std::vector<std::string>>(
106                                     &propertyPair.second);
107                             if (domainNames != nullptr)
108                             {
109                                 dnData = std::move(*domainNames);
110                             }
111                         }
112                     }
113                 }
114             }
115         }
116     }
117 }
118 
119 template <typename CallbackFunc>
120 void getEthernetIfaceData(CallbackFunc&& callback)
121 {
122     crow::connections::systemBus->async_method_call(
123         [callback{std::move(callback)}](
124             const boost::system::error_code error_code,
125             const GetManagedObjects& dbus_data) {
126             std::vector<std::string> ntpServers;
127             std::vector<std::string> domainNames;
128 
129             if (error_code)
130             {
131                 callback(false, ntpServers, domainNames);
132                 return;
133             }
134 
135             extractNTPServersAndDomainNamesData(dbus_data, ntpServers,
136                                                 domainNames);
137 
138             callback(true, ntpServers, domainNames);
139         },
140         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
141         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
142 };
143 
144 class NetworkProtocol : public Node
145 {
146   public:
147     NetworkProtocol(CrowApp& app) :
148         Node(app, "/redfish/v1/Managers/bmc/NetworkProtocol")
149     {
150         entityPrivileges = {
151             {boost::beast::http::verb::get, {{"Login"}}},
152             {boost::beast::http::verb::head, {{"Login"}}},
153             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
154             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
155             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
156             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
157     }
158 
159   private:
160     void doGet(crow::Response& res, const crow::Request& req,
161                const std::vector<std::string>& params) override
162     {
163         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
164 
165         getData(asyncResp);
166     }
167 
168     std::string getHostName() const
169     {
170         std::string hostName;
171 
172         std::array<char, HOST_NAME_MAX> hostNameCStr;
173         if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0)
174         {
175             hostName = hostNameCStr.data();
176         }
177         return hostName;
178     }
179 
180     void getNTPProtocolEnabled(const std::shared_ptr<AsyncResp>& asyncResp)
181     {
182         crow::connections::systemBus->async_method_call(
183             [asyncResp](const boost::system::error_code error_code,
184                         const std::variant<std::string>& timeSyncMethod) {
185                 const std::string* s =
186                     std::get_if<std::string>(&timeSyncMethod);
187 
188                 if (*s == "xyz.openbmc_project.Time.Synchronization.Method.NTP")
189                 {
190                     asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
191                 }
192                 else if (*s == "xyz.openbmc_project.Time.Synchronization."
193                                "Method.Manual")
194                 {
195                     asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
196                 }
197             },
198             "xyz.openbmc_project.Settings",
199             "/xyz/openbmc_project/time/sync_method",
200             "org.freedesktop.DBus.Properties", "Get",
201             "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod");
202     }
203 
204     void getData(const std::shared_ptr<AsyncResp>& asyncResp)
205     {
206         asyncResp->res.jsonValue["@odata.type"] =
207             "#ManagerNetworkProtocol.v1_4_0.ManagerNetworkProtocol";
208         asyncResp->res.jsonValue["@odata.id"] =
209             "/redfish/v1/Managers/bmc/NetworkProtocol";
210         asyncResp->res.jsonValue["@odata.context"] =
211             "/redfish/v1/"
212             "$metadata#ManagerNetworkProtocol.ManagerNetworkProtocol";
213         asyncResp->res.jsonValue["Id"] = "NetworkProtocol";
214         asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
215         asyncResp->res.jsonValue["Description"] = "Manager Network Service";
216         asyncResp->res.jsonValue["Status"]["Health"] = "OK";
217         asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
218         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
219 
220         for (auto& protocol : protocolToDBus)
221         {
222             asyncResp->res.jsonValue[protocol.first]["ProtocolEnabled"] = false;
223         }
224 
225         std::string hostName = getHostName();
226 
227         asyncResp->res.jsonValue["HostName"] = hostName;
228 
229         getNTPProtocolEnabled(asyncResp);
230 
231         // TODO Get eth0 interface data, and call the below callback for JSON
232         // preparation
233         getEthernetIfaceData([this, hostName, asyncResp](
234                                  const bool& success,
235                                  const std::vector<std::string>& ntpServers,
236                                  const std::vector<std::string>& domainNames) {
237             if (!success)
238             {
239                 messages::resourceNotFound(asyncResp->res, "EthernetInterface",
240                                            "eth0");
241                 return;
242             }
243             asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
244             if (hostName.empty() == false)
245             {
246                 asyncResp->res.jsonValue["FQDN"] = hostName;
247                 if (domainNames.empty() == false)
248                 {
249                     asyncResp->res.jsonValue["FQDN"] += "." + domainNames[0];
250                 }
251             }
252         });
253 
254         crow::connections::systemBus->async_method_call(
255             [asyncResp](const boost::system::error_code ec,
256                         const std::vector<UnitStruct>& resp) {
257                 if (ec)
258                 {
259                     asyncResp->res.jsonValue = nlohmann::json::object();
260                     messages::internalError(asyncResp->res);
261                     return;
262                 }
263                 asyncResp->res.jsonValue["HTTPS"]["Certificates"] = {
264                     {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol/"
265                                   "HTTPS/Certificates/"}};
266 
267                 for (auto& unit : resp)
268                 {
269                     for (auto& kv : protocolToDBus)
270                     {
271                         if (kv.second.serviceName !=
272                             std::get<NET_PROTO_UNIT_NAME>(unit))
273                         {
274                             continue;
275                         }
276                         const char* service = kv.first;
277                         const char* socketPath = kv.second.socketPath;
278 
279                         asyncResp->res.jsonValue[service]["ProtocolEnabled"] =
280                             (std::get<NET_PROTO_UNIT_SUB_STATE>(unit) ==
281                              "running") ||
282                             (std::get<NET_PROTO_UNIT_SUB_STATE>(unit) ==
283                              "listening");
284                         crow::connections::systemBus->async_method_call(
285                             [asyncResp, service{std::string(service)}](
286                                 const boost::system::error_code ec,
287                                 const std::variant<std::vector<std::tuple<
288                                     std::string, std::string>>>& resp) {
289                                 if (ec)
290                                 {
291                                     messages::internalError(asyncResp->res);
292                                     return;
293                                 }
294                                 const std::vector<
295                                     std::tuple<std::string, std::string>>*
296                                     responsePtr = std::get_if<std::vector<
297                                         std::tuple<std::string, std::string>>>(
298                                         &resp);
299                                 if (responsePtr == nullptr ||
300                                     responsePtr->size() < 1)
301                                 {
302                                     return;
303                                 }
304 
305                                 const std::string& listenStream =
306                                     std::get<NET_PROTO_LISTEN_STREAM>(
307                                         (*responsePtr)[0]);
308                                 std::size_t lastColonPos =
309                                     listenStream.rfind(":");
310                                 if (lastColonPos == std::string::npos)
311                                 {
312                                     // Not a port
313                                     return;
314                                 }
315                                 std::string portStr =
316                                     listenStream.substr(lastColonPos + 1);
317                                 char* endPtr = nullptr;
318                                 // Use strtol instead of stroi to avoid
319                                 // exceptions
320                                 long port =
321                                     std::strtol(portStr.c_str(), &endPtr, 10);
322                                 if (*endPtr != '\0' || portStr.empty())
323                                 {
324                                     // Invalid value
325                                     asyncResp->res.jsonValue[service]["Port"] =
326                                         nullptr;
327                                 }
328                                 else
329                                 {
330                                     // Everything OK
331                                     asyncResp->res.jsonValue[service]["Port"] =
332                                         port;
333                                 }
334                             },
335                             "org.freedesktop.systemd1", socketPath,
336                             "org.freedesktop.DBus.Properties", "Get",
337                             "org.freedesktop.systemd1.Socket", "Listen");
338                     }
339                 }
340             },
341             "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
342             "org.freedesktop.systemd1.Manager", "ListUnits");
343     }
344 
345     void handleHostnamePatch(const std::string& hostName,
346                              const std::shared_ptr<AsyncResp>& asyncResp)
347     {
348         crow::connections::systemBus->async_method_call(
349             [asyncResp](const boost::system::error_code ec) {
350                 if (ec)
351                 {
352                     messages::internalError(asyncResp->res);
353                     return;
354                 }
355             },
356             "xyz.openbmc_project.Network",
357             "/xyz/openbmc_project/network/config",
358             "org.freedesktop.DBus.Properties", "Set",
359             "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
360             std::variant<std::string>(hostName));
361     }
362 
363     void handleNTPProtocolEnabled(const bool& ntpEnabled,
364                                   const std::shared_ptr<AsyncResp>& asyncResp)
365     {
366         std::string timeSyncMethod;
367         if (ntpEnabled)
368         {
369             timeSyncMethod =
370                 "xyz.openbmc_project.Time.Synchronization.Method.NTP";
371         }
372         else
373         {
374             timeSyncMethod =
375                 "xyz.openbmc_project.Time.Synchronization.Method.Manual";
376         }
377 
378         crow::connections::systemBus->async_method_call(
379             [asyncResp](const boost::system::error_code error_code) {},
380             "xyz.openbmc_project.Settings",
381             "/xyz/openbmc_project/time/sync_method",
382             "org.freedesktop.DBus.Properties", "Set",
383             "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
384             std::variant<std::string>{timeSyncMethod});
385     }
386 
387     void handleNTPServersPatch(const std::vector<std::string>& ntpServers,
388                                const std::shared_ptr<AsyncResp>& asyncResp)
389     {
390         crow::connections::systemBus->async_method_call(
391             [asyncResp](const boost::system::error_code ec) {
392                 if (ec)
393                 {
394                     messages::internalError(asyncResp->res);
395                     return;
396                 }
397             },
398             "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0",
399             "org.freedesktop.DBus.Properties", "Set",
400             "xyz.openbmc_project.Network.EthernetInterface", "NTPServers",
401             std::variant<std::vector<std::string>>{ntpServers});
402     }
403 
404     void doPatch(crow::Response& res, const crow::Request& req,
405                  const std::vector<std::string>& params) override
406     {
407         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
408         std::optional<std::string> newHostName;
409         std::optional<nlohmann::json> ntp;
410 
411         if (!json_util::readJson(req, res, "HostName", newHostName, "NTP", ntp))
412         {
413             return;
414         }
415 
416         res.result(boost::beast::http::status::no_content);
417         if (newHostName)
418         {
419             handleHostnamePatch(*newHostName, asyncResp);
420         }
421 
422         if (ntp)
423         {
424             std::optional<std::vector<std::string>> ntpServers;
425             std::optional<bool> ntpEnabled;
426             if (!json_util::readJson(*ntp, res, "NTPServers", ntpServers,
427                                      "ProtocolEnabled", ntpEnabled))
428             {
429                 return;
430             }
431 
432             if (ntpEnabled)
433             {
434                 handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
435             }
436 
437             if (ntpServers)
438             {
439                 handleNTPServersPatch(*ntpServers, asyncResp);
440             }
441         }
442     }
443 };
444 
445 } // namespace redfish
446