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