xref: /openbmc/bmcweb/redfish-core/lib/network_protocol.hpp (revision 4d7b5ddb3a2b6cc42b7bbc0c710f297e6df4fd55)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
4 #pragma once
5 
6 #include "bmcweb_config.h"
7 
8 #include "app.hpp"
9 #include "async_resp.hpp"
10 #include "dbus_singleton.hpp"
11 #include "dbus_utility.hpp"
12 #include "error_messages.hpp"
13 #include "generated/enums/resource.hpp"
14 #include "http_request.hpp"
15 #include "identity.hpp"
16 #include "logging.hpp"
17 #include "privileges.hpp"
18 #include "query.hpp"
19 #include "redfish_util.hpp"
20 #include "registries/privilege_registry.hpp"
21 #include "utils/dbus_utils.hpp"
22 #include "utils/json_utils.hpp"
23 #include "utils/stl_utils.hpp"
24 
25 #include <boost/beast/http/field.hpp>
26 #include <boost/beast/http/status.hpp>
27 #include <boost/beast/http/verb.hpp>
28 #include <boost/system/error_code.hpp>
29 #include <boost/url/format.hpp>
30 #include <sdbusplus/message/native_types.hpp>
31 
32 #include <array>
33 #include <cstddef>
34 #include <functional>
35 #include <memory>
36 #include <optional>
37 #include <string>
38 #include <string_view>
39 #include <tuple>
40 #include <utility>
41 #include <variant>
42 #include <vector>
43 
44 namespace redfish
45 {
46 
47 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp);
48 
49 static constexpr std::string_view sshServiceName = "dropbear";
50 static constexpr std::string_view httpsServiceName = "bmcweb";
51 static constexpr std::string_view ipmiServiceName = "phosphor-ipmi-net";
52 
53 // Mapping from Redfish NetworkProtocol key name to backend service that hosts
54 // that protocol.
55 static constexpr std::array<std::pair<std::string_view, std::string_view>, 3>
56     networkProtocolToDbus = {{{"SSH", sshServiceName},
57                               {"HTTPS", httpsServiceName},
58                               {"IPMI", ipmiServiceName}}};
59 
extractNTPServersAndDomainNamesData(const dbus::utility::ManagedObjectType & dbusData,std::vector<std::string> & ntpData,std::vector<std::string> & dynamicNtpData,std::vector<std::string> & dnData)60 inline void extractNTPServersAndDomainNamesData(
61     const dbus::utility::ManagedObjectType& dbusData,
62     std::vector<std::string>& ntpData, std::vector<std::string>& dynamicNtpData,
63     std::vector<std::string>& dnData)
64 {
65     for (const auto& obj : dbusData)
66     {
67         for (const auto& ifacePair : obj.second)
68         {
69             if (ifacePair.first !=
70                 "xyz.openbmc_project.Network.EthernetInterface")
71             {
72                 continue;
73             }
74 
75             for (const auto& propertyPair : ifacePair.second)
76             {
77                 if (propertyPair.first == "StaticNTPServers")
78                 {
79                     const std::vector<std::string>* ntpServers =
80                         std::get_if<std::vector<std::string>>(
81                             &propertyPair.second);
82                     if (ntpServers != nullptr)
83                     {
84                         ntpData.insert(ntpData.end(), ntpServers->begin(),
85                                        ntpServers->end());
86                     }
87                 }
88                 else if (propertyPair.first == "NTPServers")
89                 {
90                     const std::vector<std::string>* dynamicNtpServers =
91                         std::get_if<std::vector<std::string>>(
92                             &propertyPair.second);
93                     if (dynamicNtpServers != nullptr)
94                     {
95                         dynamicNtpData = *dynamicNtpServers;
96                     }
97                 }
98                 else if (propertyPair.first == "DomainName")
99                 {
100                     const std::vector<std::string>* domainNames =
101                         std::get_if<std::vector<std::string>>(
102                             &propertyPair.second);
103                     if (domainNames != nullptr)
104                     {
105                         dnData.insert(dnData.end(), domainNames->begin(),
106                                       domainNames->end());
107                     }
108                 }
109             }
110         }
111     }
112     stl_utils::removeDuplicate(ntpData);
113     stl_utils::removeDuplicate(dnData);
114 }
115 
116 template <typename CallbackFunc>
getEthernetIfaceData(CallbackFunc && callback)117 void getEthernetIfaceData(CallbackFunc&& callback)
118 {
119     sdbusplus::message::object_path path("/xyz/openbmc_project/network");
120     dbus::utility::getManagedObjects(
121         "xyz.openbmc_project.Network", path,
122         [callback = std::forward<CallbackFunc>(callback)](
123             const boost::system::error_code& ec,
124             const dbus::utility::ManagedObjectType& dbusData) {
125             std::vector<std::string> ntpServers;
126             std::vector<std::string> dynamicNtpServers;
127             std::vector<std::string> domainNames;
128 
129             if (ec)
130             {
131                 callback(false, ntpServers, dynamicNtpServers, domainNames);
132                 return;
133             }
134 
135             extractNTPServersAndDomainNamesData(dbusData, ntpServers,
136                                                 dynamicNtpServers, domainNames);
137 
138             callback(true, ntpServers, dynamicNtpServers, domainNames);
139         });
140 }
141 
afterNetworkPortRequest(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const std::vector<std::tuple<std::string,std::string,bool>> & socketData)142 inline void afterNetworkPortRequest(
143     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
144     const boost::system::error_code& ec,
145     const std::vector<std::tuple<std::string, std::string, bool>>& socketData)
146 {
147     if (ec)
148     {
149         messages::internalError(asyncResp->res);
150         return;
151     }
152     for (const auto& data : socketData)
153     {
154         const std::string& socketPath = get<0>(data);
155         const std::string& protocolName = get<1>(data);
156         bool isProtocolEnabled = get<2>(data);
157 
158         asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] =
159             isProtocolEnabled;
160         asyncResp->res.jsonValue[protocolName]["Port"] = nullptr;
161         getPortNumber(socketPath, [asyncResp, protocolName](
162                                       const boost::system::error_code& ec2,
163                                       int portNumber) {
164             if (ec2)
165             {
166                 messages::internalError(asyncResp->res);
167                 return;
168             }
169             asyncResp->res.jsonValue[protocolName]["Port"] = portNumber;
170         });
171     }
172 }
173 
getNetworkData(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req)174 inline void getNetworkData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
175                            const crow::Request& req)
176 {
177     if (req.session == nullptr)
178     {
179         messages::internalError(asyncResp->res);
180         return;
181     }
182 
183     asyncResp->res.addHeader(
184         boost::beast::http::field::link,
185         "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/NetworkProtocol.json>; rel=describedby");
186     asyncResp->res.jsonValue["@odata.type"] =
187         "#ManagerNetworkProtocol.v1_9_0.ManagerNetworkProtocol";
188     asyncResp->res.jsonValue["@odata.id"] =
189         boost::urls::format("/redfish/v1/Managers/{}/NetworkProtocol",
190                             BMCWEB_REDFISH_MANAGER_URI_NAME);
191     asyncResp->res.jsonValue["Id"] = "NetworkProtocol";
192     asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
193     asyncResp->res.jsonValue["Description"] = "Manager Network Service";
194     asyncResp->res.jsonValue["Status"]["Health"] = resource::Health::OK;
195     asyncResp->res.jsonValue["Status"]["HealthRollup"] = resource::Health::OK;
196     asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
197 
198     // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0,
199     // but from security perspective it is not recommended to use.
200     // Hence using protocolEnabled as false to make it OCP and security-wise
201     // compliant
202     asyncResp->res.jsonValue["HTTP"]["Port"] = nullptr;
203     asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false;
204 
205     // The ProtocolEnabled of the following protocols is determined by
206     // inspecting the state of associated systemd sockets. If these protocols
207     // have been disabled, then the systemd socket unit files will not be found
208     // and the protocols will not be returned in this Redfish query. Set some
209     // defaults to ensure something is always returned.
210     for (const auto& nwkProtocol : networkProtocolToDbus)
211     {
212         asyncResp->res.jsonValue[nwkProtocol.first]["Port"] = nullptr;
213         asyncResp->res.jsonValue[nwkProtocol.first]["ProtocolEnabled"] = false;
214     }
215 
216     std::string hostName = getHostName();
217 
218     asyncResp->res.jsonValue["HostName"] = hostName;
219 
220     getNTPProtocolEnabled(asyncResp);
221 
222     getEthernetIfaceData([hostName, asyncResp](
223                              const bool& success,
224                              const std::vector<std::string>& ntpServers,
225                              const std::vector<std::string>& dynamicNtpServers,
226                              const std::vector<std::string>& domainNames) {
227         if (!success)
228         {
229             messages::resourceNotFound(asyncResp->res, "ManagerNetworkProtocol",
230                                        "NetworkProtocol");
231             return;
232         }
233         asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
234         asyncResp->res.jsonValue["NTP"]["NetworkSuppliedServers"] =
235             dynamicNtpServers;
236         if (!hostName.empty())
237         {
238             std::string fqdn = hostName;
239             if (!domainNames.empty())
240             {
241                 fqdn += ".";
242                 fqdn += domainNames[0];
243             }
244             asyncResp->res.jsonValue["FQDN"] = std::move(fqdn);
245         }
246     });
247 
248     Privileges effectiveUserPrivileges =
249         redfish::getUserPrivileges(*req.session);
250 
251     // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is
252     // something only ConfigureManager can access then only display when
253     // the user has permissions ConfigureManager
254     if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
255                                          effectiveUserPrivileges))
256     {
257         asyncResp->res.jsonValue["HTTPS"]["Certificates"]["@odata.id"] =
258             boost::urls::format(
259                 "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates",
260                 BMCWEB_REDFISH_MANAGER_URI_NAME);
261     }
262 
263     getPortStatusAndPath(std::span(networkProtocolToDbus),
264                          std::bind_front(afterNetworkPortRequest, asyncResp));
265 } // namespace redfish
266 
afterSetNTP(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec)267 inline void afterSetNTP(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
268                         const boost::system::error_code& ec)
269 {
270     if (ec)
271     {
272         BMCWEB_LOG_DEBUG("Failed to set elapsed time. DBUS response error {}",
273                          ec);
274         messages::internalError(asyncResp->res);
275         return;
276     }
277     asyncResp->res.result(boost::beast::http::status::no_content);
278 }
279 
handleNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,bool ntpEnabled)280 inline void handleNTPProtocolEnabled(
281     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, bool ntpEnabled)
282 {
283     bool interactive = false;
284     auto callback = [asyncResp](const boost::system::error_code& ec) {
285         afterSetNTP(asyncResp, ec);
286     };
287     crow::connections::systemBus->async_method_call(
288         std::move(callback), "org.freedesktop.timedate1",
289         "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "SetNTP",
290         ntpEnabled, interactive);
291 }
292 
293 // Redfish states that ip addresses can be
294 // string, to set a value
295 // null, to delete the value
296 // object_t, empty json object, to ignore the value
297 using IpAddress =
298     std::variant<std::string, nlohmann::json::object_t, std::nullptr_t>;
299 
handleNTPServersPatch(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::vector<IpAddress> & ntpServerObjects,std::vector<std::string> currentNtpServers)300 inline void handleNTPServersPatch(
301     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
302     const std::vector<IpAddress>& ntpServerObjects,
303     std::vector<std::string> currentNtpServers)
304 {
305     std::vector<std::string>::iterator currentNtpServer =
306         currentNtpServers.begin();
307     for (size_t index = 0; index < ntpServerObjects.size(); index++)
308     {
309         const IpAddress& ntpServer = ntpServerObjects[index];
310         if (std::holds_alternative<std::nullptr_t>(ntpServer))
311         {
312             // Can't delete an item that doesn't exist
313             if (currentNtpServer == currentNtpServers.end())
314             {
315                 messages::propertyValueNotInList(
316                     asyncResp->res, "null",
317                     "NTP/NTPServers/" + std::to_string(index));
318 
319                 return;
320             }
321             currentNtpServer = currentNtpServers.erase(currentNtpServer);
322             continue;
323         }
324         const nlohmann::json::object_t* ntpServerObject =
325             std::get_if<nlohmann::json::object_t>(&ntpServer);
326         if (ntpServerObject != nullptr)
327         {
328             if (!ntpServerObject->empty())
329             {
330                 messages::propertyValueNotInList(
331                     asyncResp->res, *ntpServerObject,
332                     "NTP/NTPServers/" + std::to_string(index));
333                 return;
334             }
335             // Can't retain an item that doesn't exist
336             if (currentNtpServer == currentNtpServers.end())
337             {
338                 messages::propertyValueOutOfRange(
339                     asyncResp->res, *ntpServerObject,
340                     "NTP/NTPServers/" + std::to_string(index));
341 
342                 return;
343             }
344             // empty objects should leave the NtpServer unmodified
345             currentNtpServer++;
346             continue;
347         }
348 
349         const std::string* ntpServerStr = std::get_if<std::string>(&ntpServer);
350         if (ntpServerStr == nullptr)
351         {
352             messages::internalError(asyncResp->res);
353             return;
354         }
355         if (currentNtpServer == currentNtpServers.end())
356         {
357             // if we're at the end of the list, append to the end
358             currentNtpServers.push_back(*ntpServerStr);
359             currentNtpServer = currentNtpServers.end();
360             continue;
361         }
362         *currentNtpServer = *ntpServerStr;
363         currentNtpServer++;
364     }
365 
366     // Any remaining array elements should be removed
367     currentNtpServers.erase(currentNtpServer, currentNtpServers.end());
368 
369     constexpr std::array<std::string_view, 1> ethInterfaces = {
370         "xyz.openbmc_project.Network.EthernetInterface"};
371     dbus::utility::getSubTree(
372         "/xyz/openbmc_project", 0, ethInterfaces,
373         [asyncResp, currentNtpServers](
374             const boost::system::error_code& ec,
375             const dbus::utility::MapperGetSubTreeResponse& subtree) {
376             if (ec)
377             {
378                 BMCWEB_LOG_WARNING("D-Bus error: {}, {}", ec, ec.message());
379                 messages::internalError(asyncResp->res);
380                 return;
381             }
382 
383             for (const auto& [objectPath, serviceMap] : subtree)
384             {
385                 for (const auto& [service, interfaces] : serviceMap)
386                 {
387                     for (const auto& interface : interfaces)
388                     {
389                         if (interface !=
390                             "xyz.openbmc_project.Network.EthernetInterface")
391                         {
392                             continue;
393                         }
394 
395                         setDbusProperty(asyncResp, "NTP/NTPServers/", service,
396                                         objectPath, interface,
397                                         "StaticNTPServers", currentNtpServers);
398                     }
399                 }
400             }
401         });
402 }
403 
handleProtocolEnabled(const bool protocolEnabled,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & netBasePath)404 inline void handleProtocolEnabled(
405     const bool protocolEnabled,
406     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
407     const std::string& netBasePath)
408 {
409     constexpr std::array<std::string_view, 1> interfaces = {
410         "xyz.openbmc_project.Control.Service.Attributes"};
411     dbus::utility::getSubTree(
412         "/xyz/openbmc_project/control/service", 0, interfaces,
413         [protocolEnabled, asyncResp,
414          netBasePath](const boost::system::error_code& ec,
415                       const dbus::utility::MapperGetSubTreeResponse& subtree) {
416             if (ec)
417             {
418                 messages::internalError(asyncResp->res);
419                 return;
420             }
421 
422             for (const auto& entry : subtree)
423             {
424                 if (entry.first.starts_with(netBasePath))
425                 {
426                     setDbusProperty(
427                         asyncResp, "IPMI/ProtocolEnabled",
428                         entry.second.begin()->first, entry.first,
429                         "xyz.openbmc_project.Control.Service.Attributes",
430                         "Running", protocolEnabled);
431                     setDbusProperty(
432                         asyncResp, "IPMI/ProtocolEnabled",
433                         entry.second.begin()->first, entry.first,
434                         "xyz.openbmc_project.Control.Service.Attributes",
435                         "Enabled", protocolEnabled);
436                 }
437             }
438         });
439 }
440 
getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)441 inline void getNTPProtocolEnabled(
442     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
443 {
444     dbus::utility::getProperty<bool>(
445         "org.freedesktop.timedate1", "/org/freedesktop/timedate1",
446         "org.freedesktop.timedate1", "NTP",
447         [asyncResp](const boost::system::error_code& ec, bool enabled) {
448             if (ec)
449             {
450                 BMCWEB_LOG_WARNING(
451                     "Failed to get NTP status, assuming not supported");
452                 return;
453             }
454 
455             asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = enabled;
456         });
457 }
458 
encodeServiceObjectPath(std::string_view serviceName)459 inline std::string encodeServiceObjectPath(std::string_view serviceName)
460 {
461     sdbusplus::message::object_path objPath(
462         "/xyz/openbmc_project/control/service");
463     objPath /= serviceName;
464     return objPath.str;
465 }
466 
handleBmcNetworkProtocolHead(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)467 inline void handleBmcNetworkProtocolHead(
468     crow::App& app, const crow::Request& req,
469     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
470 {
471     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
472     {
473         return;
474     }
475     asyncResp->res.addHeader(
476         boost::beast::http::field::link,
477         "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
478 }
479 
handleManagersNetworkProtocolPatch(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)480 inline void handleManagersNetworkProtocolPatch(
481     App& app, const crow::Request& req,
482     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
483     const std::string& managerId)
484 {
485     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
486     {
487         return;
488     }
489 
490     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
491     {
492         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
493         return;
494     }
495 
496     std::optional<std::string> newHostName;
497 
498     std::optional<std::vector<IpAddress>> ntpServerObjects;
499     std::optional<bool> ntpEnabled;
500     std::optional<bool> ipmiEnabled;
501     std::optional<bool> sshEnabled;
502 
503     if (!json_util::readJsonPatch(
504             req, asyncResp->res,                 //
505             "HostName", newHostName,             //
506             "NTP/NTPServers", ntpServerObjects,  //
507             "NTP/ProtocolEnabled", ntpEnabled,   //
508             "IPMI/ProtocolEnabled", ipmiEnabled, //
509             "SSH/ProtocolEnabled", sshEnabled    //
510             ))
511     {
512         return;
513     }
514 
515     asyncResp->res.result(boost::beast::http::status::no_content);
516     if (newHostName)
517     {
518         messages::propertyNotWritable(asyncResp->res, "HostName");
519         return;
520     }
521 
522     if (ntpEnabled)
523     {
524         handleNTPProtocolEnabled(asyncResp, *ntpEnabled);
525     }
526     if (ntpServerObjects)
527     {
528         getEthernetIfaceData(
529             [asyncResp, ntpServerObjects](
530                 const bool success, std::vector<std::string>& currentNtpServers,
531                 const std::vector<std::string>& /*dynamicNtpServers*/,
532                 const std::vector<std::string>& /*domainNames*/) {
533                 if (!success)
534                 {
535                     messages::internalError(asyncResp->res);
536                     return;
537                 }
538                 handleNTPServersPatch(asyncResp, *ntpServerObjects,
539                                       std::move(currentNtpServers));
540             });
541     }
542 
543     if (ipmiEnabled)
544     {
545         handleProtocolEnabled(
546             *ipmiEnabled, asyncResp,
547             encodeServiceObjectPath(std::string(ipmiServiceName) + '@'));
548     }
549 
550     if (sshEnabled)
551     {
552         handleProtocolEnabled(*sshEnabled, asyncResp,
553                               encodeServiceObjectPath(sshServiceName));
554     }
555 }
556 
handleManagersNetworkProtocolHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)557 inline void handleManagersNetworkProtocolHead(
558     App& app, const crow::Request& req,
559     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
560     const std::string& managerId)
561 {
562     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
563     {
564         return;
565     }
566     asyncResp->res.addHeader(
567         boost::beast::http::field::link,
568         "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
569     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
570     {
571         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
572         return;
573     }
574 }
575 
handleManagersNetworkProtocolGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)576 inline void handleManagersNetworkProtocolGet(
577     App& app, const crow::Request& req,
578     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
579     const std::string& managerId)
580 {
581     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
582     {
583         return;
584     }
585     asyncResp->res.addHeader(
586         boost::beast::http::field::link,
587         "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
588     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
589     {
590         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
591         return;
592     }
593 
594     getNetworkData(asyncResp, req);
595 }
596 
requestRoutesNetworkProtocol(App & app)597 inline void requestRoutesNetworkProtocol(App& app)
598 {
599     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
600         .privileges(redfish::privileges::patchManagerNetworkProtocol)
601         .methods(boost::beast::http::verb::patch)(
602             std::bind_front(handleManagersNetworkProtocolPatch, std::ref(app)));
603 
604     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
605         .privileges(redfish::privileges::headManagerNetworkProtocol)
606         .methods(boost::beast::http::verb::head)(
607             std::bind_front(handleManagersNetworkProtocolHead, std::ref(app)));
608 
609     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
610         .privileges(redfish::privileges::getManagerNetworkProtocol)
611         .methods(boost::beast::http::verb::get)(
612             std::bind_front(handleManagersNetworkProtocolGet, std::ref(app)));
613 }
614 
615 } // namespace redfish
616