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