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