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