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