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