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 if (!json_util::readJsonPatch(
511 req, asyncResp->res, //
512 "HostName", newHostName, //
513 "NTP/NTPServers", ntpServerObjects, //
514 "NTP/ProtocolEnabled", ntpEnabled, //
515 "IPMI/ProtocolEnabled", ipmiEnabled, //
516 "SSH/ProtocolEnabled", sshEnabled //
517 ))
518 {
519 return;
520 }
521
522 asyncResp->res.result(boost::beast::http::status::no_content);
523 if (newHostName)
524 {
525 messages::propertyNotWritable(asyncResp->res, "HostName");
526 return;
527 }
528
529 if (ntpEnabled)
530 {
531 handleNTPProtocolEnabled(asyncResp, *ntpEnabled);
532 }
533 if (ntpServerObjects)
534 {
535 getEthernetIfaceData(
536 [asyncResp, ntpServerObjects](
537 const bool success, std::vector<std::string>& currentNtpServers,
538 const std::vector<std::string>& /*dynamicNtpServers*/,
539 const std::vector<std::string>& /*domainNames*/) {
540 if (!success)
541 {
542 messages::internalError(asyncResp->res);
543 return;
544 }
545 handleNTPServersPatch(asyncResp, *ntpServerObjects,
546 std::move(currentNtpServers));
547 });
548 }
549
550 if (ipmiEnabled)
551 {
552 handleProtocolEnabled(
553 *ipmiEnabled, asyncResp,
554 encodeServiceObjectPath(std::string(ipmiServiceName) + '@'));
555 }
556
557 if (sshEnabled)
558 {
559 handleProtocolEnabled(*sshEnabled, asyncResp,
560 encodeServiceObjectPath(sshServiceName));
561 }
562 }
563
handleManagersNetworkProtocolHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)564 inline void handleManagersNetworkProtocolHead(
565 App& app, const crow::Request& req,
566 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
567 const std::string& managerId)
568 {
569 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
570 {
571 return;
572 }
573 asyncResp->res.addHeader(
574 boost::beast::http::field::link,
575 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
576 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
577 {
578 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
579 return;
580 }
581 }
582
handleManagersNetworkProtocolGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)583 inline void handleManagersNetworkProtocolGet(
584 App& app, const crow::Request& req,
585 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
586 const std::string& managerId)
587 {
588 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
589 {
590 return;
591 }
592 asyncResp->res.addHeader(
593 boost::beast::http::field::link,
594 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
595 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
596 {
597 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
598 return;
599 }
600
601 getNetworkData(asyncResp, req);
602 }
603
requestRoutesNetworkProtocol(App & app)604 inline void requestRoutesNetworkProtocol(App& app)
605 {
606 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
607 .privileges(redfish::privileges::patchManagerNetworkProtocol)
608 .methods(boost::beast::http::verb::patch)(
609 std::bind_front(handleManagersNetworkProtocolPatch, std::ref(app)));
610
611 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
612 .privileges(redfish::privileges::headManagerNetworkProtocol)
613 .methods(boost::beast::http::verb::head)(
614 std::bind_front(handleManagersNetworkProtocolHead, std::ref(app)));
615
616 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
617 .privileges(redfish::privileges::getManagerNetworkProtocol)
618 .methods(boost::beast::http::verb::get)(
619 std::bind_front(handleManagersNetworkProtocolGet, std::ref(app)));
620 }
621
622 } // namespace redfish
623